본문 바로가기
iOS/설명

[iOS] KVO (Key-Value Observing)

by Sky Titan 2022. 4. 16.
728x90
 

Key-Value Observing(KVO) in Swift

안녕하세요 :) Zedd입니다. 오늘은 KVO에 대해서 공부! # KVO - Key-Value Observing의 약자 - 객체의 프로퍼티의 변경사항을 다른 객체에 알리기 위해 사용하는 코코아 프로그래밍 패턴 - Model과 View와 같

zeddios.tistory.com

 

Apple Developer Documentation

 

developer.apple.com

KVO (Key-Value Observing)

  • Key-Value Observing의 약자
  • 객체의 특정 프로퍼티의 변경사항을 다른 객체에 알리기 위해 사용하는 코코아 프로그래밍 패턴
  • Model가 View와 같이 논리적으로 분리된 파트 간의 변경사항을 전달하는데 유용하다.
  • NSObject를 상속한 클래스에서만 KVO를 사용할 수 있다.
  • didSet, willSet과 상당히 유사하다.
  • 장점:
    • 두 객체 간의 동기화가 가능
    • 특정 객체의 내부구현을 바꾸지 않고 외부에서 프로퍼티의 변경을 관찰할 수 있다.
    • KeyPath를 사용하여 nested 프로퍼티도 관찰이 가능하다.
    • 따로 옵저버를 remove할 필요없이 시스템이 알아서 removeObserver를 호출한다.
  • 단점:
    • NSObject를 상속해야되기 때문에 Objective-C 런타임에 의존하게 된다.

 

다른 객체에서 observeValue 함수로 특정 프로퍼티를 observing하는 법

  • Observed 되는 객체에 NSObject를 상속한 후 해당 프로퍼티에 @objc attribute와 dynamic modifier를 붙여준다.
  • Observer 객체에 NSObject를 상속한 후 func observeValue(forKeyPath:) 메소드를 오버라이드한다.
import UIKit
import Foundation

class MyObject: NSObject {
    @objc dynamic var observedValue: Int = 0
    
    
}

class MyObserver: NSObject {
    
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "observedValue", let newValue = change?[.newKey] as? Int {
            print("new \(newValue)")
        }
    }
}

let observer = MyObserver()

let object = MyObject()

object.addObserver(observer, forKeyPath: "observedValue", options: .new, context: nil)

object.observedValue = 3
/*
 new 3
 */

 

changeHandler로 observe하는 법

  • observe함수를 호출해서 keyPath, options, changeHandler를 지정해준다.
import UIKit
import Foundation

class MyObject: NSObject {
    @objc dynamic var observedValue: Int = 0
}

let object = MyObject()


object.observe(\.observedValue, options: [.new, .old], changeHandler: { object, change in
    print("old \(change.oldValue), new \(change.newValue)")
})
object.observedValue = 3
/*
 old Optional(0), new Optional(3)
 */

 

options

  • options에는 .new, .old, .initial, .prior의 4가지 값들이 있다.
    • old, new는 말그대로 변경되기 전의 값, 변경된 후의 값을 가져온다는 것을 의미한다. (만약 이 옵션들을 지정하지 않으면 변경된 값들을 확인할 수가 없다. -> nil이 출력됨)
    • initial은 초기값을 지정할 때도 observer가 호출되게 하고 싶을 때 추가하면 된다
    • prior은 before, after 각각의 변화가 하나의 단일 notification이 아니라 분리된 notification으로 각각 observer로 보내진다.
      willChange로서의 기능이 필요할 때 사용할 수 있다.

 

1. .initial

import UIKit
import Foundation

class MyObject: NSObject {
    @objc dynamic var observedValue: Int = 0
}

let object = MyObject()


object.observe(\.observedValue, options: [.new, .old, .initial], changeHandler: { object, change in
    print("old \(change.oldValue), new \(change.newValue)")
})
object.observedValue = 3
object.observedValue = 5
/*
 old nil, new Optional(0)
 old Optional(0), new Optional(3)
 old Optional(3), new Optional(5)
 */

2. .prior

import UIKit
import Foundation

class MyObject: NSObject {
    @objc dynamic var observedValue: Int = 0
}

let object = MyObject()


object.observe(\.observedValue, options: [.new, .old, .prior], changeHandler: { object, change in
    print("old \(change.oldValue), new \(change.newValue)")
})
object.observedValue = 3
object.observedValue = 5
/*
 old Optional(0), new nil
 old Optional(0), new Optional(3)
 old Optional(3), new nil
 old Optional(3), new Optional(5)
 */

 

UIView의 center값을 observe하기

  • redView가 움직일 때, center값을 추적해서 blueView도 같이 움직이게 한다.
class ViewController: UIViewController {
    
    @IBOutlet weak var redView: DraggableView! //touch로 drag해서 움직일 수 있는 view (center값을 바꾼다)
    var blueView: UIView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        
        let blueView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        blueView.backgroundColor = .blue
        view.addSubview(blueView)
        
        self.blueView = blueView
        
        redView.addObserver(self, forKeyPath: "center", options: .new, context: nil)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        blueView?.center = CGPoint(x: view.frame.width / 2, y: view.frame.height / 2)
    }
    
}
extension ViewController {
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "center", let center = change?[.newKey] as? CGPoint {
            print("center \(center)")
            
            blueView?.center = center
        }
    }
}

728x90

'iOS > 설명' 카테고리의 다른 글

[iOS] 레이아웃 update cycle  (0) 2022.04.18
[iOS] autoresizingMask  (0) 2022.04.16
[iOS] APNs 이용해서 Push 보내기  (0) 2022.04.09
[iOS] Real Device vs iOS Simulator  (0) 2022.04.09
[iOS] Lottie  (0) 2022.04.09

댓글