swift

Key-Value-Observing (KVO)

kimyounggyun 2022. 10. 5. 12:19

Key-Value-Observing

Key-Value-Observing (KVO)는 코코아 프로그래밍 패턴 중 한 개로 프로퍼티의 변화를 다른 객체에 알릴 때 사용할 수 있다. 애플리케이션의 뷰(View) 또는 모델(Model)의 프로퍼티 변화를 컨트롤러(Controller)에 알릴 때 유용하다.

 

KVO는 프로퍼티가 변경될 때마다 알림을 보내기 위한 체계를 구현할 필요가 없다. 프로퍼티의 변경 사항이 생기면 관찰 객체에 직접 알림을 전달하기 때문에 NSNotificationCenter와 다르게 관찰자에게 변경 사항을 알리는 중앙 개체가 필요 없다는 것이 장점이다. NSObject를 상속할 때 KVO를 위한 기본적인 메서드가 NSObject에 이미 구현되어 있기 때문에 이러한 메서드를 오버라이드 할 필요도 없다.

 

또한 상속 혹은 코드의 변경 없이 프로퍼티의 변화를 관찰할 수 있다는 장점이 있다. 프로퍼티 옵저버를 이용해도 프로퍼티의 변화를 감지할 수 있다. 하지만 프로퍼티 옵저버와의 차이점은 관찰하려고 하는 프로퍼티가 외부 라이브러리에서 정의되어 있다면 코드의 수정이 불가능하므로 프로퍼티 옵저버로 관찰하는 것은 한계가 있다. 이때 KVO를 사용할 수 있다.

 

마지막으로 KeyPath를 사용하여 프로퍼티를 관찰하기 때문에 중첩된 객체의 프로퍼티도 쉽게 관찰할 수 있다.

Using Key-Value Observing in Swift

 Employee 객체가 직원의 세부 정보를 나타내는 Details 객체를 프로퍼티로 가지고 있다. Details 객체의 id, phoneNumber 프로퍼티가 변경될 때마다 Employee 객체가 알고 싶을 때에 어떻게 처리를 하는지 코드를 작성해보자.

class Details {
  var id: Int
  var phoneNumber: Int
  
  init(id: Int, phoneNumber: Int) {
    self.id = id
    self.phoneNumber = phoneNumber
  }
}

class Employee {
  var name: String
  var details: Details
  
  init(name: String, details: Details) {
    self.name = name
    self.details = details
  }
}

Inherit NSObject

NSObejct를 상속하고 Employee에 변화를 감지할 observation을 추가하자.

class Details: NSObject {
  var id: Int
  var phoneNumber: Int
  
  init(id: Int, phoneNumber: Int) {
    self.id = id
    self.phoneNumber = phoneNumber
  }
}

class Employee: NSObject {
  var name: String
  var details: Details
  var observation: NSKeyValueObservation?
  
  init(name: String, details: Details) {
    self.name = name
    self.details = details
    // observation 초기화
  }
}

Annotate a Property for Key-Value Observing

관찰할 프로퍼티에 @objc속성과 dynamic 키워드를 추가하자.

class Details: NSObject {
  @objc dynamic var id: Int
  
  ...
}

class Employee: NSObject {
  var name: String
  @objc dynamic var details: Details
  var observation: NSKeyValueObservation?
  
  ...
}

Define an Observer

observation 프로퍼티를 초기화하기 위해 observe(_ keyPath: options: changeHandler:) 메서드를 호출해 옵저버를 등록한다. options으로 제공하는 NSKeyValueObservingOptions에는 new, old, initial, prior 4가지를 제공해 준다. 객체가 초기화되는 시점부터 값의 변화를 알고 싶다면 initial을 사용할 수 있고 단순히 이전 값과 현재 값의 변화를 알고 싶다면 oldnew 옵션을 사용하면 된다.

class Details: NSObject {
  @objc dynamic var id: Int
  var phoneNumber: Int
  
  init(id: Int, phoneNumber: Int) {
    self.id = id
    self.phoneNumber = phoneNumber
  }
}

class Employee: NSObject {
  var name: String
  @objc dynamic var details: Details
  var observation: NSKeyValueObservation?
  
  init(name: String, details: Details) {
    self.name = name
    self.details = details
    super.init()
    self.observation = observe(\.details.id,
                                options: [.old, .new]) { object, change in
      print("id changed from: \(change.oldValue!), updated to: \(change.newValue!)")
    }
  }
}

사용

var id = \Details.id
print(detail.id)  // 1004
detail[keyPath: id] = 1234 // id changed from: 1004, updated to: 1234
print(detail.id)  // 1234