KVC 집합 접근자/변경자 메소드 작성방법
키밸류 코딩의 집합 접근자/변경자 메소드
to-many 관계의 프로퍼티에 대한 조정은 키밸류 코딩에서 배열 프록시를 통해서 이루어진다고 했다. 이 때 개별 원소를 추가/삭제/교체하는 작업의 효율을 높이고, 각 동작에 대해서도 KVO 지원을 가능하게 하기 위해서 배열 프록시와 연계하여 동작할 수 있는 집합 메소드를 추가로 정의하는 것이 강력하게 권장된다. 이들 메소드들은 기본적으로 NSMutableArray의 기본적인 액세스 메소드들에 키 이름이 혼합된 형태로, 일정한 규칙에 의해 이름 지어진다. 단, 키 이름이 메소드 이름에 들어가기 때문에 메소드명이 고정되지 않았고, 따라서 NSKeyValueCoding
레퍼런스 상에서는 소개되지 않는다.
집합 접근자 및 변경자들은 기본적으로 NSMuatbleArray
의 몇몇 메소드들의 이름에 키 이름을 추가하여 변형한 것이다. 집합메소드에서 키 이름은 <key>로 쓰여진다. 키 이름이 대문자로 시작해야하는 경우는 <Key>로 쓰였으니 잘 구분해서 사용해야 한다.
구분 | Array 메소드 | KVC 집합 메소드 | 비고 |
접근자 | -count |
-countOf<Key> |
필수 |
-objectAtIndex: |
-objectIn<Key>AtIndex: |
둘 중 하나 필수 | |
-objectsAtIndexes: |
-<key>AtIndexes: |
||
-getObjects:range: |
-getObjectsIn<Key>:range: |
선택적 | |
변경자 | -insertObject:atIndex: |
-insertObject:in<Key>AtIndex: |
배열 변경이 필요한 경우에는 필수 |
-removeObjectAtIndex: |
-removeObjectFrom<Key>AtIndex: |
||
-replaceObjectAtIndex:withObject: |
-replaceObjectIn<Key>AtIndex:withObject: |
선택적 |
Swift 버전을 생각하기
Swift에서는 몇가지 고려해야 하는 부분이 있다. (Swift4, Xcode9 버전 대응)
- KVC는 Objective-C 런타임의 기능이므로 이름 패턴 검색 정책 자체는 달라지는 점이 없다.
- 집합 메소드 프로퍼티는 모두 런타임에 이름을 통해서 액세스될 수 있어야 하므로
@objc
를 통해서 런타임에 노출되어야 한다. insert...
를 제외하고는 메소드 이름을 지을 때,...AtIndex(_:)
나...(atIndex:)
나 차이가 없다. 예를 들어 Swift 메소드명,objectInEmployeesAtIndex(_ index:)
나objectInEmployees(atIndex:)
모두 Objective-C에서는-objectInEmployeesAtIndex:
로 해석된다.- 하지만
insertObject(_:...)
은insert(object:...
)로 바꿔썼을 때 제대로 찾지 못한다.[^1] 즉, 파라미터 개수가 2개 이상인 경우는 명시적으로 밖으로 빼서 첫번째 파라미터를 숨겨야 한다. 이것은 Swift2 버전의 기본 메소드 이름 규칙인데, Objective-C 런타임에 적용된 이름 규칙이 오래전 버전인 것 같다. - 이름 해석 규칙이 애매할 수도 있고, Xcode 버전이 올라감에 따라 다시 다른 규칙이 적용될지 모른다. 따라서 가장 좋은 방법은
@objc()
변경자에서 Objective-C 런타임상의 이름을 같이 써주는게 좋다. (그러면 Swift 이름은 뭐 어떻게 짓든 상관없다.) - 그외 타입규칙
- 인덱스를 가리키는
NSUInteger
는Int
로 번역된다. - Element의 타입은 Any, AnyObject가 아닌 소속 클래스 타입을 그대로 쓰면 된다. 다만 어떤 접근자이든 옵셔널타입을 쓰지는 않는다. 옵셔널 타입으로 파라미터나 리턴타입을 정의하면 올바른 메소드 이름이 아닌 것으로 간주되니 주의할 것.
- 인덱스를 가리키는
다음 예에서 Foo 클래스의 words프로퍼티에 대한 집합 접근자/변경자는 다음과 같이 작성할 수 있다.
/// 프로퍼티 선언
/// Objective-C
@property (strong, nonatmoic) NSMutableArray<NSString *>* words;
/// Swift
/// setter에 의한 교체를 추적하려면 @objc dynamic var... 로 선언해야 하지만
/// 배열 프록시를 통해서 제어하는 경우, 굳이 해당 프로퍼티가 노출되지 않아도 된다.
var words:[String] = []
/// 집합 액세스 메소드
/// 꼭 필요하지는 않다.
/// Swift의 경우 @objc 를 붙이지 않았다면 이들을 구현해서
/// 배열 프록시로 동작하게 할 수 있다.
/// Objective-C
- (NSUInteger)countOfWords {
return [_words count];
}
- (NSString*)objectInWordsAtIndex:(NSUInteger)index
{
return [_words objectAtIndex:index];
}
/// Swift
@objc func countOfWords() -> Int {
return words.count
}
@objc func objectInWord(atIndex index: Int) -> String
{
return words[index]
}
/// 변경자
/// Objective-C
- (void)insertObject:(NSString*)object inWordsAtIndex:(NSUInteger)index
{
[_words insertObject:object atIndex:index];
}
- (void)removeObjectFromWordsAtIndex:(NSUInteger)index
{
[_words removeAtIndex:index];
}
/// swift
@objc(insertObject:inWordsAtIndex:)
func insert(words: String, at index:Int) {
words.insert(words, at:index)
}
@objc(removeFromWordsAtIndex:)
func remove(at index:Int) {
words.remove(at:index)
}