본문 바로가기

Swift/꼬꼬무

Key-Value Coding

SwiftUI 로 개발을 하다보면 \.self 이런 표현식을 접하게 되는데, 그냥 쓰려니까 도저히 이해가 되지 않아서 하는 정리

일단 차근차근 시작해봅시다.

먼저 \. (백슬래쉬 닷)은 Key-Path String Expression 이라고 함.

Key-Path Expression 에 대한 문서인데

 

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/expressions/#Key-Path-String-Expression

 

처음 설명이 Objective-C에서 속성을 참조하는 데 사용되는 문자열에 액세스 해서

KVC(Key-Value Coding), KVO(Key-Value observing) API에서 사용할 수 있다고 하는데..

그럼 KVC랑 KVO에 대해서 먼저 알아보자

 

KVC (Key-Value Coding)

NSKeyValueCoding 프로토콜을 통해서 활성화 되는 메커니즘

객체가 프로퍼티에 대한 간접적인 액세스를 제공

문자열 식별자를 통해 객체의 프로퍼티를 찾음, 키는 일반적으로 객체에 의해 정의된 접근자 메서드나 인스턴스 변수의 이름에 해당해야 함.

키는 ASCII로 인코딩되어야 하고 소문자로 시작해야하고 공백이 없어야 함

 

종합해보면 KVC는 NSKeyValueCoding 프로토콜을 통해 문자열 식별자를 이용해서 프로퍼티에 대한 간접적인 액세스를 제공하는 매커니즘

 

 

예제는 BankAccount 오브젝트와 프로퍼티

@interface BankAccount : NSObject
 
@property (nonatomic) NSNumber* currentBalance;              // An attribute
@property (nonatomic) Person* owner;                         // A to-one relation
@property (nonatomic) NSArray< Transaction* >* transactions; // A to-many relation
 
@end

 

 

이런 객체에 속성에 접근하기 위해 메서드를 구현할 수 있습니다.

[myAccount setCurrentBalance:@(100.0)];

 

이렇게 되면 직접적이긴 하지만 유연성이 부족합니다.

 

이런 경우 KVC를 사용하면 문자열 식별자를 사용하여 객체의 프로퍼티에 접근할 수 있습니다.

[myAccount setValue:@(100.0) forKey:@"currentBalance"];

 

키를 사용하여 속성 값 얻기

  • valueForKey - 키 파라미터로 명명된 속성의 값을 반환, 속성을 찾지 못하면 valueForUndefinedKey 메시지를 발생
  • valueForKeyPath - 지정된 키 경로의 값을 반환
  • dictionaryWithValuesForKeys - 키 배열의 값을 반환

 

단일 값인지, 배열인지 차이를 제외하면

 

valueForKey, valueForKeyPath 이렇게 두가지로 값을 반환 받을 수 있는 거 같은데

 

차이점을 Araboza

말로 설명하면 어려워서 바로 코드를 볼게요.

// 1: valueForKey 사용
[[myObject valueForKey:@"foo"] valueForKey:@"bar"]

// 2: valueForKeyPath 사용
[myObject valueForKeyPath:@"foo.bar"]

 

위 예제의 1, 2는 결과 값이 같습니다.

1은 valueForKey를 2번 써서 path를 찾아간거고

2번은 Dot(.)을 사용해서 직접 path를 입력해서 값을 얻는 방법 입니다.

키를 사용하여 속성 값을 설정하는 법도 비슷해요

  • setValue: forKey: - 지정된 키 값을 주어진 값으로 설정
  • setValue: forKeyPath - 지정된 키 경로에 지정된 값을 설정
  • setValuesForKeysWithDictionary - 지정된 dictionary 값으로 여러 키 값을 주어진 값들로 설정

 

저는 Key-Path String Expression 을 위해 KVC를 공부하려는거라 여기까지만

사실 그 이상은 어려워서..

 

근데 프로퍼티에 액세스 하는 한가지 설명이 더 있어서 그냥 겸사겸사 공부해보면

 

키를 사용해서 객체 액세스를 단순화 시킬수도 있다고 하는데~

바로 예제 부터 보기

 

먼저 Key-Value Coding 없이

- (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row
{
    id result = nil;
    Person *person = [self.people objectAtIndex:row];
 
    if ([[column identifier] isEqualToString:@"name"]) {
        result = [person name];
    } else if ([[column identifier] isEqualToString:@"age"]) {
        result = @([person age]);  // Wrap age, a scalar, as an NSNumber
    } else if ([[column identifier] isEqualToString:@"favoriteColor"]) {
        result = [person favoriteColor];
    } // And so on...
 
    return result;
}

 

Objective-C를 할줄 몰라서 너무 어렵..

대충 column의 identifier를 각각 스트링과 같은지 조건을 돌려서 결과 값을 반환해야 하는데

 

Key-Value Coding을 사용하면 이렇게 좀 더 간결하게

- (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row
{
    return [[self.people objectAtIndex:row] valueForKey:[column identifier]];
}

 

 

 

자 이제 예제 코드로 볼게요

 

 

먼저 위에 Objective-C 기반의 문서를 보고 예제를 구현해 봤어요

valueForKey로 값 찾고, setValueForKey로 값 설정하고, 근데 이렇게 하려니까

 

Key-Value Observing은 Objective-C의 런타임 기능이기 때문에 프로퍼티 앞에 “@objc dynamic”을 붙여줘야 해요

class Person: NSObject {
    @objc dynamic var account: Account
    
    init(account: Account) {
        self.account = account
    }
}

class Account: NSObject {
    @objc dynamic var balance: Int
    var interestRate: Float
    
    init(balance: Int, interestRate: Float) {
        self.balance = balance
        self.interestRate = interestRate
    }
}

let account = Account(balance: 0, interestRate: 2.0)
let person = Person(account: account)

account.setValue(2000, forKey: "balance")
print(account.value(forKey: "balance"))
// Optional(2000)

person.setValue(3000, forKeyPath: "account.balance")
print(person.value(forKeyPath: "account.balance"))
// Optional(3000)

 

이렇게 예제를 해보고 음 그렇구나 하고 있는데, 좀 더 Swift 문법에 맞는 KVC를 찾았어요.

 

이건 좀 더 Swift 형식에 맞게 KVC를 사용한 예제에요.

class Person: NSObject {
    var account: Account
    
    init(account: Account) {
        self.account = account
    }
}

class Account: NSObject {
    var balance: Int
    var interestRate: Float
    
    init(balance: Int, interestRate: Float) {
        self.balance = balance
        self.interestRate = interestRate
    }
}

let account = Account(balance: 0, interestRate: 2.0)
let person = Person(account: account)

account[keyPath: \\.balance] = 2000
print(account[keyPath: \\.balance])

person[keyPath: \\.account.balance] = 3000
print(person[keyPath: \\.account.balance])