Swift/Swift 기본문법

Swift 기본 문법 - 12. 프로퍼티와 메서드

728x90

프로퍼티는 클래스, 구조체, 또는 열거형 등에 관련된 값을 뜻합니다.

메서드는 특정 타입에 관련된 함수를 뜻합니다.

 

12.1.1 저장프로퍼티

클래스의 경우 옵셔널을 준다면, init을 통해 초기값을 주지않아도 괜찮습니다.

나중에 값을 알게되었을 때 class에 값을 넣어줄 수 도 있습니다.

// 좌표
struct CoordinatePoint {
    var x: Int = 0  // 저장 프로퍼티
    var y: Int = 0  // 저장 프로퍼티
}

// 구조체에는 기본적으로 저장 프로퍼티를 매개변수로 갖는 이니셜라이저가 있다.

let kemiPoint = CoordinatePoint(x: 3, y: 5)


//
class Position {
    var point: CoordinatePoint
    // 저장 프로퍼티(변수) - 위치(point)는 변경될 수 있음을 뜻합니다.
    let name: String
    
    init(name: String, currentPoint: CoordinatePoint) {
        self.name = name
        self.point = currentPoint
    }
}

let kemiPosition: Position = Position(name: "kemi", currentPoint: kemiPoint)
// 좌표
struct CoordinatePoint {
    var x: Int = 0  // 저장 프로퍼티
    var y: Int = 0  // 저장 프로퍼티
}

// 구조체에는 기본적으로 저장 프로퍼티를 매개변수로 갖는 이니셜라이저가 있다.




//
class Position {
    var point: CoordinatePoint?
    // 저장 프로퍼티(변수) - 위치(point)는 변경될 수 있음을 뜻합니다.
    // 옵셔널을 설정해 주면, init을 하지않아도 된다.
    let name: String
    
    init(name: String) {
        self.name = name
    }
}

let kemiPosition: Position = Position(name: "kemi")

// 위치를 알게되어 나중에 넣어줘도 된다.
kemiPosition.point = CoordinatePoint(x: 3, y: 5)

 

12.1.2 지연 저장 프로퍼티

지연 저장 프로퍼티는 호출이 있어야 값을 초기화하며, 이때 lazy 키워드를 사용합니다.

상수는 인스턴스가 완전히 생성되기 전에 초기화해야 하므로 필요 할 때 값을 할당하는 지연 저장프로퍼티와는 맞지 않습니다.

var 키워드를 사용하여 변수로 정의합니다.

 

지연저장프로퍼티는 이때 사용합니다.

클래스 프로퍼티에 다른 클래스 인스턴스나 구조체 인스턴스를 할당해야 할 때가 있습니다.

굳이 모든 저장프로퍼티를 사용 할 필요가 없다면, 불필요한 성능저하나 공간 낭비를 줄일 수 가 있습니다.

 

// 좌표
struct CoordinatePoint {
    var x: Int = 0  // 저장 프로퍼티
    var y: Int = 0  // 저장 프로퍼티
}

class Position {
    lazy var point: CoordinatePoint = CoordinatePoint()
    let name: String
    
    init(name: String) {
        self.name = name
    }
}

let kemiPosition: Position = Position(name: "kemi")

// 이 코드를 통해 point 프로퍼티로 처음 접근 할 때
// point 프로터피의 CoordinatePoint가 생성됩니다.
print(kemiPosition.point)

 

연산 프로퍼티

연산프로퍼티를 사용하면 코드가 좀더 심플해집니다.

// 연산프로퍼티를 사용하지 않았을 때,
struct CoordinatePoint {
    var x: Int // 저장 프로퍼티
    var y: Int // 저장 프로퍼티
    
    // 대칭정을 구하는 메서드 - 접근자
    func oppositePoint() -> Self {
        return CoordinatePoint(x: -x, y: -y)
    }
    
    // 대칭점을 설정하는 메서드 - 설정자
    mutating func setOppositePoint(_ opposite: CoordinatePoint) {
        x = -opposite.x
        y = -opposite.y
    }
}

var kemiPosition: CoordinatePoint = CoordinatePoint(x: 10, y: 20)

