프로퍼티(porperty)

Stored property(저장 프로퍼티)

  • 타입(클래스, 구조체)의 인스턴스에서 을 저장하는 프로퍼티.
  • 저장 프로퍼티가 옵셔널이 아니면 반드시 클래스, 구조체 내에서 초기값을 설정하거나 생성자를 통해 초기화해야 한다. 옵셔널이면 반드시 초기화할 필요가 없다.
  • 클래스 또는 구조체의 인스턴스는 사용되기 전에 반드시 생성자를 통해 초기화를 해야 한다.
  • memberwise 생성자: 저장 프로퍼티 명들을 매개변수로 하는 생성자. 구조체는 memberwise 생성자를 기본적으로 제공하며 재정의할 수 있다.

*Lazy stored property(지연 저장 프로퍼티)

저장 프로퍼티가 객체 안에서 인스턴스가 생성될 때 초기화되는 것이 아니라 사용되는 시점에 초기화된다.

메모리를 효율적으로 사용할 수 있다. 메모리를 많이 사용하는 시스템일 때 쓰면 좋다. 좀 좋은 컴파일러는 대부분 이렇게 한다.

struct CoordinatePoint {
    var x: Int = 0
    var y: Int = 0
}
class Position {
    lazy var point: CoordinatePoint = CordinatePoint()
    let name: String

    init(name: String) {
        self.name = name
    }
}

Computed property(연산 프로퍼티)

  • 저장 프로퍼티의 get 메서드와 set 메서드를 수행한다.
  • get: 저장 프로퍼티 값을 읽어 온다.
  • set: 저장 프로퍼티에 값을 저장한다.
  • 변수(var)에만 선언할 수 있다. 상수(let)에는 선언할 수 없다.
  • get 메서드만 구현해도 되지만 set 메서드만 구현할 수는 없다.
  • get 메서드는 매개변수가 필요없고, 반환만 잘하면 된다. 내부 코드가 한 줄이다. 반환값의 자료형이 연산 프로퍼티의 자료형과 동일하면 return 키워드를 생략할 수 있다.
  • set의 자료형이 연산 프로퍼티의 자료형과 동일하면 자료형을 작성하지 않는다. 매개변수가 생략되면 newValue라는 매개변수가 자동으로 지정된다.
  • 일반적으로 get/set 메서드의 자료형은 연산 프로퍼티의 자료형과 동일해서 생략해도 되는 일이 많다.
  • get이랑 set을 나누는 이유는 보안 때문이다.

struct CoordinatePoint {
    var x: Int
    var y: Int

    func getOppositePoint() -> Self {
        return CoordinatePoint(x: -x, y: -y)
    }

    mutating func setOppositePoint(_ opposite: CoordinatePoint) {
        self.x = -opposite.x
        self.y = -opposite.y
    }
}

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

print(aPosition)    //CoordinatePoint(x: 10, y: 20)
print(aPosition.getOppositePoint())    //CoordinatePoint(x: -10, y: -20)
aPosition.setOppositePoint(CoordinatePoint(x: 15, y: 10))
print(aPosition)    //CoordinatePoint(x: -15, y: -10)
//위 구조체를 연산 프로퍼티로 구현
sturct CoordinatePoint {
    var x: Int
    var y: Int

    var oppositePoint: CoordinatePoint {
        get {
            return CoordinatePoint(x: -x, y: -y)
        }
        set(opposite) {
            x = -opposite.x    //x = -newValue.x
            y = -opposite.y    //y = -newValue.y
        }
    }
}
  • mutating: 구조체와 열거형에서 메서드가 프로퍼티를 변경한다는 것을 명시하기 위해 사용한다. 네? 구조체에 함수를 잘 안 넣는다고요?

뭔소린지 모르겠음.

print 쓰면 get 메서드가 호출되고 값을 저장하면 값이 저장되는 게 아니라 set 메서드가 호출된다. 연산 프로퍼티 이름은 껍데기일 뿐...😢

Type property(타입 프로퍼티)

  • 타입의 프로퍼티. 인스턴스 없이 타입을 통해 접근할 수 있다. C의 static 변수와 유사하다.
  • 타입만 선언해도 그 자체로 메모리가 존재한다.
  • static 키워드를 붙여서 저장 프로퍼티와 연산 프로퍼티를 타입 프로퍼티로 만들 수 있다. 이때 저장 타입 프로퍼티는 반드시 초기값이 설정되어 있어야 하며 기본적으로 지연 연산이 되므로 lazy 키워드를 붙일 필요가 없다.

Property observer(프로퍼티 감시자)

  • willSet 메서드: 저장 프로퍼티가 변경되기 직전, set 메서드가 실행되기 전에 호출된다. 매개변수가 없으면 newValue를 사용할 수 있다.
  • didSet 메서드: 저장 프로퍼티가 변경된 직후, set 메서드가 실행된 후에 호출된다. 매개변수가 없으면 oldValue를 사용할 수 있다.

값이 어떻게 변경되는지 관찰하기 편하다.

연산 프로퍼티는 클래스를 상속받았을 때만 연산 프로퍼티를 재정의하여 프로퍼티 감시자를 구현할 수 있다.

