Notice
Recent Posts
Recent Comments
Link
관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] @propertyWrapper의 projectedValue 개념 ($ 접두사, 달러 접두사) 본문

iOS 응용 (swift)

[iOS - swift] @propertyWrapper의 projectedValue 개념 ($ 접두사, 달러 접두사)

jake-kim 2022. 3. 5. 22:33

PropertyWrapper

  • 이미 정의된 property가 있을 때, 이 property를 감싸서(wrapping) computed-property로 만든 새로운 wrapper 프로퍼티를 의미
  • propertyWrapper 프로퍼티 안에는 wrappedValue라는 property가 존재하며, 이 property를 stored-property나 computed-property로 정의하여 사용 가능
    • stored-property로 사용 (didSet)
    • computed-property로 사용 (get, set)

ex) stored-property로 사용 (didSet)

내부에서 복잡하지 않고 단순한 처리만 사용하는 경우

// ex1) stored-property로 사용하여 didSet 이용
@propertyWrapper
struct TrimmedString {
  var wrappedValue: String {
    didSet { self.wrappedValue = self.wrappedValue.trimmingCharacters(in: .whitespacesAndNewlines) }
  }
  init(wrappedValue: String) {
    // 주의: observer property의 didSet 실행은 초기화가 완료된 이후에만 트리거되므로 주의
    self.wrappedValue = wrappedValue.trimmingCharacters(in: .whitespacesAndNewlines)
  }
}

// 사용
class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()

    @TrimmedString var string = "   trimmined string? \n"
    print(string) // "trimmined string?"
  }
}

ex) computed-property 사용 (get, set)

내부에서 UserDefaults와 같이, 프로퍼티에서 복잡한 작업을 시도하는 경우 (get과 set 다르게 정의해야하는 경우)

// ex2) computed-property로 사용하여 get, set 이용
@propertyWrapper
struct AccessToken {
  private enum Key {
    static let accessToken = "accessToken"
  }
  private let storage = UserDefaults.standard
  
  var wrappedValue: String {
    get { self.storage.string(forKey: Key.accessToken) ?? "defaultValue" }
    set { self.storage.set(newValue, forKey: Key.accessToken) }
  }
}

// 사용
class ViewController: UIViewController {
  @AccessToken var accessToken // <- 주의: computedProperty로 구현된 PropertyWrapper는 초기화 x
  override func viewDidLoad() {
    super.viewDidLoad()
    print(self.accessToken) // "defaultValue"
  }
}
  • 응용
  • propertyWrapper를 이용하여, struct 형태도 UserDefaults 쉽게 사용하는 방법 - 포스팅 글 참고

* wrappedValue 초기화 시 주의할 점

  • propertyWrapper안에서 init에 wrappedValue 초기화가 있으면, 사용하는 쪽에서 일반 property 초기화 하듯이 접근 가능
@propertyWrapper
struct SomeProperty {
  var wrappedValue = "someValue"
  
  init(wrappedValue: String) {
     self.wrappedValue = wrappedValue
  }
}

// 사용
@SomeProperty myProperty = "" // ok
  • propertyWrapper안에서 init에 wrappedValue 초기화가 없으면, 사용하는 쪽에서 일반 property 초기화 하듯이 접근 불가
@propertyWrapper
struct SomeProperty {
  var wrappedValue = "someValue"
  
  init() {
    print("init!")
  }
}

// 사용
@SomeProperty myProperty = "" // compileError
@SomeProperty myProperty // ok

 

PropertyWrapper의 projectedValue

  • projectedValue이란?
    • propertyWrapper 내부에서 다른 값을 정의하여 사용하는쪽에서 달러 `$` 키워드로 해당 값에 접근할 수 있는 기능
    • propertyWrapper에서 부가적인 프로퍼티 접근에 사용
  • wrappedValue와의 차이
    • wrappedValue는 사용하는 쪽에서 선언할 때 그 변수값에 대한 wrapping된 값에 접근
    • projectedValue는 사용하는 쪽에서, 선언된 변수 앞에 `$` 접두사로 접근하면 propertyWrapper 내부에서 정의한 값 반환
  • projectedValue 사용 방법
    • 정의하는 쪽: propertyWrapper 내부의 projectedValue를 정의
    • 사용하는 쪽: $로 선언한 변수명으로 접근

ex) Flag라는 propertyWrapper

name과 flag 값이 들어있는 형태이고, projectedValue를 통해서 외부에서 name에 관한 값도 접근 가능

// ex3) projectedValue
@propertyWrapper
struct Flag {
  let name: String
  
  var wrappedValue = false
  var projectedValue: String { self.name }
  
  init(name: String) {
    self.name = name
  }
}

// 사용
class ViewController: UIViewController {
  @Flag(name: "isSignIn") var isSignIn
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    print(self.isSignIn) // false
    print(self.$isSignIn) // "isSignIn"
  }
}

* 전체 코드: https://github.com/JK0369/ExPropertyWrapper

 

* 참고

https://www.swiftbysundell.com/articles/property-wrappers-in-swift/

Comments