[iOS] - KVO
KVO를 깊게 파보자.
KVO 패턴이란? : 한 객체의 프로퍼티가 변했음을 다른 객체에게 알리는 Cocoa 프로그래밍 패턴. 관찰하고자 하는 프로퍼티의 key path를 사용해 관찰할 수 있다.
KVO
KVO는 Key-Value Observing의 약자다. Key-Value observing은 한 객체에게 다른 객체의 프로퍼티가 변했음을 알리는 Cocoa 프로그래밍 패턴이다.
변화를 앱에서 논리적인 부분들이 서로 알리는데 유용하다. 논리적으로 분리되어 있는 부분들은 model과 view와 같은 것들이 속할 수 있다.
Key-value observing은 NSObject
을 상속받은 클래스에서만 사용할 수 있다.
NSObject? NSObject는 대부분의 Objective-C 클래스 계층의 root 클래스다. NSObject 클래스를 상속받은 자식 클래스들은 런타임 시스템의 기본 인터페이스와 Objective-C 객체로서 동작할 수 있는 능력을 상속받는다.
KVO를 사용할 프로퍼티 표시하기
KVO는 어떤 객체의 프로퍼티가 변했음을 다른 객체에 알리는 프로그래밍 패턴이라고 했다. 이 프로퍼티가 변했음을 알기 위해서는 프로퍼티를 관찰해야 한다.
KVO로 관찰하고 싶은 프로퍼티를 @objc
attribute와 dynamic
modifier를 함께 붙여서 표시한다.
class MyObservableObject: NSObject {
// 관찰할 프로퍼티
@objc dynamic var level = 1
func levelUp() {
level += 1
}
}
위 코드에서는 MyObservableObject
클래스를 정의했고, 이 클래스는 level
이라는 관찰할 수 있는 프로퍼티를 가지고 있다.
Observer 정의하기
위에서 변화가 일어나는지 관찰할 프로퍼티를 정했다. 이제 이 프로퍼티가 변했음을 알림받을 관찰자, observer가 필요하다.
observer 클래스의 인스턴스는 하나 이상의 프로퍼티에서 생긴 변화에 대한 정보를 관리한다. Observer를 생성할 때, 내가 관찰하고 싶은 프로퍼티를 나타내는 key path로 observe(_:options:changeHandler:)
메서드를 호출해서 관찰을 시작한다.
class MyObserver: NSObject {
@objc var observableObject: MyObservableObject
var observation: NSKeyValueObservation?
init(object: MyObservableObject) {
self.observableObject = object
super.init()
// observe(_:options:changeHandler:)는 self.observe~ 로 호출하는 메서드라 이 전에 super.init()을 호출해야 한다.
self.observation = observe(
\.observableObject.level,
options: [.old, .new],
changeHandler: { object, change in
print("level was : \(change.oldValue!), level is now : \(change.newValue!)")
})
}
}
위 MYObserver
클래스에서는 관찰할 객체 observableObject
를 프로퍼티로 가지고 있다. 그리고 observe(_:options:changeHandler:)
메서드에서 관찰할 객체의 level
프로퍼티가 변할 때, 변하기 이전의 값과 변하고 난 값을 모두 변한 내용을 담는 딕셔너리에 담을 수 있게 설정했다.
changeHandler
에서는 change
의 oldValue
와 newValue
프로퍼티를 사용하고 있다. change
는 NSKeyValueObservedChange 타입으로, indexes
, isPrior
, kind
, newValue
, oldValue
와 같은 프로퍼티를 가지고 있어서 내가 관찰하는 프로퍼티가 어떻게 변했는지 확인할 수 있게 해준다.
만약 프로퍼티가 어떻게 변했는지 알 필요가 없다면 observe(_:options:changeHandler:)
의 options
파라미터를 빼도 된다. 이 options
를 빼면 프로퍼티의 변하기 이전 값과 변하고 난 값을 저장하지 않게 되기 때문에 change
의 oldValue
와 newValue
가 nil
이 된다.
참고
Observer 클래스도 NSObject
를 상속해야 한다는 것을 잊지 말자.
Observer와 관찰할 프로퍼티 연결하기
우리는 위에서
- 관찰할 프로퍼티를 설정하고
- 프로퍼티를 관찰하는 Observer 클래스를 생성했다.
따라서 우리가 구현한 observer 클래스의 이니셜라이저에 관찰할 객체를 전달해서 관찰할 프로퍼티와 observer를 연결하면 된다.
let levelObject = MyObservableObject()
let observer = MyObserver(object: levelObject)
프로퍼티가 변했을 때
KVO를 사용하는 객체들(위에서는 LevelObject
)은 프로퍼티의 값이 변할 때 observer에 변화를 알린다.
levelObject.levelUp()
// 자동으로 observer의 change handler가 호출된다.
// level was : 1, level is now : 2
KVO와 Struct, Enum
위에서 설정한 변화를 관찰할 수 있는 프로퍼티는 클래스(objective-C 타입)이어야 하기 때문에, 구조체, 열거형은 KVO로 관찰할 수 없다.
전체 코드
import Foundation
class MyObservableObject: NSObject {
// 관찰할 프로퍼티
@objc dynamic var level = 1
func levelUp() {
level += 1
}
}
class MyObserver: NSObject {
@objc var observableObject: MyObservableObject
var observation: NSKeyValueObservation?
init(object: MyObservableObject) {
self.observableObject = object
super.init()
// observe(_:options:changeHandler:)는 self.observe~ 로 호출하는 메서드라 이 전에 super.init()을 호출해야 한다.
self.observation = observe(
\.observableObject.level,
options: [.old, .new],
changeHandler: { object, change in
print("level was : \(change.oldValue!), level is now : \(change.newValue!)")
})
}
}
let levelObject = MyObservableObject()
let observer = MyObserver(object: levelObject)
levelObject.levelUp()
// 자동으로 observer의 change handler가 호출된다.
// level was : 1, level is now : 2
- 참조
- https://developer.apple.com/documentation/swift/cocoa_design_patterns/using_key-value_observing_in_swift