// 현재좌표
print(kemiPosition)

// 대칭좌표
print(kemiPosition.oppositePoint())

kemiPosition.setOppositePoint(CoordinatePoint(x: 15, y: 10))

// 현재 좌표는 -15, -10으로 설정됩니다.
print(kemiPosition)

// 연산 프로퍼티를 사용한 예시
struct CoordinatePoint {
    var x: Int // 저장 프로퍼티
    var y: Int // 저장 프로퍼티
    
    var oppsitePoint: CoordinatePoint { // 연산 프로퍼티
        // 접근자
        get {
            return CoordinatePoint(x: -x, y: -y)
        }
        
        // 생성자
        set(opposite) {
            x = -opposite.x
            y = -opposite.y
        }
        
    }
}

var kemiPosition: CoordinatePoint = CoordinatePoint(x: 10, y: 10)

// 현재 좌표
print(kemiPosition) // CoordinatePoint(x: 10, y: 10)

// 대칭 좌표
print(kemiPosition.oppsitePoint) // CoordinatePoint(x: -10, y: -10)

// 설정
kemiPosition.oppsitePoint = CoordinatePoint(x: 15, y: 10)
print(kemiPosition) // CoordinatePoint(x: -15, y: -10)

 

12.1.4 프로퍼티 감시자(Property Observers)

프로퍼티 감시자를 사용하면 프로퍼티의 값이 변경됨에 따라 적절한 작업을 취할 수 있습니다.

프로퍼티 값이 새로 할당될 때마다 호출합니다. 오로지 저장프로퍼티, 상속받은 연산프로퍼티에 사용 할 수 있습니다.

 

프로퍼티 감시자는 프로퍼티의 값이 변경되기 직전에 willSet 메서드와 프로퍼티의 값이 변경된 직후에 호출되는 didSet 메서드가 있습니다.

 

willset 메서드의 전달인자는 프로퍼티가 변경될 값이고, didSet 메서드의 전달인자는 변경되기 전 값입니다.

class Account {
    var credit: Int = 0 {
        willSet {
            print("credit 잔액이 \(credit)원에서 \(newValue)원으로 변경될 예정입니다.")
        }
        didSet {
            print("credit 잔액이 \(oldValue)원에서 \(credit)원으로 변경되었습니다.")
        }
    }
    
    var dollorValue: Double { // 연산 프로퍼티
        get {
            return Double(credit) //1000.0
        }
        set {
            credit = Int(newValue * 1000)
            print("잔액을 \(newValue)달러로 변경 중입니다. ")
        }
        
    }
}

class ForeignAccount: Account {
    // 상속받은 dollorValue
    override var dollorValue: Double {
        willSet {
            print("dollorValue 잔액이 \(dollorValue)원에서 \(newValue)원으로 변경될 예정입니다.")
        }
        didSet {
            print("dollorValue 잔액이 \(oldValue)원에서 \(dollorValue)원으로 변경되었습니다.")
        }
    }
}

let myAccount: ForeignAccount = ForeignAccount()

// 잔액이 0원에서 1000원으로 변경될 예정입니다.
myAccount.credit = 1000
// 잔액이 0원에서 1000원으로 변경되었습니다.


//dollorValue 잔액이 1000.0원에서 2.0원으로 변경될 예정입니다.
//credit 잔액이 1000원에서 2000원으로 변경될 예정입니다.
//credit 잔액이 1000원에서 2000원으로 변경되었습니다.
myAccount.dollorValue = 2 // 잔액을 2.0달러로 변경되었습니다. dollorValue.set
// dollorValue 잔액이 1000.0원에서 2000.0원으로 변경되었습니다.

 

전연변수에도 감시자와 연산변수를 설정 할 수 있습니다.

var wonInPocket: Int = 2000 {
    willSet {
        print("주머니의 돈이 \(wonInPocket)원에서 \(newValue)원으로 변경될 예정입니다.")
    }
    didSet {
        print("주머니의 돈이 \(oldValue)원에서 \(wonInPocket)원으로 변경되었습니다.")
    }
}

