티스토리 뷰

Swift

Key-Value Coding(KVC) / KeyPath in Swift

Zedd0202 2021. 2. 28. 22:21
반응형

 

안녕하세요 :) Zedd입니다.

오늘은 KVC에 대해서 공부해보겠습니다. 

 

# KVC

- Key-Value Coding 의 약자

- 객체의 값을 직접 가져오지않고, Key 또는 KeyPath 를 이용해서 간접적으로 데이터를 가져오거나 수정하는 방법

- key는 String

- key는 일반적으로 객체에 정의된 accessor method 또는 인스턴스 변수의 "이름"

- key는 특정 규칙을 따라야함 - ASCII로 인코딩 되고, 소문자로 시작해야하며, 공백이 없어야한다.

 

예를들어

person객체에 address프로퍼티가 있고,

address프로퍼티는 town프로퍼티를 가지고 있다고 생각해봅시다.

id address = [person valueForKey:@"address"];
id town = [address valueForKey:@"town"];

그럼 이렇게 String key를 이용하여 프로퍼티에 접근할 수 있게 됩니다.

id town = [person valueForKeyPath:@"address.town"];

이렇게 keypath로도 접근이 가능합니다. address.town으로 바로 접근할 수 있게 됐죠

key를 가지고 value를 가져오는 그런 코딩 방식(?)을 KVC라고 하는 것 같습니다.

 

근데 Swift에서 이걸 해보려고 하면 살짝 상상이 안가죠.

struct Address {
    var town: String
}

struct Person {
    var address: Address
}
let address = Address(town: "어쩌구")
let zedd = Person(address: address)

print(zedd.address)
print(zedd.address.town)

왜냐면 나는 String으로 가져오지 않고..

그냥 zedd.address.town이런식으로 가져오니까..그러니까 객체의 값을 "직접"가져오는 것이 일반적입니다.

 

KVC는 key또는 keyPath를 이용하여 간접적으로 값을 가져오는 방식이었는데..

Swift에서 KVC를 하려면 어떻게 할 수 있을까요? 

struct Address {
    var town: String
}

struct Person {
    var address: Address
}
let address = Address(town: "어쩌구")
let zedd = Person(address: address)

let zeddAddress = zedd[keyPath: \.address]
let zeddTown = zedd[keyPath: \.address.town]
print(zeddTown) // 어쩌구

Swift에서도 이렇게 keypath를 이용하여 간접적으로 value를 가져올 수 있습니다.

Objc코드에서는 값을 가져오는것만 했는데, 수정도 가능합니다.

struct Address {
    var town: String
}

struct Person {
    var address: Address
}
let address = Address(town: "어쩌구")
var zedd = Person(address: address)

zedd[keyPath: \.address.town] // 어쩌구

zedd[keyPath: \.address.town] = "바보"
zedd[keyPath: \.address.town] // 바보
zedd.address.town // 바보

 

이렇게 말이죠.

위 코드에서 보면 \.address.town가 반복되어 나타나는 것을 볼 수 있습니다.

keyPath를 만들어 놓고 여러군데서 쓰면 좋을텐데요.

 

# KeyPath를 만드는 방법

Keypath를 만드는 syntax입니다.

let writableKeyPath = \Person.address.town

zedd[keyPath: writableKeyPath] // 어쩌구
zedd[keyPath: writableKeyPath] = "바보"
zedd[keyPath: writableKeyPath] // 바보

이렇게 keyPath를 만들어 사용하고싶은 곳에 사용하면 됩니다. 

Q :  \.address.town으로 했는데 왜  \Person.address.town으로 해줘야해?

A : zedd가 이미 Person타입이므로 타입을 생략할 수 있었습니다. 

 

# KeyPath의 종류 

Keypath의 종류에는 위와같은것들이 있는데요, 

AnyKeyPath - 타입이 지워진 KeyPath

PartialKeyPath - 부분적으로 타입이 지워진 KeyPath


✔️ KeyPath - Read-only

✔️ WritableKeyPath - value type 인스턴스에 사용 가능. 변경 가능한 모든 프로퍼티에 대한 read & write access 제공 

✔️ ReferenceWritableKeyPath - 클래스의 인스턴스에 사용 가능. 변경 가능한 모든 프로퍼티에 대한 read & write access 제공. 
struct Address {
    var town: String
}

struct Person {
    var address: Address
}

let writableKeyPath = \Person.address.town

zedd[keyPath: writableKeyPath] // 어쩌구
zedd[keyPath: writableKeyPath] = "바보"
zedd[keyPath: writableKeyPath] // 바보

자..제가 writableKeypath라고 이름을 지어줬는데.. WritableKeyPath타입입니다.

✔️ WritableKeyPath - value type 인스턴스에 사용 가능. 변경 가능한 모든 프로퍼티에 대한 read & write access 제공 

왜냐면..

struct Address {
    var town: String
}
let writableKeypath = \Person.address.town

town이 var이기 때문입니다. (변경가능한 모든 프로퍼티에 대한..)

만약 town이 let이라면 수정할 수 없으므로 

더이상 WritableKeyPath가 아닌 그냥 KeyPath타입이 되게 됩니다.

당연히 Read-only이니 변경도 안됩니다. 

let이면 자동으로 KeyPath타입을.

var면 자동으로 WritableKeyPath타입을 넣으니 참고해주세요.

 

자...위 예제들에서 Person과 Address는 모두 struct(value type)였는데요.

이걸 class(reference type)로 바꿔보겠습니다.

class Address {
    var town: String
    
    init(town: String) {
        self.town = town
    }
}

class Person {
    var address: Address
    
    init(address: Address) {
        self.address = address
    }
}

let referenceWritableKeyPath = \Person.address.town

이때는 저 keypath가 자동으로 ReferenceWritableKeyPath타입이 됩니다.

✔️ ReferenceWritableKeyPath - 클래스의 인스턴스에 사용 가능. 변경 가능한 모든 프로퍼티에 대한 read & write access 제공. 

여기서도 town이 let이면 KeyPath타입이 되겠죠?

 

 

참고 

developer.apple.com/wwdc17/212

developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/index.html

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

반응형