개발하는 뚝딱이

[swift] 프로퍼티(property) 본문

swift

[swift] 프로퍼티(property)

개발자뚝딱이 2020. 4. 15. 14:14

책 <스위프트 프로그래밍 3판 ;야곰 지음> 을 정리한 글입니다.

 


프로퍼티

1. 저장 프로퍼티

2. 지연 저장 프로퍼티

3. 연산 프로퍼티

4. 프로퍼티 감시자

5. 타입 프로퍼티

6. 키 경로


프로퍼티

- 클래스, 구조체, 열거형 등에 관련된 값을 의미

- 저장 프로퍼티 / 연산 프로퍼티 / 타입 프로퍼티

- 프로퍼티 감시자(Property Observers)를 이용하여 값이 변하는 것을 감시

 

 

1-0. 저장 프로퍼티 (Stored Property)

- var / let  사용가능

- let 키워드를 사용한 상수 프로퍼티는 값을 재할당할 수 없음

- struct는 저장 프로퍼티를 모두 포함하는 이니셜라이저를 자동으로 생성

- class는 저장 프로퍼티가 옵셔널이 아니면 기본값을 저장하거나, 사용자 정의 이니셜라이저를 통해 반드시 초기화해야 함

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

let point1: CoordinatePoint = CoordinatePoint(x: 10, y: 5)
class Position {
    let name: String // 저장 프로퍼티
    var point: CoordinatePoint // 저장 프로퍼티
    
    init(name: String, currentPoint: CoordinatePoint) { // 이니셜라이저 필수
        self.name = name
        self.point = currentPoint
    }
}

let point2: Position = Position(name: "Jin", currentPoint: point1)

 

 

1-1. 저장 프로퍼티의 초깃값 지정

- 프로퍼티의 초깃값을 할당하면, 이니셜라이저 전달인자로 초깃값을 넘길 필요가 없음

struct CoordinatePoint {
    var x: Int = 2 // 저장 프로퍼티
    var y: Int = 3// 저장 프로퍼티
}

let point1: CoordinatePoint = CoordinatePoint(x: 10, y: 5)
let point2: CoordinatePoint = CoordinatePoint()
class Position {
    var name: String = "James" // let으로 선언하면 값 변경이 안되므로 var 키워드 사용
    var point: CoordinatePoint
    
    init(currentPoint: CoordinatePoint) {
        self.point = currentPoint
    }
}

let point2: Position = Position(currentPoint: point1)
point2.name = "Jin"

- 옵셔널로 타입을 선언하면 이니셜라이징하지 않아도 됨

struct CoordinatePoint {
    var x: Int
    var y: Int
}

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

let jinPosition: Position = Position(name: "Jin")
jinPosition.point = CoordinatePoint(x: 20, y: 5)

 

2-0. 지연 저장 프로퍼티 (lazy stored property)

- 호출이 있어야 값을 초기화

- 상수는 인스턴스가 완전히 생성되기 전에 초기화해야 함 → let 이 아닌 var 키워드 사용 → 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 position1: Position = Position(name: "Jin")
position1.point = CoordinatePoint(x: 3, y: 10) // point 프로퍼티로 처음 접근하므로 CoordinatePoint가 생성됨

 

3-0. 연산 프로퍼티 (Computed Property)

- 실제 값을 저장하는 프로퍼티가 아니라, 특정 상태에 따른 값을 연산하는 프로퍼티

- setter, getter의 역할 가능

- 메소드 대신 연산 프로퍼티를 쓰는 이유 ; 인스턴스 외부에서 메서드를 통해 내부 값에 접근하려면 getter, setter 두개를 구현해야 함. 또한 코드의 가독성을 고려했을 때 연산 프로퍼티가 훨씬 직관적

- 그러나 연산 프로퍼티 구현 시, getter만 O / getter, setter 둘 다 O / setter만 X

- 관용적으로 setter 연산프로퍼티에서 newValue 사용

struct CoordinatePoint {
    var x: Int = 0
    var y: Int = 0
    
    // getter 메서드
    func oppositePoint() -> Self { // Self 대신 CoordinatePoint 사용 가능
        return CoordinatePoint(x: -x, y: -y)
    }
    
    // setter 메서드
    mutating func setOppositePoint(_ opposite: CoordinatePoint) {
        x = -opposite.x
        y = -opposite.y
    }
}

var position1: CoordinatePoint = CoordinatePoint(x: 10, y: 20)
print(position1) // 10, 20
print(position1.oppositePoint()) // -10, -20

position1.setOppositePoint(CoordinatePoint(x: 10, y: 15))
print(position1) // -10, -15
struct CoordinatePoint {
    var x: Int = 0
    var y: Int = 0
    
    var oppositePoint: CoordinatePoint {
        // getter
        get {
            return CoordinatePoint(x: -x, y: -y)
        }
        
        // setter
        set(newValue) {
            x = -newValue.x
            y = -newValue.y
        }
    }
}