var dollarInPocket: Double {
    get {
        return Double(wonInPocket)  // 1000.0
    }
    set {
        wonInPocket = Int(newValue * 1000)
        print("주머니 달러를 \(newValue)달라로 변경 중입니다.")
    }
}

//주머니의 돈이 2000원에서 3500원으로 변경될 예정입니다.
//주머니의 돈이 2000원에서 3500원으로 변경되었습니다.

dollarInPocket = 3.5 //주머니 달러를 3.5달라로 변경 중입니다.

 

12.1.6 타입 프로퍼티 

각각의 인스턴스가 아닌 타입 자체에 속하는 프로퍼티를 타입 프로퍼티라고 합니다.

static으로 선언된 프로퍼티.

인스턴스의 생성 여부와 상관없이 타입프로퍼티는 값은 하나며, 그 타입의 인스턴스가 공통으로 사용하는 값입니다.

 

C언어의 static constant와 유사합니다.

class AClass {
    
    //저장 타입 프로퍼티
    static var typeProperty: Int = 0
    
    // 저장 인스턴스 프로퍼티
    var instanceProperty: Int = 0 {
        didSet {
            Self.typeProperty = instanceProperty + 100
        }
    }
    
    // 연산 타입 프로퍼티
    static var typeComputedProperty: Int {
        get {
            return typeProperty
        }
        set {
            typeProperty = newValue
        }
    }
}

AClass.typeProperty = 123

let classInstance: AClass = AClass()
classInstance.instanceProperty = 100

print(AClass.typeProperty) // 200
print(AClass.typeComputedProperty) // 200

 

12.1.7 키 경로

어떤 프로퍼티의 위치만 참조하도록 할 수 있습니다. 이 위치를 통해 값을 넣거나 가져올 수 있습니다.

키경로 타입은 WritableKeyPath<Root, Value> 와 ReferenceWritableKeyPath<Root, Value> 타입입니다.

 

class Person {
    let name: String
    init(name: String) {
        self.name = name
    }
}

struct Stuff {
    var name: String
    var owner: Person
}

print(type(of: \Person.name))   // ReferenceWritableKeyPath<Person, String>
print(type(of: \Stuff.name))    // WritableKeyPath<Stuff, String>

let kemi = Person(name: "kemi")
let hana = Person(name: "hana")
let macbook = Stuff(name: "MacBook Pro", owner: kemi)
var iMac = Stuff(name: "iMac", owner: kemi)
let iPhone = Stuff(name: "iPhone", owner: hana)

let stuffNameKeyPath = \Stuff.name
let ownerKeyPath = \Stuff.owner

// \Stuff.owner.name과 같은 표현이 됩니다.
let ownerNameKeyPath = ownerKeyPath.appending(path: \.name)

// 키 경로와 서브스크립트를 이용해 프로퍼티에 접근하여 값을 가져옵니다.
print(macbook[keyPath: stuffNameKeyPath])
print(iMac[keyPath: stuffNameKeyPath])
print(iPhone[keyPath: stuffNameKeyPath])

print(macbook[keyPath: ownerNameKeyPath])
print(iMac[keyPath: ownerNameKeyPath])
print(iPhone[keyPath: ownerNameKeyPath])

iMac[keyPath: stuffNameKeyPath] = "iMac Pro"
iMac[keyPath: ownerKeyPath] = hana

 

키 경로를 활용하면 프로토콜과 마찬가지로 타입 간의 의존성을 낮추는 데 많은 도움을 줍니다.

애플의 프레임워크는 키-값 코딩 등 많은 곳에서 키 경로를 활용함으로 잘 알아둬야합니다.

 

12.2 메서드

 

func와 기본적으로 문법은 같으나 struct, class, 열거형 등 특정 타입에 관련된 함수를 뜻합니다.

class LevelClass {
    // 현재 레벨을 저장하는 저장 프로퍼티
    var level: Int = 0 {
        didSet {
            print("Level \(level)")
        }
    }
    
    // 레벨이 올랐을 때 호출할 메서드
    func levelUp() {
        print("Level Up!")
        level += 1
    }
    
    // 레벨이 감소했을 때 호출할 메서드
    func levelDown() {
        print("Level Down")
        level -= 1
        if level < 0 {
            reset()
        }
    }
    
