[Swift] Key Value Coding(KVC), Key Value Observing(KVO)
- -
Key의 개념
- Swift의 KeyPath, KVC, KVO 자꾸나오는 Key의 개념은?
- 문자열(Key)를 의미
- 이 key값을 통해 instance의 property value에 간접적으로 접근하게 해주는 Objective-C에서 나온 개념
KVC, KVO가 생기게 된 이유
Objective - C로 개발하던 시절에는 MVC 패턴이 정답이라고 생각하던 시기
MVC에서 가장 중요한것은 Model과 View의 Sync를 맞추는 일
여기서 Controller는 2가지 역할을 수행하여야 함
- Model의 변화를 View에 반영
- View의 Interaction을 Model에 반영
View나 Model에서 Action이 일어날 때마다 이 2가지 과정을 거쳐
일일이 상태값을 업데이트 해주고 View와 Model을 동기화 해줘야하는 문제에 직면
이를 나누지 말고 묶어서 서로 업데이트 해주면 되지 편리하지 않을까 ? 라는 관점에서 나온 것
Key Value Coding
- 객체의 값을 직접 가져오지 않고 Key 또는 KeyPath를 이용해서 간접적으로 데이터를 가져오거나 수정하는 방법
- Objective-C의 것이기 때문에 NSObject가 지고 있으므로 NSObject의 서브클래스여야 가능
- KVC는 Objective-C Dynamic Dispatch 특성에 의존하므로 프로퍼티 앞에 @objc 어노테이션을 붙여서 사용
- key는 String
- key는 일반적으로 객체에 정의된 accessor method 또는 인스턴스 변수의 "이름"
- key는 특정 규칙을 따름
- ASCII로 인코딩 되어야 함
- 소문자로 시작해야함
- 공백이 없어야함
KVC는 NSKeyValueCoding 을 준수
NSKeyValueCoding | Apple Developer Documentation
NSKeyValueCoding에는 다양한 getter와 setter가 있는데 가장 많이 사용하는 4가지의 사용법을 알아보자
get method
set method
KVC 예제
- objctive-C 에서 생긴 개념이기에 NSObject를 상속하고 프로퍼티에 @objc 어노테이션을 사용
ForKey
class Point: NSObject {
@objc var x: Double
@objc var y: Double
}
let point = Point(x: 0, y: 0)
point.value(forKey: "x") // 0
point.setValue(5, forKey: "x")
point.value(forKey: "x") // 5
key값을 이용해 value에 접근할 수 있다. 그렇다면 keypath는 언제 쓸까 ?
ForKeyPath
객체안에 객체가 들어가 있을 때 사용한다. Key로는 객체 내부의 프로퍼티만 접근가능하기 때문
class Point: NSObject {
@objc let x: Double
@objc let y: Double
}
class Size: NSObject {
@objc var width: Double
@objc var height: Double
}
class Rect: NSObject {
@objc var point: Point
@objc var size: Size
@objc var min: Double
@objc var max: Double
}
let rect = Rect(
point: .init(x: 1, y: 2),
size: .init(width: 3, height: 4),
min: 5,
max: 6
)
rect.value(forKey: "point") // <Key.Point: 0x60000183b060>
위와 같이 선언되어 있을 때 Rect객체의 Point의 x값을 확인하려면 KeyPath를 이용할 수 밖에 없다.
rect.value(forKeyPath: "point.x") // 1
rect.value(forKeyPath: "point.y") // 2
rect.value(forKeyPath: "size.width") // 3
rect.value(forKeyPath: "size.height") // 4
rect.value(forKeyPath: "min") // 5
rect.value(forKeyPath: "max") // 6
rect.setValue(100, forKeyPath: "point.x") // rect.point.x = 100
rect.setValue(15, forKeyPath: "min") // rect.min = 15
rect.setValue(Point(x: 10, y: 20), forKey: "point") // rect.point = Point(10, 20)
KVO - Key Value Observing
- NSObject를 상속하고 프로퍼티에 @objc dynamic 을 붙혀 사용
class MyPoint: NSObject {
@objc dynamic var x: Double
@objc dynamic var y: Double
}
let myPoint = MyPoint(x: 0, y: 0)
myPoint의 x,y의 값이 변경될 때마다 특정 작업을 하기위해 observing 하려면 observe 함수를 정의한다.
var observer: NSKeyValueObservation? = myPoint.observe(\\.x, options: [.old, .new]) { myPoint, change in
print("myPoint.x의 현재 값 \\(change.oldValue!)가 \\(change.newValue!)로 변경")
}
myPoint.x = 2 // 위 코드로 인해 "myPoint.x의 현재 값 0.0가 2.0로 변경" 출력
observer = nil
- myPoint의 프로퍼티 x에 observer 추가
- 프로퍼티가 변경됨
- observer의 changeHandler 호출
- handler 내의 클로저 실행
KeyPath
- 어떤 값을 observe할 것인지를 정함
- 위 예시에선 x를 observe 하기 때문에 x 이외의 값은 아무리 바꾸어도 observer가 실행되지 않는다.
let myPoint = MyPoint(x: 0, y: 0)
var observer: NSKeyValueObservation? = myPoint.observe(\\.x, options: [.old, .new]) { myPoint, change in
print("myPoint.x의 현재 값 \\(change.oldValue!)가 \\(change.newValue!)로 변경")
}
myPoint.x = 2 // myPoint.x의 현재 값 0.0가 2.0로 변경
myPoint.y = 1 // 아무일 없음
myPoint.y = 2 // 아무일 없음
myPoint.y = 3 // 아무일 없음
observer = nil
options
old, new
new와 old는 willSet, didSet의 oldValue, newValue를 생각하면 됨
old, new만 주었을 때는 초기화 후 x의 값에 새로운 값을 주었을 때만 handler 호출
var myPoint = MyPoint(x: 0, y: 0)
var observer: NSKeyValueObservation? = myPoint.observe(\\.x, options: [.old, .new]) { myPoint, change in
print(change.oldValue, change.newValue)
// Optional(0.0) Optional(1.0)
}
myPoint.x = 1
observer = nil
initial
초기화 시에도 호출 할것인지를 묻는 것
initial을 주면 초기화 시에도 handler 호출
var myPoint = MyPoint(x: 0, y: 0)
var observer: NSKeyValueObservation? = myPoint.observe(\\.x, options: [.initial, .old, .new]) { myPoint, change in
print(change.oldValue, change.newValue)
// nil Optional(0.0)
// Optional(0.0) Optional(1.0)
}
myPoint.x = 1
observer = nil
prior
이전 상태의 값과 현재 상태의 값을 둘 다 주는 것
var myPoint = MyPoint(x: 0, y: 0)
var observer: NSKeyValueObservation? = myPoint.observe(\\.x, options: [.prior, .old, .new]) { myPoint, change in
print(change.oldValue, change.newValue)
// Optional(0.0) nil <- myPoint.x = 1 이 실행되기 전의 상태 값 newValue가 없기에 nil
// Optional(0.0) Optional(1.0)
}
myPoint.x = 1
observer = nil
initial을 안주어서 초기화 상태 말고 x가 1로 변경될 때 변경되기 전의 상태 값과 변경되고 난 후의 상태값 을 호출
initial을 주게되면 ?
var myPoint = MyPoint(x: 0, y: 0)
var observer: NSKeyValueObservation? = myPoint.observe(\\.x, options: [.initial, .prior, .old, .new]) { myPoint, change in
print(change.oldValue, change.newValue)
// nil Optional(0.0)
// Optional(0.0) nil
// Optional(0.0) Optional(1.0)
}
myPoint.x = 1
observer = nil
initial은 prior을 벗어난것을 확인 할 수 있다.
KVO의 장단점
장점
- Model, View 두 객체간의 동기화 가능
- 외부 라이브러리나 다른 사람이 작성한 객체의 경우 코드 변경 없이 내부의 값 관찰 가능
- 단 해당 객체가 NSObject를 무조건 상속받고 있어야 하며 @objc dynamic도 붙어 있어야 함 …
장점맞나 ?
- KeyPath를 사용하여 프로퍼티를 관찰하므로 중첩된 프로퍼티도 관찰 가능
- ex) rect.point.x
단점
- NSObject를 상속받아야 함
- object-c 런타임에 의존하게 됨
- static dispatch가 dynamic dispatch가 된다는 단점
번외
setValue시 this class is not key value coding-compliant for the key 에러가 뜨는 경우가 있는데
이 경우는 말 그대로 key값으로 value를 변경할 수 없을 때 뜨는 에러인데
object.setValue(value, key)일 때 보통 아래 경우라 볼 수 있다.
- object가 nil
- class Point: NSObject { @objc var x: Double @objc var y: Double } let point: Point? point.setValue(1, "x")
- key값이 잘못 됨
- class Point: NSObject { @objc var x: Double @objc var y: Double } let point = Point(x: 0, y: 0) point.setValue(1, "XX")
- value 타입이 맞지 않음
- class Point: NSObject { @objc var x: Int @objc var y: Int } let point = Point(x: 0, y: 0) point.setValue(1.4, "x")
- let 으로 선언되어 있음
- class Point: NSObject { @objc let x: Double @objc let y: Double } let point = Point(x: 0, y: 0) point.setValue(1, "x")
참고
Using Key-Value Observing in Swift | Apple Developer Documentation
NSKeyValueObserving | Apple Developer Documentation
Key-Value Coding Programming Guide: About Key-Value Coding
NSKeyValueObservingOptions | Apple Developer Documentation
[iOS] KVO(Key-Value Observing)
Key-Value Coding(KVC) / KeyPath in Swift
Key-Value Observing(KVO) in Swift
'iOS > Swift' 카테고리의 다른 글
[Swift] GCD, Async/Sync 알아보기 비동기(Asynchronous)란 말과 Concurrency(동시성)란 말이 같은 말인가? (1) | 2024.01.22 |
---|---|
[Swift] RxSwift, Combine 원리 이해하기 (0) | 2024.01.20 |
[Swift] 정규표현식 (1) | 2024.01.12 |
[Swift] Swift에서의 메모리 관리 (0) | 2023.12.28 |
[Swift] Copy-on-Write (1) | 2023.12.28 |
소중한 공감 감사합니다