재정의(override)하면서 willSet과 didSet도 재정의할 수 있다.

import Foundation


class Account {

    var credit: Int = 0 {

        willSet {

            print("1 저장 willSet - 잔액이 \(credit)원에서 \(newValue)원으로 변경될 예정입니다.")

        }

        didSet {

            print("2 저장 didSet - 잔액이 \(oldValue)원에서 \(credit)원으로 변경되었습니다.")

        }

    }

    

    var dollarValue: Double {

        get {

            return Double(credit / 1000)

        }

        set {

            credit = Int (newValue * 1000)

            print("3 연산 set - 잔액을 \(newValue)달러로 변경 중입니다.")

        }

    }

}


class ForeignAccount: Account {

    override var dollarValue: Double {

        willSet {

            print("4 감시자 willSet - 잔액이 \(self.dollarValue)달러에서 \(newValue)달러로 변경될 예정입니다.")

        }

        didSet {

            print("5 감시자 didSet - 잔액이 \(oldValue)달러에서 \(self.dollarValue)달러로 변경되었습니다.")

        }

    }

}


let myAccount: ForeignAccount = ForeignAccount()

myAccount.credit = 1000

myAccount.dollarValue = 2


/* 출력 결과

1 저장 willSet - 잔액이 0원에서 1000원으로 변경될 예정입니다.

2 저장 didSet - 잔액이 0원에서 1000원으로 변경되었습니다.

4 감시자 willSet - 잔액이 1.0달러에서 2.0달러로 변경될 예정입니다.

1 저장 willSet - 잔액이 1000원에서 2000원으로 변경될 예정입니다.

2 저장 didSet - 잔액이 1000원에서 2000원으로 변경되었습니다.

3 연산 set - 잔액을 2.0달러로 변경 중입니다.

5 감시자 didSet - 잔액이 1.0달러에서 2.0달러로 변경되었습니다.

 */

처음에 호출되는 건 ForeignAccount의 willSet -> Account의 willSet 2000으로 변경-> Account의 didSet -> dollarValue의 set -> ForeignAccount의 didSet


키 경로(key path)

\타입.프로퍼티,프로퍼티.프로퍼티

타입(클래스, 구조체) 내의 프로퍼티의 주소 경로

  • WritableKeyPath<Root, Value>: 구조체(값 타입) 내의 읽고 쓸 수 있는 프로퍼티의 주소 경로의 자료형
  • ReferenceWritableKeyPath<Root, Value>: 클래스(참조 타입) 내의 읽고 쓸 수 있는 프로퍼티의 주소 경로의 자료형
  • 서브스크립트: 인스턴스 뒤에 대괄호[]를 사용하여 인스턴스 내의 프로퍼티에 접근 가능하게 한다.


메서드(method)

인스턴스 메서드(instance method)

인스턴스가 생성되었을 때 인스턴스를 통해 호출할 수 있는 호출 가능하다. 인스턴스를 생성해야 사용할 수 있다.

메서드가 프로퍼티를 변경한다는 것을 명시하기 위해 mutating을 써야할지 생각을 해야 한다. 구조체 또는 열거형에서만 명시한다는 점을 기억하자~

callAsFunction: 인스턴스를 함수처럼 호출하도록 하는 메서드. 다중정의(overloading)가 가능하다. - 메서드 명은 동일하지만 매개변수와 반환 타입을 다르게 해서 여러 개의 메서드를 생성할 수 있다.

타입 메서드(type method)

인스턴스 없이 타입을 통해 호출 가능한 메서드.

프로퍼티는 뇌고 메소드는 몸이라고 누가 그랬대.

설명이 막 길게 써있는데 우리는 프로그래머니까 코드를 보면 알겠지.(ㅋㅋㅋ)

클래스 타입의 메서드는 static 키워드를 사용하면 상속 후 재정의 불가, class 키워드를 사용하면 가능하다.

구조체는 static 키워드를 사용하여 메서드를 타입 메서드로 정의한다.

import Foundation


struct SystemVolume {

    static var volume: Int = 5

    static func mute() {

        Self.volume = 0

    }

}


class Navigation {

    var volume: Int = 5

    

    func guideWay() {

        SystemVolume.mute()

    }

    func finishGuideWay() {

        SystemVolume.volume = self.volume

    }

}


SystemVolume.volume = 10

SystemVolume.mute()


var myNavi: Navigation = Navigation()


myNavi.guideWay()

print(SystemVolume.volume)


myNavi.finishGuideWay()

print(SystemVolume.volume)


/* 결과

 0

 5

 */

SystemVolume은 실행하자마자 메모리에 자리가 생겼음.

종속되어 있는 프로퍼티인지(?)

self 프로퍼티와 Self 프로퍼티

  • Self: 타입(클래스, 구조체) 자기 자신을 나타내는 프로퍼티
  • self: 인스턴스 자기 자신을 나타내는 프로퍼티. 클래스의 인스턴스는 참조 타입이기 때문에 변경할 수 없다. 구조체와 열거형의 인스턴스는 값 타입이라서 self 프로퍼티를 변경할 수 있다.