답변 요약
Key-Value Observing 이란 특정 키값의 변화를 감지하는 기능으로 객체 프로퍼티 변경사항을 다른 객체에게 알릴 수 있는 코코아 프레임워크에 내장되어 있는 패턴입니다.
부가 설명
KVC(Key-Value Coding)는 NSKeyValueCoding 프로토콜에 의해 동작한다. NSKeyValueCoding 의 특징은 컴파일 타임에 접근하는 것이 아닌 문자열을 사용하여 런타임에 객체의 프로퍼티에 접근하거나 값을 설정한다는 것이다. Key-value coding은 KVO(Key Value Observing), 코코아 바인딩, 코어 데이터 등 코코아 프레임워크에 자주 적용되는 원리이다.
또한 NSObject를 채택하고 있는 친구는 모두 NSKeyValueCoding 프로토콜을 채택하고 있다고 볼 수 있다. (런타임에 주소를 찾아가기 때문에 값타입에는 채택이 안되어 있는게 당연하기는 하다) 따라서 KVC를 이용하는 코코아 프레임워크 패턴은 NSObject를 채택해야한다.
import Foundation
// 관찰할 클래스 정의
class Person: NSObject {
@objc dynamic var name: String
init(name: String) {
self.name = name
}
}
// 옵저버 클래스 정의
class NameObserver: NSObject {
var observation: NSKeyValueObservation?
init(person: Person) {
super.init()
// KVO 설정
observation = person.observe(
\.name, // 'name' 프로퍼티에 대한 관찰
options: [.old, .new] // 이전 값과 새로운 값 둘 다 관찰
) { object, change in
if let oldName = change.oldValue, let newName = change.newValue {
print("Name changed from \(oldName) to \(newName)")
}
}
}
}
// KVO 사용 예시
let person = Person(name: "Alice")
let observer = NameObserver(person: person) // 옵저버 생성 및 등록
// 프로퍼티 값 변경
person.name = "Bob" // 출력: "Name changed from Alice to Bob"
KVO 사용방법은 크게 3가지 단계로 구성된다.
1. 관찰 당할 클래스(KVO는 KVC를 사용하기 때문에 NSObject를 채택해야함) 를 정의한다.
2. 관찰 할 클래스(옵저버) 정의 (같은 이유로 NSObject 채택)
3. 옵저버 생성 및 관찰할 클래스 등록
먼저 관찰 당할 클래스를 정의할 때는 @objc 어트리뷰트와 dynamic 모디파이어를 선언해야한다.
KVO는 KVC 매커니즘을 이용하고 KVC는 Objective-C 기반 동적 메커니즘이다. 이 문장을 순서대로 다시 바꾸면
"@objc(오브젝티브-C 이용해서) dynamic(동적으로) 처리할거임" 을 암시한다. (원래 Swift의 프로퍼티 처리 매커니즘은 정적 디스패치를 사용함 - 컴파일 타임 처리)
두번째에서는 KVO 설정을 위해 NSKeyValueObservation을 채택하고 있는 observation 프로퍼티를 선언해준다.
NSKeyValueObservationdms 는 observe 메서드가 호출될 때마다 리턴이 되면서 값의 변화를 관찰한다. 또한 자동으로 메모리를 관리하는 기능도 있어 관찰하는 객체가 해제되면 자동으로 참조도 해제된다. 만약 직접 참조를 해제하고 싶다면 observation?.invalidation()을 포함하는 메서드를 만들어 외부에서 호출해준다.
세번째는 이제 다차려진 밥상에 숟가락만 올린다.
관찰할 객체 타입을 선언한다. 그리고 옵저버 객체에다 관찰할 객체를 넣어줌으로써 객체 변화를 관찰한다.
그렇다면 위 코드에서 \. 은 무엇을 의미할까? 바로 keyPath를 의미한다. keyPath는 말그대로 특정 프로퍼티에 대한 경로로 직접적인 프로퍼티 이름을 사용하지 않고 프로퍼티에 접근할 수 있다. 특정 프로퍼티 이름을 통해 직접 접근하면 컴파일 타임에서 확인하여 동적인 바인딩이 불가능하지만 keyPath를 활용하면 동적인 바인딩이 되어 참조를 통해 변경사항을 업데이트 할 수 있기 때문에 KVO, SwfitUI 등에서 자주 사용된다.
import Foundation
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
let person = Person(name: "Alice", age: 25)
// Key Path 생성
let nameKeyPath = \Person.name // Person 객체의 `name` 프로퍼티에 대한 Key Path
let ageKeyPath = \Person.age // Person 객체의 `age` 프로퍼티에 대한 Key Path
// Key Path를 사용하여 프로퍼티 값 읽기
let personName = person[keyPath: nameKeyPath] // "Alice"
let personAge = person[keyPath: ageKeyPath] // 25
// Key Path를 사용하여 프로퍼티 값 설정
person[keyPath: nameKeyPath] = "Bob"
레퍼런스
'Flutter' 카테고리의 다른 글
The plugin "cloud_firestore" requires a higher minimum iOS deployment version than your application is targeting. 에러 (1) | 2024.07.24 |
---|---|
Swift에서 KeyPath 타입이란? (1) | 2024.06.24 |
위젯에 Key 를 사용해야하는 이유 (0) | 2024.06.12 |
| WWDC 16 | Concurrent Programming with GCD (0) | 2024.06.05 |
Flutter 화면전환에서 routes 와 onGenerateRoute 의 차이 (0) | 2024.06.04 |