ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • dynamicMemberLookup (+ KeyPath)
    Swift 2021. 9. 23. 17:03

    Swift 4.2의 변경사항

    Dictionary 형태의 값을 subscript(dynamicMember:)를 사용해서 값을 마치 property처럼 사용이 가능하도록 하는것으로 이해했다.

    아래의 예제가 도움이 될것이다! (참고는 https://github.com/apple/swift-evolution/blob/master/proposals/0252-keypath-dynamic-member-lookup.md)

    @dynamicMemberLookup
    struct Person {
      subscript(dynamicMember member: String) -> String {
        switch member {
          case "age":
            return "20"
          case "name":
            return "hello"
          default:
            return ""
        }
      }
    }
    
    // 사용할때는 아래처럼
    let taylor = Person()
    print(taylor.age)

    위의 코드로 알 수 있을때 사용할때 마치 파싱한것처럼 사용이 가능한 셈이다. -> dot syntax로 바로 호출할 수 있는것...!!

    그치만 return이 한가지로 제한되는 부분, key 자동완성 등등 실제로 동적 멤버와의 호환성이 좋다고 보기 어려웠다. 하지만...

     

    ++ 추가 사용 예제

    더보기
    // Json enum을 dynamic member lookup으로 만들어 놓은게 있는데 다음처럼 사용가능하다.
    
    @dynamicMemberLookup
    enum JSON {
       case intValue(Int)
       case stringValue(String)
       case arrayValue(Array<JSON>)
       case dictionaryValue(Dictionary<String, JSON>)
    
       var stringValue: String? {
          if case .stringValue(let str) = self {
             return str
          }
          return nil
       }
    
       subscript(index: Int) -> JSON? {
          if case .arrayValue(let arr) = self {
             return index < arr.count ? arr[index] : nil
          }
          return nil
       }
    
       subscript(key: String) -> JSON? {
          if case .dictionaryValue(let dict) = self {
             return dict[key]
          }
          return nil
       }
    
       subscript(dynamicMember member: String) -> JSON? {
          if case .dictionaryValue(let dict) = self {
             return dict[member]
          }
          return nil
       }
    }
    
    // 사용할때 아래처럼 (평소엔 Codable로 상속 받아서 model.stringValue 처럼 호출 가능)
    
    json[0]?["name"]?["first"]?.stringValue

     

    출처 : https://www.hackingwithswift.com/articles/55/how-to-use-dynamic-member-lookup-in-swift

     

    두둥 Swift 5.1에서 추가가 되었다.

     

    바로 keyPath를 통해 동적으로 접근이 가능하게 된 셈인데, 이는 기존에 리턴 타입이 제한되는 부분이나, 멤버 변수가 무엇인지 모르는 문제도 해결이 되었다. 

     

    + KeyPath가 뭔지 확실히 알고 싶다 아래를 펼쳐 보세요~

    더보기

    KeyPath는 값에 대한 참조가 아닌 프로퍼티에 대한 참조이다. 말 그대로 값에 대한 path 

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

    struct Person {
    	var name: String
    }
    
    let taylor = Person(name: "me")
    taylor.name // 변수를 사용해 값에 접근.
    
    var testTaylor = Person(name: "me")
    // KeyPath<Person, String>
    let nameKeyPath = \Person.name
    
    print(testTaylor[keyPath: \.name])
    print(testTaylor[keyPath: nameKeyPath]) // 이런식으로 keypath 만들어서 사용가능

    위의 예시로 사용 방법이 익혀지길 바란다.

     

    일단 근본적으로 이걸 왜 사용할까?
    : 딕셔너리의 단점을 보완해준다고 생각하면 될것 같다.

    딕셔너리의 키값이 존재하지 않거나, return 타입이 다르거나, key 값을 오타로 찾는 등등

     

    사용예시?

    여러가지가 있겠지만, KVC(Key-Value-Coding) 에서 Key나 KeyPath로 간접적으로 가져오는데 요때 사용하거나, 지금 설명하는 dynamicMemberLookup에도 그 개념이 적용된다고 볼 수 있다.

     

    + Observe도 제공하는데 이때는 NSObject를 상속 받아야 하고 아래처럼 사용하는것을 본적이 있을것이다.

    class A: NSObject {
        @objc dynamic var b = 0
    }

     

    5가지 KeyPath

    • AnyKeyPath
    • APartialKeyPath
    • KeyPath<Source, Target>
    • WritableKeyPath<Source, Target>
    • ReferenceWritableKeyPath<Source, Target>

    아래처럼 사용했을때 사용도가 매우 다르게 느껴졌다. :b

    @dynamicMemberLookup
    struct PersonWrapper<T> {
    	private let primate: T
    	
    	init(_ primate: T) {
    		self.primate = primate
    	}
    	
    	subscript<U>(dynamicMember keyPath: WritableKeyPath<T, U>) -> U {
    		return primate[keyPath: keyPath]
    	}
    }
    
    struct Person {
    	var name: String = "me"
    	var age: Int = 27
    }
    
    let person = PersonWrapper(Person())
    print(person.age) // 이렇게 하면 전과 달리 dot 뒤로 뜬다.

    아래 활용방법의 예제로 Builder를 protocol로 선언하고 확장하는 방법을 적어두었다. 매개변수가 많을때 생성자 대신 빌더를 사용하는게 가독성이 좋고 사용이 쉬운 코드를 작성할 수 있다.

     

    활용방법

    여기서 사용 방법? 어떤 사용성이 있을까 고민되었는데 아주 적절한 사용을 알게 되었다.

    http://minsone.github.io/programming/swift-ribs-access-dependency-property-using-dynamic-member-lookup

    위의 출처에서 따온 예제 코드

    public protocol TestDependency: Dependency {
      var name: String { get }
      var age: UInt { get }
    }
    
    final class TestComponent: Component<TestDependency> {
      fileprivate let name: String
      fileprivate let age: UInt
    
      override init(dependency: TestDependency) {
      	self.name = dependency.name
      	self.age = dependency.age
      	super.init(dependency: dependency)
      }
    }
    
    // 하나하나 init에 코드를 넣어줘야한다. 또는 let 대신 자주 사용하는 Computed Property 처럼 사용하기도 하지요
    
    final class TestComponent: Component<TestDependency> {
      fileprivate var name: String {
      	dependency.name
      }
      fileprivate var age: UInt {
        dependency.age	
      }
      override init(dependency: TestDependency) {
      	super.init(dependency: dependency)
      }
    }
    
    // 이때도 dependency.~~ 이렇게 호출하고 있다.

    dynamicMemberLookup를 사용해서 아래처럼 수정할 수 있다.

    public protocol TestDependency: Dependency {
      var name: String { get }
      var age: UInt { get }
    }
    
    @dynamicMemberLookup
    final class TestComponent: Component<TestDependency> {
      override init(dependency: TestDependency) {
      	super.init(dependency: dependency)
      }
      subscript<U>(dynamicMember keyPath: KeyPath<TestDependency, U>) -> U {
        dependency[keyPath: keyPath]
      }
    }
    
    public protocol TestBuildable: Buildable {
      func build() -> TestRouting
    }
    
    public final class TestBuilder: Builder<TestDependency>, TestBuildable {
      public override init(dependency: TestDependency) {
        super.init(dependency: dependency)
      }
      
      public func build() -> TestRouting {
        let component = TestComponent(dependency: dependency) // 이런식으로!!
        let viewController = TestViewController(name: component.name)
        print(component.name)
      }
    }

    ++ 위의 아키텍쳐를 제외하고 볼 수 있는 예제 

    출처 : https://techblog.woowahan.com/2715/

    의 dynamicMemberLookup 과 KeyPath 를 이용한 Builder pattern 부분 + 그 뒤 예제도 도움이 많이 되었다.

     

    728x90

    'Swift' 카테고리의 다른 글

    Computed Property + Access Control  (0) 2021.09.30
    Builder - dynamicMemberLookup (+ KeyPath)  (0) 2021.09.30
    ios 웹뷰와 통신 (Native <-> JavaScript 통신)  (0) 2021.07.21
    Combine  (0) 2021.07.20
    Swift Swizzling  (0) 2021.07.16

    댓글

Designed by Tistory.