-
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로 선언하고 확장하는 방법을 적어두었다. 매개변수가 많을때 생성자 대신 빌더를 사용하는게 가독성이 좋고 사용이 쉬운 코드를 작성할 수 있다.
활용방법
여기서 사용 방법? 어떤 사용성이 있을까 고민되었는데 아주 적절한 사용을 알게 되었다.
위의 출처에서 따온 예제 코드
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