Swift: 키 경로(Key Path)
야곰님의 SWIFT 프로그래밍 책을 공부하고 요약하여 정리한 것입니다~~~
함수는 일급 객체로서 상수나 변수에 참조를 할당할 수 있었다.
함수를 참조해두고, 나중에 원할 때 호출할 수 있고, 다른 함수를 참조하도록 할 수도 있다.
func someFunction(paramA: Any, paramB: Any) {
print("someFunction called...")
}
func anotherFunction(paramA: Any, ParamB: Any) {
print("anotherFunction called...")
}
var functionReference = someFunction(parmaA: , paramB: )
functionReference("A", "B")
functionReference = anotherFunction(paramA: , ParamB: )
프로퍼티도 키 경로(Key Path)를 이용해서 값을 꺼내 오는 것이 아니라 프로퍼티의 참조를 담을 수 있다.
키 경로 타입은 AnyKeyPath라는 클래스로부터 파생된다. 제일 많이 확장된 키 경로 타입은 WritableKeyPath <Root, Value>와 ReferenceWritableKeyPath <Root, Value> 타입이다. 제네릭 타입으로 표현되어 있다.
WritableKeyPath <Root, Value> 타입은 값 타입에 키 경로 타입으로 읽고 쓸 수 있는 경우에 적용된다.
ReferenceWritableKeyPath <Root, Value> 타입은 참조 타입, 클래스 타입에 키 경로 타입으로 읽고 쓸 수 있는 경우에 적용된다.
키 경로는 다음과 같이 구성된다.
\타입이름.경로.경로.경로
경로는 프로퍼티의 이름이라고 생각하면 된다.
class Person {
var name: String
init(name: String) {
self.name = name
}
}
struct Stuff {
var name: String
var owner: Person
}
print(type(of: \Person.name)) //ReferenceWritableKeyPath<Person, String>
print(type(of: \Stuff.name)) //WritableKeyPath<Stuff, String>
appending 메서드를 사용하여 기존의 키 경로에 하위의 경로를 첫 붙여 줄 수도 있다.
let keyPath = \Stuff.owner
let nameKeyPath = keyPath.appending(path: \.name) // \Stuff.owner.name과 동일
상수로 지정한 값 타입과 읽기 전용 프로퍼티는 키 경로 서브스크립로도 값을 바꿔줄 수 없음을 주의하자
키 경로를 잘 활용하면 프로토콜과 마찬가지로, 타입 간의 의존성을 낮추는데 많은 도움을 준다. 애플의 프레임 워크에서도 key-value 코딩 등에서 키 경로를 활용하므로 알아두면 좋다.
자신을 나타내는 키 경로인 \.self를 사용하면 인스턴스 그 자체를 표현하는 키 경로가 된다. 컴파일러가 타입을 유추할 수 있는 경우에는 키 경로에서 타입 이름을 생략할 수도 있다.
스위프트 5.2 버전부터(SomeType) -> Value 타입의 클로저를 키 경로 표현으로 대체하여 사용할 수 있다.
struct Person {
let name: String
let nickName: String?
let age: Int
var isAdult: Bool {
return age > 18
}
}
let jaehoon: Person = Person(name: "jaehoon", nickName: "cowJaehoon", age: 24)
let eunsung: Person = Person(name: "eunsung", nickName: nil, age: 100)
let minsung: Person = Person(name: "minsung", nickName: "min", age: 16)
let friends: [Person] = [jaehoon, eunsung, minsung]
let names: [String] = friends.map(\.name)
print(names) //["jaehoon", "eunsung", "minsung"]
let nickNames: [String] = friends.compactMap(\.nickName)
print(nickNames) //["cowJaehoon", "min"]
let adults: [String] = friends.filter(\.isAdult).map(\.name)
print(adults) //["jaehoon", "eunsung"]
friends 배열의 [Person] 타입이며, 이 타입의 map은 (Person) -> T를, compactMap은 (Person) -> T?를, filter는 (Person) -> Bool 타입의 클로저를 매개변수를 통해 전달받는다.
\.name 표현은 (Person) -> T 타입의 클로저를 대체하여 표현하였고, 이는 클로저 표현인 {$0.name}의 표현과 같은 역할을 수행한다.