Swift

Swift: 키 경로(Key Path)

소재훈 2022. 2. 11. 18:14

야곰님의 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 공식문서

WritableKeyPath <Root, Value> 타입은 값 타입에 키 경로 타입으로 읽고 쓸 수 있는 경우에 적용된다. 

 

ReferenceWritableKeyPath 공식문서

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}의 표현과 같은 역할을 수행한다.