KVO(Key-Value Observing)
KVO는 Key-Value-Observing의 약자입니다.
약자에서 예상할 수 있듯이 KVO는 Key를 통해 Value 값을 Observing(관찰)하는 것을 말합니다.
KVO 예시
class People {
var age: Int
var name: String
init() {
self.age = 15
self.name = "철수"
}
}
People이라는 클래스를 하나 만들었습니다. 여기서 저희는 사람의 나이가 변할 때마다 어떤 특정 조건을 통과하는지 검사하고 싶다고 하겠습니다. 그렇다면 age 프로퍼티의 값(value)가 변화하는 것을 검사해주면 될 것입니다. 여기서 KVO를 사용하면 age 프로퍼티의 값 변화를 감지할 수 있습니다. KVO를 어떻게 사용하는지 한번 알아보겠습니다.
- KVO 활용 규칙
- NSObject 상속
- @Objc dynamic
Options
.old, .new, .priod, .initial
KVO를 사용할 때 Observe 메서드를 통해서 options 파라미터를 전달하게 되는데 options에 전달하는 배열들이 어떤 것들이 있는지 한번 살펴보겠습니다.
- [.old, .new]
class People: NSObject {
@objc dynamic var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
let people = People(age: 15, name: "철수")
people.observe(\.age, options: [.old, .new]) { object, changed in
print("Current Object : \(object)")
print("Old Value Before Changed : \(changed.oldValue)")
print("New Value After Changed : \(changed.newValue)")
print("===========구분점============")
}
people.age = 16
/*
Current Object : <__lldb_expr_41.People: 0x600000218040>
Old Value Before Changed : Optional(15)
New Value After Changed : Optional(16)
===========구분점============
*/
- [.old, .new, .initial]
initial은 초기화시에도 이 observer handler를 호출할거냐...??라고 물어보는 option이라고 보면 될 것 같습니다.
class People: NSObject {
@objc dynamic var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
let people = People(age: 15, name: "철수")
people.observe(\.age, options: [.old, .new, .initial]) { object, changed in
print("Current Object : \(object)")
print("Old Value Before Changed : \(changed.oldValue)")
print("New Value After Changed : \(changed.newValue)")
print("===========구분점============")
}
people.age = 16
/*
Current Object : <__lldb_expr_37.People: 0x6000002003e0>
Old Value Before Changed : nil
New Value After Changed : Optional(15)
===========구분점============
Current Object : <__lldb_expr_37.People: 0x6000002003e0>
Old Value Before Changed : Optional(15)
New Value After Changed : Optional(16)
===========구분점============
*/
- [.old, .new, .prior]
class People: NSObject {
@objc dynamic var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
let people = People(age: 15, name: "철수")
people.observe(\.age, options: [.old, .new, .prior]) { object, changed in
print("Current Object : \(object)")
print("Old Value Before Changed : \(changed.oldValue)")
print("New Value After Changed : \(changed.newValue)")
print("===========구분점============")
}
people.age = 16
/*
Current Object : <__lldb_expr_33.People: 0x600000204060>
Old Value Before Changed : Optional(15)
New Value After Changed : nil
===========구분점============
Current Object : <__lldb_expr_33.People: 0x600000204060>
Old Value Before Changed : Optional(15)
New Value After Changed : Optional(16)
===========구분점============
*/
prior option에서 이전값인지 아니면 변경된 값이 알고 싶다면 isPrior 이라는 속성을 사용하면 됩니다.
print(object.age, changed.isPrior)
// 15 true
// 16 false
그런데 사용하다 보니 Swift에는 속성 감시자 역할을 하는 willset, didset이 있는데 굳이 Obejctive-C의 방식을 사용할 필요가 있을까요? 그렇다면 이번엔 KVO를 왜 사용하는지 한번 알아보겠습니다.
KVO를 사용하는 이유
Swift의 속성 감시자와 차이점은 속성 감시자는 클래스 내부에 구현해야하고 KVO는 클랙스의 외부에 구현한다는 차이점이 있습니다.
- Swift-속성 감시자(willSet, didSet)
- 내부 구현
- KVO
- 외부 구현
KVO 장, 단점
- 외부 구현된다는 것의 장점
- 두 객체간의 동기화를 달성 가능, 위에서 언급했듯이 Model과 View와 같이 논리적으로 분리된 파트간의 변경사항을 전달
- 객체의 구현을 변경하지 않고 내부 객체의 상태 변화에 대응할 수 있음.(SDK 객체의 경우)
- 객체의 내부 코드를 일체 변경하지 않다는 말과는 다름 예를 들어 NSObject를 상속받는 다던지, 변경을 감지할 속성에 @objc dynamic을 붙힌다던지의 행위가 필요하지만 일일히 코드를 수정하거나 추가하지 않고도 외부에서 해당 속성의 변경 사항을 감지할 수 있다는데 장점이 있다는 말인 것 같다.
- 외부 구현되는 것의 단점
- NSObject 상속해야함 == Objective-C 런타임에 의존하게 됨
Swift에서 실제 활용 예시
Mapkit을 사용해봤다면 좌표를 Custom하거나 활용하기 위해 coordinate 속성을 사용해봤을 것입니다. coordinate의 swift 공식문서를 확인해보면 다음과 같이 설명되어있습니다.
이 속성의 구현은 KVO(Key-Value-Observing)를 준수해야 합니다.
coordinate | Apple Developer Documentation
The center point (specified as a map coordinate) of the annotation.
developer.apple.com
class CustomAnnotation: NSObject, MKAnnotation {
// This property must be key-value observable, which the `@objc dynamic` attributes provide.
@objc dynamic var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
var imageName: String?
init(title: String, coordinate: CLLocationCoordinate2D) {
self.title = title
self.coordinate = coordinate
}
}
MKAnnotation을 상속받고 coordinate 프로퍼티를 사용할 때는 위에서 KVO 프로그래밍 가이드에 따라 다음과 같이 사용할 것이 준수되어집니다.
마지막으로, 외부에서 특정 객체의 속성을 감시할 수 있다는 것은 알겠고 willset, didset과의 차이도 알아봤지만 대체 언제 사용할까? 라는 의문과 함께 선뜻 와닿지는 않았는데 이렇게 활용되어지는 예시가 있다는 것 정도로 알고 있으면 좋을 것 같습니다.
'iOS > Swift' 카테고리의 다른 글
Swift - @objc dynamic property (2) | 2023.12.29 |
---|---|
Swift - Swift에서는 왜 문자열을 Subscript로 접근할 수 없을까? (1) | 2023.12.12 |
Swift - Method Swizzling (1) | 2023.12.01 |
ARC(Automatic Reference Counting) (0) | 2023.09.04 |
Swift - Protocol (0) | 2023.08.07 |