관리 메뉴

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

[iOS - Swift] KVC, KeyPath, DynamicMemberLookup 개념 본문

iOS 응용 (swift)

[iOS - Swift] KVC, KeyPath, DynamicMemberLookup 개념

jake-kim 2022. 11. 19. 22:42

KVC와 KeyPath

  • KVC(Key - Value coding) - 객체의 값을 직접 가져오지 않고 KeyPath를 이용해서 간접적으로 데이터를 가져오거나 사용 (key는 string)
  • KeyPath
    • 값에 대한 참조가 아닌, 프로퍼티를 참조
struct Person {
  var name: String
}

let person = Person(name: "jake")

person.name // 값을 참조
person[keyPath: \.name] // 프로퍼티를 참조
  • KeyPath 종류
    • AntyKeyPath: 타입이 지워진 KeyPath
    • PartialKeyPath: 부분적으로 타입이 지워진 KeyPath
    • KeyPath: 읽기 전용
    • WriatableKeyPath: 읽기, 쓰기 가능
    • ReferenceWritableKeyPath: 클래스의 인스턴스에 사용 가능, 변경 가능한 모든 프로퍼티에 대한 읽기와 쓰기 가능
  • KeyPath의 특징 - 프로퍼티 분류가 외부에서 선택이 가능
// KeyPath 없이 child의 name을 가져오는 경우, getChild1Name(), getChild2Name() 
struct Parent {
  var child1: Child
  var child2: Child
  
  func getChild1Name() -> String {
    child1.name
  }
  func getChild2Name() -> String {
    child2.name
  }
}

struct Child {
  var name: String
}

let parent = Parent(child1: Child(name: "1"), child2: Child(name: "2"))
parent.getChild1Name()
parent.getChild2Name()

(KeyPath 사용한 경우 - 키패스는 프로퍼티를 구분할 수 있으므로 키패스사용)

struct Parent {
  var child1: Child
  var child2: Child
  
  func getChildName(keyPath: KeyPath<Parent, Child>) -> String {
    self[keyPath: keyPath].name
  }
}

struct Child {
  var name: String
}

let parent = Parent(child1: Child(name: "1"), child2: Child(name: "2"))
parent.getChildName(keyPath: \.child1) // 값에 대한 참조가 아닌 프로퍼티를 참조
parent.getChildName(keyPath: \.child2)
  • KeyPath 타입: <Root타입, Root타입이 가지고 있는 인스턴스 타입>
public class KeyPath<Root, Value> : PartialKeyPath<Root> {
}

dynamic member lookup

  • 인덱싱은 원래 arr[i]처럼 접근하지만, dynamic lookup은 닷 ‘.’으로 접근할수 있는 기능

(적용 전 - [”jake”]와 같이 괄호안에 인덱싱으로 접근)

struct Person1 {
  var info: [String: String] // name: gender
  
  subscript(name: String) -> String? {
    return self.info[name]
  }
}

let person1 = Person1(
  info: ["jake": "man1"]
)

print(person1["jake"]) // Optional("man1")

(적용 - 점으로 접근)

  • @dynamicMemberLookup
  • subscript와 dynamicMember를 명시하는 subscript 정의
@dynamicMemberLookup
struct Person2 {
  var info: [String: String] // name: gender
  
  subscript(dynamicMember name: String) -> String? {
    return self.info[name]
  }
}

let person2 = Person2(
  info: ["jake": "man2"]
)

print(person2.jake) // Optional("man2")

KeyPath + dynamicLookup 사용하여 syntatic sugar 만들기

  • keyPath와 dynamicLookup 사용
    • keyPath: 프로퍼티 별 접근이 가능
    • dynamicLookup: ‘.’ dot으로 접근 가능

ex) parent안에 있는 child의 name접근 시, parent.name로 접근하기

(일반적인 경우 - parent.child.name으로 접근)

struct Parent {
  var child: Child
}

struct Child {
  var name: String
  var company: String
}

let parent = Parent(child: Child(name: "1", company: "c1"))
print(parent.child.name)

(KeyPath + dynamicLookup 사용 - parent.name 접근)

1단계) subscript안에 keyPath를 주입받도록 구현

  • parent[\.company]로 접근 가능 (KeyPath의 루트가 Child 형태이므로)
struct Parent {
  var child: Child

  subscript(keyPath: KeyPath<Child, String>) -> String {
    child[keyPath: keyPath]
  }
}

struct Child {
  var name: String
  var company: String
}

let parent = Parent(child: Child(name: "1", company: "c1"))
print(parent[\\.company])

2단계) dynamicMemberLookup을 사용하여 대괄호 대신에 dot ‘.’으로 접근 가능하도록 구현

  • parent[\.company]에서 parent.company로 접근 가능 (매우 간편하게 접근)
@dynamicMemberLookup
struct Parent {
  var child: Child

  subscript(dynamicMember keyPath: KeyPath<Child, String>) -> String {
    child[keyPath: keyPath]
  }
}

struct Child {
  var name: String
  var company: String
}

let parent = Parent(child: Child(name: "1", company: "c1"))
print(parent.company)

요약

  • keyPath: 값에 대한 참조가 아닌, 프로퍼티를 참조
  • dynamic member lookup: 대괄호 []로 접근하는 기호를 dot '.'으로 접근할 수 있게끔 하는 방법
  • keyPath + dynamic member lookup: Parent가 가지고 있는 Child이 프로퍼티를 외부에서 쉽게 접근 가능 (parent.childName) 

 

* 참고

https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/KeyValueCoding.html

Comments