관리 메뉴

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

[iOS - swift] KeyPath, WritableKeyPath, ReferenceWritableKeyPath, DynamicMemberLookup 개념 본문

iOS 기본 (swift)

[iOS - swift] KeyPath, WritableKeyPath, ReferenceWritableKeyPath, DynamicMemberLookup 개념

jake-kim 2022. 1. 9. 21:44

KeyPath

  • 특정 속성에 대한 path정보를 가지고 있는 key값 (KeyPath 인스턴스를 통해 해당 값에 접근이 가능)
  • // KeyPath 문법: `\`키워드 + 유형 + 프로퍼티 이름
    let nameKeyPath: KeyPath<Person, String> = \Person.name
    
    let person = Person(age: 12, name: "jake")
    print(person[keyPath: nameKeyPath]) // jake
  • KeyPath 활용 - RxSwift의 Observable 구독 할때 특정 property를 가져오기 위해 map에서 사용 (간결성 향상)
    struct Person {
      var age: Int
      var name: String
    }
    
    var personObservable: Observable<Person> {
      Observable.create { observer in
        observer.onNext(.init(age: 12, name: "jake"))
        observer.onCompleted()
        return Disposables.create()
      }
    }
    
    ...
    
      override func viewDidLoad() {
        super.viewDidLoad()
        
        personObservable
          .map(\.age) // <- KeyPath 사용
          .subscribe { print($0) }
          .disposed(by: self.disposeBag)
      }​

writableKeyPath, ReferenceWritableKeyPath

  • writableKeyPath는 struct와 같은 Value-Type 전용이고, ReferenceWritableKeyPath는 class와 같은 Referece-Type에서 사용
  • (Struct와 같은 Value-Type에 적용) KeyPath는 읽기 전용이지만, writableKeyPath는 쓰기도 가능
  • KeyPath를 선언할 때 타입을 입력하지 않으면 writableKeyPath으로 되는것을 주의
// KeyPath 문법: `\`키워드 + 유형 + 프로퍼티 이름
let nameWritableKeyPath = \Person.name // WritableKeyPath<Person, String>

var person = Person(age: 12, name: "jake")

person[keyPath: nameWritableKeyPath] = "abc"
print(person) // abc​

dynamicMemberLookup

https://docs.swift.org/swift-book/ReferenceManual/Attributes.html

  • 정의: 명백한 member (프로퍼티) 표현이 아닐 때, 컴파일러가 subscript(dynamicMember):)를 보고 해당 멤버임을 받아들이게 하는 방법
  • 사용처
    • dictionary 값에 접근 할 때 key값을 문자열로 접근하는게 아닌, 일반 프로퍼티처럼 접근 가능
    • subscript에 KeyPath를 인수로 받으면 Wrapper로써 기능 사용 가능
      (원래 A.B.property로 접근해야 하지만, A.property로 접근 가능)
  • 사용 방법
    • @dydnamicMemberLookup 키워드를 struct와 같은 유형 위에 선언
    • subscript(dydnamicMember:) 메소드 구현

ex1) 문자열로 접근하는게 아닌 일반 프로퍼티처럼 접근 기능

- someDictionary.name으로 접근 

- someDictionary.value.name도 아니라 곧바로 name으로 접근

- name은 문자열이지만 프로퍼티처럼 접근 가능

@dynamicMemberLookup
struct SomeDictionary {
  let value = [
    "name": "jake",
    "age": "12"
  ]
  subscript(dynamicMember member: String) -> String? {
    return value[member]
  }
}

let someDictionary = SomeDictionary()
print(someDictionary.name ?? "") // jake
print(someDictionary.abc ?? "") // ""
print(someDictionary.age ?? "") // 12

 

  • KeyPath를 이용하면 Wrapper 타입처럼 사용 가능

ex2) subscript에 KeyPath를 인수로 받으면 Wrapper로써 기능 사용 가능

- ContainerStruct 생성자에 다른 Struct를 넣고, 안에 있는 struct를 마치 프로퍼티처럼 접근

- KeyPath와 같이 사용

@dynamicMemberLookup
struct MyContainer<Root> {
  private let root: Root
  
  init(_ root: Root) {
    self.root = root
  }
  
  subscript<Value>(dynamicMember keyPath: WritableKeyPath<Root, Value>) -> Value {
    return root[keyPath: keyPath]
  }
}

struct MyStruct {
  var name: String
  var age: Int
}

let myStruct = MyStruct(name: "jake", age: 12)
let myContainer = MyContainer(myStruct)
print(myContainer.name) // <- 그냥 일반 프로퍼티처럼 접근
  • Rx에서 map 키워드를 생략하여 프로퍼티 접근 가능하도록 설정
// Observable
// @dynamicMemberLookup 키워드는 extension에 선언하지 못하므로 따로 protocol 선언 -> Observable에서 해당 프로토콜 준수하여 구현
@dynamicMemberLookup
protocol DynamicMemberLookupObservableType {
  associatedtype Root
  /// Value 타입 - Observable<Value>
  subscript<Value>(dynamicMember keyPath: KeyPath<Root, Value>) -> Observable<Value> { get }
}

extension Observable: DynamicMemberLookupObservableType {
  subscript<Value>(dynamicMember keyPath: KeyPath<Element, Value>) -> Observable<Value> {
    self.map { $0[keyPath: keyPath] }
  }
}

struct Person3 {
  var name: String
  var gender: String
}

let person3 = self.exampleObservableDynamicMember()
let name = person3.name.asObservable().subscribe { print($0) } // next(jake)
// person3.map { $0.name } 처럼 접근 안해도 되는 간편성

* 참고

https://github.com/apple/swift-evolution/blob/master/proposals/0252-keypath-dynamic-member-lookup.md

https://developer.apple.com/documentation/swift/keypath

Comments