스위프트의 프로퍼티

개요


property 직역하면 속성.

클래스, 구조체, 열거형등의 속성, 관련된 값이라고 할 수 있다.

프로퍼티의 개념을 알아보고 어떻게 사용하는지 알아보겠다.




프로퍼티


프로퍼티는 저장 프로퍼티(Stored Properties), 연산 프로퍼티(Computed Properties), 타입 프로퍼티(Type Properties)들이 있다.

프로퍼티의 값이 변하는 것을 감시하는 프로퍼티 감시자(Property Observers)도 있다.

각자의 명칭에서 역할을 유추해볼 수 있다. 세부적으로 각자의 역할에 대해서 알아보자.




저장 프로퍼티


저장 프로퍼티는 말 그대로 인스턴스의 변수 또는 상수를 의미한다. 저장 프로퍼티는 클래스와 구조체에서만 제공된다.

저장 프로퍼티를 정의할 때 기본값과 초기값을 지정할 수 있다. 구조체는 저장 프로퍼티가 옵셔널이 아니더라도 생성자를 자동으로 생성한다.

그런데 클래스는 기본값을 지정하거나 옵셔널로 선언하거나 생성자를 만들어서 초기화 해주어야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct ExStored {
  var a: Int
  var b: Int
}

let exInit = ExStored(a: 1,b: 2) // ok

class ExStored2 {
  var a: Int
  var b: Int
  
  init(one: Int, two: Int) {
    self.a = one
    self.b = two
  }
}

let exInit2 = ExStored2(one: 1, two: 2)




지연 저장 프로퍼티


Lazy Stored Properties는 호출이 있어야 값을 초기화하며, lazy 키워드를 사용한다.

let은 인스턴스가 완전히 생성되기 전에 초기화해야 하므로 var로 선언하여 필요할 때 값을 할당한다.

클래스나 구조체가 복잡해지면 사용한다.

1
2
3
4
5
6
struct ExStored {
  var a: Int = 0
  var b: Int = 0
}

lazy var ex: ExStored = ExStored()




연산 프로퍼티


computed properties로 값을 저장하는 프로퍼티가 아니라 특정 상태에 따른 값을 연산하는 프로퍼티이다.

값을 연산에서 돌려주는 getter와 값을 간접적으로 설정하는 setter가 있다.

setter을 적는다면 getter을 생략할 수 없다.

set키워드 옆에 set구문에서 사용할 매개변수를 적어줘야하고 안적으면 newValue가 기본 매개변수이다.

코드의 간결화와 가독성 때문에 사용하는 일이 많다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct ManAge {
  var age: Int
  var manAge: Int {
    get {
      return age - 1
    }
    set {
      age = newValue - 1
    }
  }
}

var ex: ManAge = ManAge(age: 10)
ex.manAge = 5 // age = 4




프로퍼티 감시자


Property Observers를 이용하여 프로퍼티의 값이 변경됨에 따라 적절한 작업을 취할 수 있다.

프로퍼티 감시자에는 willSet과 didSet 메서드가 있다.

둘다 매개변수가 하나씩 있는데 willSet 매개변수는 프로퍼티가 변경될 값이고, didSet 메서드에 전달되는 전달인자는 변경되기 전의 값이다.

따라서 자동으로 이름이 newValue, oldValue라는 것이 설정된다.

주로 저장 프로퍼티에 사용되는데 연산 프로퍼티의 기능을 사용하는 것이라고 보면된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Age {
  var Age: Int = 0 {
    willSet {
      print("나이는 \(newValue)이다")
    }
    didSet {
      print("나이는 \(oldValue)였다")
    }
  }
}

var ex: Age = Age()
ex.Age = 20




타입 프로퍼티


위의 프로퍼티들은 인스턴스가 생성되었을 때 사용할 수 있는 인스턴스 프로퍼티이다.

그래서 각각의 인스턴스마다 다른 값을 가지고 있을 수 있다.

타입 프로퍼티는 타입 자체에 속하는 프로퍼티이다. 인스턴스 생성 여부와는 상관없이 타입 프로퍼티의 값은 하나이다.

일반 프로퍼티에 static으로 선언하면 되는데 C언어의 static과 같은 것이라고 생각하면된다.

타입 프로퍼티 또한 저장 타입 프로퍼티, 연산 타입 프로퍼티로 선언할 수 있는데, 저장은 변수와 상수로 선언이 가능하고 연산은 변수만 가능하다.




키 경로


프로퍼티의 값을 꺼내오는 것이 아니라 프로퍼티의 위치만 참조하도록 하는 것이 Key Path이다.

\타입이름.프로퍼티.프로퍼티와 같은 형식으로 사용한다.

키 경로를 잘 활용하면 타입 간의 의존성을 낮추는 데 많은 도움을 준다.

SwiftUI에서 많이 사용하는데 특히 .self를 많이 사용한다.

.self는 자신을 나타내는 키 경로로 인스턴스 그 자체를 표현한다.




메서드


메서드는 특정 타입에 관련된 함수를 말한다.




인스턴스 메서드


인스턴스 메서드는 특정 타입의 인스턴스에 속한 함수를 말한다. 그러므로 인스턴스가 존재할 때만 사용할 수 있다.

예시를 보면 그냥 일반적으로 우리가 사용하는 메서드이다.

구조체나 열거형은 값 타입이므로 메서드 앞에 mutating 키워드를 붙여서 메서드가 인스턴스 내부의 값을 바꾼다는 것을 명시해야한다.




self 프로퍼티


모든 인스턴스는 암시적으로 생성된 self 프로퍼티를 갖는다.

자바나 cpp의 this키워드라고 생각하면된다.

사용법도 같다. 다른점은 클래스는 자기자신으로 다시 할당 불가능, 구조체는 가능하다.

1
2
3
4
5
6
7
8
9
//ERROR
class LevelClass {
    var level: Int = 0

    func reset() {
      self = LevelClass()
    }
}
//구조체로는 가능




타입 메서드


타입 프로퍼티와 똑같다. static으로 선언하여 전역으로 사용할 수 있게해준 메서드이다.




프로퍼티래퍼


공식문서를 직역하면 프로퍼티 래퍼는 프로퍼티가 어떻게 저장되는지 관리하는 코드와 정의하는 코드를 분리한다.

클래스, 구조체, 열거형 선언에 속성을 적용하면 프로퍼티의 접근 패턴을 정의하는 타입이 된다.

propertyWrapper 속성을 적용한 타입은 wrappedValue라는 이름의 프로퍼티를 구현해야한다.

@frozen @propertyWrapper struct State<Value> @state의 원형이다. 여기에도 보면 propertyWrapper속성이 적용되어있다.

프로퍼티앞에 @State를 쓰면 State의 패턴을 사용한 프로퍼티가 된다.

코드의 재사용성을 위해서 사용한다. 약간 프로토콜이랑 비슷하게, 프로퍼티계의 프로토콜 느낌??

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@propertyWrapper
struct TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()

rectangle.height = 24
print(rectangle.height)// "12"