[iOS] KVC, KVO, KeyPath 알아보기

  [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)
    }
}

}

참고 동영상



  마무리

클로져 타입으로 변경되면서 오히려 코드가 많이 간결해 진것 같아서 마음에 들며, 활용도 면에서도 좋아진 것 같다. 실제 프로젝트에 적용해 보아야겠다.

끝.

댓글