[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) } } }
참고 동영상
마무리
클로져 타입으로 변경되면서 오히려 코드가 많이 간결해 진것 같아서 마음에 들며, 활용도 면에서도 좋아진 것 같다. 실제 프로젝트에 적용해 보아야겠다.끝.
댓글
댓글 쓰기