    // 특정 레벨로 이동할 때 호출할 메서드
    func jumpLevel(to: Int) {
        print("Jump to \(to)")
        level = to
    }
    
    // 레벨을 초기화 할 때 호출할 메서드
    func reset() {
        print("Reset!")
        level = 0
    }
}

var levelClassInstance: LevelClass = LevelClass()
levelClassInstance.levelUp()
levelClassInstance.levelDown()
levelClassInstance.levelDown()

levelClassInstance.jumpLevel(to: 3)

 

 

struct에서는 자신의 프로퍼티 값을 수정 할 때 메서드 앞에 mutating 키워드를 붙여서 해당 메서드가 인스턴스 내부의 값을 변경한다는 것을 명시해야합니다.

struct LevelStruct {
    // 현재 레벨을 저장하는 저장 프로퍼티
    var level: Int = 0 {
        didSet {
            print("Level \(level)")
        }
    }
    
    // 레벨이 올랐을 때 호출할 메서드
    mutating func levelUp() {
        print("Level Up!")
        level += 1
    }
    
    // 레벨이 감소했을 때 호출할 메서드
    mutating func levelDown() {
        print("Level Down")
        level -= 1
        if level < 0 {
            reset()
        }
    }
    
    // 특정 레벨로 이동할 때 호출할 메서드
    mutating func jumpLevel(to: Int) {
        print("Jump to \(to)")
        level = to
    }
    
    // 레벨을 초기화 할 때 호출할 메서드
    mutating func reset() {
        print("Reset!")
        level = 0
    }
}

var levelStructInstance: LevelStruct = LevelStruct()
levelStructInstance.levelUp()
levelStructInstance.levelDown()
levelStructInstance.levelDown()

levelStructInstance.jumpLevel(to: 3)

 

타입 메서드

타입 프로퍼티가 있는 것 처럼 메서드에도 타입 메서드가 존재합니다.

메서드 앞에 class를 붙여주거나 static을 붙여주면 됩니다.

둘의 차이점은 static은 overriding이 불가능하고 class는 오버라이딩(재정의)가 가능하다는 점입니다.

코드로 보면 다음과 같습니다.

class AClass {
    static func staticTypeMethod() {
        print("AClass staticTypeMethod")
    }
    class func classTypeMethod() {
        print("AClass classtypeMethod")
    }
}

class BClass: AClass {
    /*
        // 오류 발생 재정의 불가!!
        override static func staticTypeMethod() {
        
        }
     */
    override class func classTypeMethod() {
        print("BClass classTypeMethod")
    }
}

AClass.staticTypeMethod()
AClass.classTypeMethod()
BClass.classTypeMethod()

 

타입 프로퍼티를 제어하기위해 타입 메서드를 같이 사용하곤 합니다.

타입 프로퍼티는 언제나 유일한 값을 가질 수 있습니다. 그렇기 때문에 class를 메모리에 올려 인스턴스로 생성하지 않아도 접근 할 수 있게됩니다.

// 시스템 음량은 한 기기에서 유일한 값이어야합니다.
struct SystemVolume {
    // 타입 프로퍼티를 사용하면 언제나 유일한 값이 됩니다.
    static var volume: Int = 5
    
    // 타입 프로퍼티를 제어하기 위해 타입 메서드를 사용합니다.
    static func mute() {
        self.volume = 0
    }
}

// 내비게이션 역할은 여러 인스턴스가 수행 할 수 있습니다.
class Navigation {
        // 내비게이션 인스턴스마다 음량을 따로 설정할 수 있습니다.
    var volum: Int = 5
    
    // 길 안내 음성 재생
    func guideWay() {
        // 내비게이션 외 다른 재생원 음소거
        SystemVolume.mute()
    }
    
    // 길 안내 음성 종료
    func finishGuideWay() {
        // 기존 재생원 음량 복구
        SystemVolume.volume = self.volum
    }
}

SystemVolume.volume = 10

let myNavi: Navigation = Navigation()

myNavi.guideWay()
print(SystemVolume.volume)

myNavi.finishGuideWay()
print(SystemVolume.volume)

 

반응형