var position1: CoordinatePoint = CoordinatePoint(x: 10, y: 20)
print(position1) // 10, 20
print(position1.oppositePoint) // -10, -20

position1.oppositePoint = CoordinatePoint(x: 20, y: 15)
print(position1) // -20, -15

 

4. 프로퍼티 감시자

- 프로퍼티의 값이 새로 할당될 때마다 호출

- 일반 저장 프로퍼티, 재정의해 상속받은 저장 프로퍼티, 상속받은 연산 프로퍼티 사용 가능 (지연 저장 프로퍼티 사용 불가)

- willSet 메서드 : 값이 변경되기 직전에 호출. 매개변수(newValue)는 변경될 값으로 자동 저장

- didSet 메서드 : 값이 변경된 직후에 호출. 매개변수(oldValue)는 변경되기 전 값으로 자동 저장

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

class ForeignAccount: Account { // Account를 상속받음
    override var dollarValue: Double {
        willSet {
            // [1]
            print("잔액이 \(dollarValue)달러에서 \(newValue)달러로 변경될 예정입니다")
        }
        
        didSet {
            // [5]
            print("잔액이 \(oldValue)달러에서 \(dollarValue)달러로 변경되었습니다")
        }
    }
}

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

// [1] 잔액이 1.0달러에서 2.0달러로 변경될 예정입니다
// [2] 잔액이 1000원에서 2000원으로 변경될 예정입니다.
// [3] 잔액이 1000원에서 2000원으로 변경되었습니다
myAccount.dollarValue = 2 // [4] 잔액이 2.0달러로 변경 중입니다
// [5] 잔액이 1.0달러에서 2.0달러로 변경되었습니다

 

5. 타입 프로퍼티

- 타입 자체에 속하는 프로퍼티

- 해당 타입의 모든 인스턴스가 공통으로 사용 가능

- 모든 인스턴스에서 공용으로 접근하고 값을 변경할 수 있음 (var로 선언된 경우)

- 저장 타입 프로퍼티인 경우 초깃값 반드시 설정해야 함

- 인스턴스 생성 없이도 사용 가능

class AClass {
    
    // 상수 역할
    static let typeConstantProperty: Int = 100
    
    // 저장 타입 프로퍼티
    static var typeProperty: Int = 0
    
    // 저장 인스턴스 프로퍼티
    var instanceProperty: Int = 0 {
        didSet {
            // AClass.typeProperty와 Self.typeProperty는 같은 말이다
            Self.typeProperty = instanceProperty + 100
        }
    }
    
    // 연산 타입 프로퍼티
    static var typeComputedProperty: Int {
        get {
            return typeProperty
        }
        
        set {
            typeProperty = newValue
        }
    }
}

AClass.typeProperty = 123

let classInstance: AClass = AClass()
print(AClass.typeProperty) // 123

classInstance.instanceProperty = 100
print(AClass.typeProperty) // 200
print(AClass.typeComputedProperty) // 200

 

6. 키 경로

- 값을 바로 꺼내오는 것이 아니라 어떤 프로퍼티의 위치만 참조

- 어떤 프로퍼티 값을 가져야 할 지 미리 지정하여 사용 가능

- 키 경로 타입은 AnyKeyPath 라는 클래스로부터 파생됨

- WritableKeyPath <Root, Value>은 값 타입의 키 경로타입 / 읽고 쓸 수 있는 경우 적용

- ReferenceWritableKeyPath <Root, Value>은 참조 타입의 키 경로 타입 / 읽고 쓸 수 있는 경우 적용

- KeyPath은 읽을 수만 있는 경우 적용

class Person {
    var 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 keyPath = \Stuff.owner
let namePath = keyPath.appending(path: \.name)
class Person {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

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

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

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

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

// 키 경로와 서브스크립트를 이용해 프로퍼티에 접근, 값 가져옴
print(macbook[keyPath: stuffNameKeyPath]) // MacBook Pro
print(iMac[keyPath: stuffNameKeyPath]) // iMac
print(iPhone[keyPath: stuffNameKeyPath]) // iPhone

print(macbook[keyPath: ownerNameKeyPath]) // ttukttak
print(iMac[keyPath: ownerNameKeyPath]) // ttukttak
print(iPhone[keyPath: ownerNameKeyPath]) // jin

// 키 경로와 서브스크립트를 이용해 프로퍼티에 접근, 값 변경
iMac[keyPath: stuffNameKeyPath] = "iMac Pro"
iMac[keyPath: ownerKeyPath] = jin

 

 

 

 

'swift' 카테고리의 다른 글

[swift] 옵셔널  (0) 2020.04.28
[swift] 메소드 (method)  (0) 2020.04.27
[swift] 구조체(struct)와 클래스(class) 비교  (0) 2020.04.08
[swift] 연산자  (0) 2020.04.06
[swift] 데이터 타입  (0) 2020.04.02