#keyPath와 #selector는 문법적인 내용이 아닌 Framework와 관련된 내용이다.
objc부터 쓰이던 기술임에도 불고하고 여전히 쓰이던 내용이다. framework 내부적인 메커니즘에 여전히 objc를 떼어놓고 사용할 수 없는 몇 가지 기술들이 있기 때문에 여전히 쓰인다. 물론 버려져야함..
#keyPath
person.name
⬇️
"person.name" or person["name"] 이런식으로 접근하면 안될까?
키경로(문자열처럼 쉽게 만들 수 있는 인스턴스)/ 문자열로 속성에 접근하는 것이 편하지 않을까?
라는 생각으로 시작된 문법??
이런 문법이 필요한 예를 들어보자.
class Restaurant{
var name: String
var affiliate: Snack
init(name: String, affiliate: Snack){
self.name = name
self.affiliate = affiliate
}
}
class Snack{
var staffname: Person
init(staffname: Person){
self.staffname = staffname
}
}
class Person{
var name: String
init(name: String){
self.name = name
}
}
let person = Person(name: "clamp")
let snack = Snack(staffname: person)
let restaurant = Restaurant(name: "master", affiliate: snack)
// Restaurant는 Snack클래스를 갖고있고,
// Snack클래스는 Person클래스를 갖고있고
// Person클래스는 name을 갖고있다.
// 최종적으로 알고싶은 Person의 name까지 접근하기 위해 써야하는 코드가 늘어난다면?
let clampname = restaurant.affiliate.staffname.name // clamp
let namePath = \Restaurant.affiliate.staffname.name // 미리 경로를 지정[keyPath]
restaurant[keyPath: namePath] // 딕셔너리방식으로 접근
미리 경로를 만들어 놓고 그 경로를 통해서 clamp까지 접근할 수 있었다.
만약 clamp와 같은 경로까지 무수히 많은 인스턴스에대해 접근해야한다면 일일이 .점접근문법을 써서 직접적으로 접근한다.
하지만 키패스를 사용하면 간접적으로 접근할 수 있다.
직접적인 접근
let clampname = restaurant.affiliate.staffname.name
간접적인 접근
let namePath = \Restaurant.affiliate.staffname.name
restaurant[keyPath: namePath]
경로를 정해놓고 간접적으로 접근할 수 있다.
keyPath 타입
- AnyKeyPath : 어떤 속성인지 특정되지 않음(보통, 함수 파라미터형식으로만 사용)
- PartialKeyPath<Root> : 타입에 대한 키패스(예를 들어 배열 같은 것으로 묶을때 사용)
- KeyPath<Root, Value> : 타입과 (읽기)속성에 대한 키패스(구조체)
- WritableKeyPath<Root, Value> : 타입과 읽기/쓰기 가능한 속성에 대한 키패스(구조체)
- ReferenceWritableKeyPath<Root, Value> : 클래스의 타입과 읽기/쓰기 가능한 속성에 대한 키패스
결론
키패스 == 경로
+ 추가 예전버전, Objective-C의 방식, #keyPath(타입.속성) 방식
// NSObject클래스를 상속해야함(구조체 지원 안함)
// - NSObject에 value(forKey:)메서드가 구현이 되어있기 때문
// - 속성에도 @objc를 붙여야함
// - 사용시, 구체적타입으로 다시 타입캐스팅해서 사용해야함
class Person: NSObject {
@objc var name: String
init(name: String) {
self.name = name
}
}
let person2 = Person(name: "clamp")
person2.name
let gjName = person2.value(forKey: "name") as? String
let gjName2 = person2.value(forKeyPath: #keyPath(Person.name)) as? String
let path = #keyPath(Person.name)
let gjName3 = person2.value(forKeyPath: path) as? String
+추가 키패스 경로를 추가하는 방법
// 타겟 \Restaurant.affiliate.staffname.name
let namePath = \Restaurant.affiliate.staffname // 어머 name을 빼먹었네
let newNamePath = namePath.appending(path: \.name) //추가