[iOS] KVC, KVO, KeyPath 알아보기
안녕하세요. 이번에 Swift 버전의 KVC(Key Value Coding), KVO(Key Value Observing) 그리고 KeyPath를 알아보려고 한다.
참고
Objc KVO : https://xodhks0113.blogspot.com/2019/02/ios-kvokey-value-observing.html
Objc KVC : https://xodhks0113.blogspot.com/2019/01/ios-kvckey-value-coding.html
Sample App : http://github.com/kimjiwook/KVCDemo
Swift 4.x 이상부터 KeyPath 접근하는 방식이 변경되었는데, 과거 버전에서 Property를 String 값으로 쓰다보니 오타 발생시 앱이 죽는현상때문에 꺼려지는 방식이였다면, 이번에는 Property 참조하는 방식으로 제공이 되어 생각보다 편하게 사용할 수 있는 것 같다.
KVO 객체 만들기
Objc 접근 가능해야하며, NSObject 상속받아야한다.+ dynamic 키워드를 사용해야 KVO 를 사용할 수 있다.
// Swift KVC, KVO, KeyPath 테스트 하기
@objcMembers class KVCObject: NSObject { // KVO 시 NSObject 상속 필요함. (KVC 에는 상관없음)
dynamic var name = "" // dynamic 을 사용해야 실시간 변화 감지가 가능함.
dynamic var age = 0
}
KVO, KVC
NSKeyValueObservation 변수를 선언하여, 클로져 타입으로 선언하여 사용한다.keyPath \.name 형식으로 KVC 사용이 가능하다.
class ViewController: UIViewController {
let kvcObject1 = KVCObject()
// 옵져빙 관리할 Array 객체 (deInit 할때 비워주는 로직있으면 됨.)
private var observerList: [NSKeyValueObservation] = []
// 옵져빙 할 객체들.
var kvcObject1Name:NSKeyValueObservation? = nil
var kvcObject1Age:NSKeyValueObservation? = nil
// KVO 옵져버 셋팅 (keyPath에 맞춰서 자료형은 맞춰짐)
kvcObject1Name = kvcObject1.observe(\.name, options: [.old, .new], changeHandler: { (object, change) in
print("Name old Value(String)\(change.oldValue) new Value(String)\(change.newValue)")
// 추가적으로 반영할 내용.
})
// KVC KeyPath 방식
kvcObject1[keyPath: \.name] = textField.text!
.........
}
전체 소스코드
import UIKit
// Swift KVC, KVO, KeyPath 테스트 하기
@objcMembers class KVCObject: NSObject { // KVO 시 NSObject 상속 필요함. (KVC 에는 상관없음)
dynamic var name = "" // dynamic 을 사용해야 실시간 변화 감지가 가능함.
dynamic var age = 0
}
class ViewController: UIViewController {
let kvcObject1 = KVCObject()
// 옵져빙 관리할 Array 객체 (deInit 할때 비워주는 로직있으면 됨.)
private var observerList: [NSKeyValueObservation] = []
// 옵져빙 할 객체들.
var kvcObject1Name:NSKeyValueObservation? = nil
var kvcObject1Age:NSKeyValueObservation? = nil
// 옵져빙 할때 표현하려고
@IBOutlet weak var lbResult: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.setObserver()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// deInit 때 가지고있는 Ovserver 제거
observerList.removeAll()
}
func setObserver() {
// 옵져버 셋팅 (keyPath에 맞춰서 자료형은 맞춰짐)
kvcObject1Name = kvcObject1.observe(\.name, options: [.old, .new], changeHandler: { (object, change) in
print("Name old Value(String)\(change.oldValue) new Value(String)\(change.newValue)")
self.lbResult.text = "\(change.newValue ?? "") \(object.age)"
})
// 관리 array 추가
observerList.append(kvcObject1Name!)
kvcObject1Age = kvcObject1.observe(\.age, options: [.old, .new], changeHandler: { (object, change) in
print("Age old Value(Int)\(change.oldValue) new Value(Int)\(change.newValue)")
self.lbResult.text = "\(object.name) \(change.newValue ?? 0)"
})
// 관리 array 추가
observerList.append(kvcObject1Age!)
}
/// 텍스트뷰 체인지 될때마다.
/// - Parameter sender:
@IBAction func textFieldChanged(_ sender: Any) {
let textField:UITextField = sender as! UITextField
kvcObject1[keyPath: \.name] = textField.text!
}
@IBAction func stepperValueChanged(_ sender: Any) {
let stepper:UIStepper = sender as! UIStepper
kvcObject1[keyPath: \.age] = Int(stepper.value)
}
}
}
참고 동영상
마무리
클로져 타입으로 변경되면서 오히려 코드가 많이 간결해 진것 같아서 마음에 들며, 활용도 면에서도 좋아진 것 같다. 실제 프로젝트에 적용해 보아야겠다.끝.
댓글
댓글 쓰기