Swift4 에서 키밸류 옵저빙에서 몇 가지 변화가 있었는데, 옵저버 중심의 콜백이 아니라, 타깃 중심의 핸들러 기반 API가 추가되었다. 그리고 몇 가지 편의 클래스들이 추가되었다. 먼저 observe(_:options:changeHandler:)
메소드가 추가되었다. 이 메소드는 addObserver:...
를 대신해서 옵저버 객체가 아닌 변경이 발생했을 때 호출될 핸들러를 지정하는 것이다. 전체 시그니처는 대략 다음과 같다. (아직까지 NSObject
나 NSKeyValueObserving
문서가 업데이트되지 않아서 해당 API에 대한 페이지가 존재하지는 않는다.)
func observe<Value>(_ keyPath: KeyPath<Self, Value>
options: NSKeyValueObseringOptions=[]
handler: @escaping (Self, NSKeyValueObservedChange) -> Void)
-> NSKeyValueObservation
일단 NSKeyValueObservedChange
와 NSKeyValueObservation
의 두 개의 새로운 타입이 추가되었다.
NSKeyValueObservedChange
이전의 옵저버 메소드는 변경사항의 디테일을 Dictionary<NSKeyValueChangeKey, Any>?
타입으로 주었다. 이 타입을 메소드 내에서 사용하는데에는 치명적인 문제가 있었다.
- 일단 사전 자체가 옵셔널 타입인데다가, 사전은 키로 값을 찾으면 다시 옵셔널 값을 내놓는다. 따라서 이중 옵셔널의 문제가 발생한다.
enum
값들은NSDictionary
로 들어가면서 죄다NSNumber
로 변환되고 다시 이것이 브릿징되면서Int
값이 되어버린다. 따라서NSKeyValueChange(rawValue:)
를 사용해서 이것을 다시.setting
과 비교하거나 하는 삽질을 해야 한다.
그래서 코드가 엄청나게 지저분해져야 했었다. NSKeyValueObservedChange
는 변경사항의 세부 사항을 프로퍼티로 하는 타입으로 만들었다. 그래서 다음과 같이 깔끔하게 정리된다.
kind
:NSKeyValueChange
값newValue?
: 이전 값oldValue?
: 이후 값isPrior
: 변경 전에 발생하는 통지인지 여부indexes?
: to-many 변경에서 변경이 발생한 위치
NSKeyValueObservation
옵저빙 메소드는 NSKeyValueObservation
타입의 토큰을 리턴한다. 만약 이 토큰이 릴리즈되면 옵저빙이 중단되는 식으로 동작한다. 따라서 옵저버든 어떤 컨텍스트에서 이 값을 참조할 이름을 마련해두었다가 옵저빙을 끝내려는 시점에 nil
로 변경하는 식으로 참조를 제거할 수 있다.
다음 예제는 흔한 to-many 프로퍼티에 대한 옵저빙을 수행하는 코드이다. 코드가 매우 간단해졌음을 확인할 수 있다.
아래 예제 코드에서 switch 문에서 매치하는 대상은 change
(NSKeyValueObservedChange
)가 아니라 change.kind
(NSKeyValueChange
값)이 맞습니다. 한병호 님의 지적으로 수정하였습니다. 다시 한 번 한병호님께 감사드립니다.
import Foundation
class Foo: NSObject {
@objc var moo: [String] = []
@objc(insertObject:inMooAtIndex:)
func insert(mo: String at index:Int) { moo.insert(mo, at:index) }
@objc(removeObjectFromMooAtIndex:)
func remove(at index: Int) { moo.remove(at: index) }
}
let f = Foo()
f.moo = ["one", "two", "three"]
var token: NSKeyValueObservation? = f.observe(\Foo.moo){ _, change in
switch change.kind {
case .setting: print("moo가 교체되었다")
case .insertion: print("\(change.indexes!.startIndex)에 삽입")
case .removal: print("\(change.indexes!.startIndex)에서 제거")
case .replacement: print("\(change.indexes!.startIndex)에서 교체")
default:
break
}
}
let arr = f.mutableArrayValue(forKey: #keyPath(Foo.moo))
arr.add("four")
arr.removeObject(at: 1)
arr.replaceObject(at: 2, with: "hello")
token = nil
예제가 잘못 된 것 같습니다.
change in
switch change {
case .setting: print(“moo가 교체되었다”)
case .insertion: print(“(change.indexes!.startIndex)에 삽입”)
case .removal: print(“(change.indexes!.startIndex)에서 제거”)
case .replacement: print(“(change.indexes!.startIndex)에서 교체”)
default:
break
}
}
에서 switch change가 아니라
switch change.kind 여야 하지 않나요?
지적 감사합니다.
change는 NSKeyValueObservedChange 이며, 실제로 switch 문에서 매칭하는 값은 NSKeyValueChange 값이므로, 말씀하신 바와 같이 change.kind 로 사용하는 것이 맞습니다.
감사합니다.
댓글이 닫혔습니다.