[SWIFT] KVO (Key - Value - Observing)

2021. 9. 26. 17:15SWIFT

반응형

저번 시간에는 KVC(Key - Value - Coding)에 대해서 알아보았는데요. 

조금은 비슷한 개념들이 있는 KVO(Key - Value - Observing)에 대해 알아보려고 합니다. 

 

아마 크게 어려운 개념들은 없었던 것 같은데, 알아두면 꼭 개발을 하면서 한 번은 사용할 부분이 있어서 좋은 것 같습니다~

 

KVO란?

KVOKey - Value - Observing의 약자입니다.

 

딱 이름만 들었을 때, 어떤 느낌이 오나요? 

뭔가 Key에 관한 값의 변화를 관찰한다는 느낌이 딱 오지 않나요~

 

이전에 KVC에서도 Key에 대한 Value를 지정할 때, Key Path를 이용했었죠? 

KVO에서도 동일하게 Key Path를 사용합니다.

 

먼저 예시로 보겠습니다.

class People {
    var age: Int
    var name: String
    
    init() {
        self.age = 15
        self.name = "동동"
	}
}

우선 People이라는 객체가 있는데, 저희가 원하는 것은 여기서 나이가 변할때마다 어떤 특정 조건을 통과하는지 검사하고 싶습니다.

그러면 우선 변할때마다 age라는 Key 값의 Value를 검사해주면 되겠죠?

 

여기서 KVO를 사용하면 age라는 값이 변화하는 것을 감지가 가능합니다. 

사용하기 위해서는 저번에 KVC에서 사용했던 것과 동일하게 NSObject를 상속받아야 합니다.

 

그리고 Observing하고 싶은 프로퍼티에는 Objective-C 런타임 중에 접근이 가능하게 @objc dynamic이라는 키워드를 prefix로 붙여주어야 합니다.

class People: NSObject {
    @objc dynamic var age: Int
    var name: String
    
    override init() {
        self.age = 15
        self.name = "동동"
	}

}

이렇게 하면 이제 Objective-C 런타임 중에 age라는 변수에 접근할 준비가 완료된 것입니다.

Dispatch의 관점에서 보았을 때는 dynamic dispatch로 접근을 하게 되는것이겟죠?

 

이제는 age라는 값이 변화하는 것을 관찰하기 위해 세팅을 해볼게요~

class People: NSObject {
    @objc dynamic var age: Int
    var name: String
    
    override init() {
        self.age = 15
        self.name = "동동"
	}
}

let dongdong = People()
dongdong.observe(\.age, options: [.old, .new]) { object, changed in
    print("Current Object : \(object)")
    print("Old Value Before Changed : \(changed.oldValue)")
    print("New Value After Changed : \(changed.newValue)")
}

dongdong.age = 16
// 동동.age = 16
// Old Value Before Changed : 15
// New Value After Changed : 16

dongdong.age = 17
// 동동.age = 17
// Old Value Before Changed : 16
// New Value After Changed : 17

이렇게하면, 변화하는 값과 변화되기 전의 값이 출력되는 것을 확인할 수 있죠?

그리고 현재 동동이라는 초기 값은 관찰이 안되는데요. 그 값도 역시 options에 .initial이라는 옵션을 넣어주면 Observing이 가능합니다.

dongdong.observe(\.age, options: [.old, .new, .initial]) { object, changed in
    print("Current Object : \(object)")
    print("Old Value Before Changed : \(changed.oldValue)")
    print("New Value After Changed : \(changed.newValue)")
}

// 동동.age = 15
// Old Value Before Changed : nil
// New Value After Changed : 15

이렇게 options 값들로 필요한 값들만 Observing하는 것도 가능하게 됩니다. 

+ 지금 키 값을 설정할 때, Key Path로 설정하고 있으니 만약 People 안에 다른 클래스의 프로퍼티를 Observing하고 싶을 때도 체이닝을 이용해 충분히 가능하겠죠? 

 

근데 보다보니 Swift에도 Property Observers라고 willSet, didSet을 이용한 비슷한 기능이 있잖아요?!

사실 비슷하게 사용할 수 있을 것 같은데요. 그럼 뭐가 다른지 어떤 차이로 활용할 수 있는지를 간단히 생각한 것을 정리하고 오늘 포스팅을 끝마치도록 하겠습니다 :)

 

 

KVO 사용이유(?)

  1. RxSwiftSubscribe처럼 논리적인 분리가 가능해질 것 같습니다. 예로 ViewModel의 어떤 프로퍼티가 변화하였을 때, View에서의 변화를 유도할 때 사용가능할 것 같습니다.
    • 다만 이 경우 KVO의 경우 Objective-C의 런타임을 이용해야한다고 했는데 예로 어떤 class가 final이거나 static dispatch로 사용가능할 때, 모두 Objective-C의 런타임을 이용해 dynamic dispatch를 사용하게 된다면 성능에서 조금 차이가 날 것 같습니다.
  2. 내부의 코드는 건드리지 않고 외부에서 해당 값의 변화를 관찰해서 동작하는 것이 가능해집니다. willSet, didSet의 경우에는 class 내부에 구현해야하는데 KVO를 이용하는 경우에는 논리적인 분리가 가능하기 때문에, 외부에서 관찰하는 것이 가능해집니다.
    • 외부 라이브러리 or SDK를 이용하는 내부의 코드를 건드리기가 힘든데, 만약 이경우 내부에 NSObject를 상속받고 접근이 가능하다면 외부에서 이를 관찰해서 원하는 동작을 설계하는 것이 용이해집니다. (물론 NSObject를 상속받고 @objc dynamic으로 Objective-C 런타임 중에 접근이 가능할때만...)
  3. 기존 내장 프레임워크에서 아직 Objective-C가 fully Swift로 전환되지는 않아서 KVO를 이용해야만 접근이 가능한 것들이 있는 것 같습니다. 특히 AVFoundation의 Player를 이용하는 경우 내부 변수들을 참초할 때, 이용하는 경우가 많았습니다.
    • self.avPlayerItem?.observe(\AVPlayerItem.status, options: [.initial, .new]) { [weak self] avPlayerItem, _ in
          guard let self = self else { return }
          switch avPlayerItem.status {
          case .readyToPlay: print("playerItem Ready To Play")
          case .unknown:     print("Not to Play")
          case .failed:      print("Fail to Play")
          @unknown default:  print("NNNN")
          }
      }
      위는 예전에 사용했던 예시인데, 외에도 AVURLAsset.isPlayable의 KVO로 접근 가능한 변수들이 많습니다.

 

오늘은 이렇게 KVC에 이어 KVO라는 개념에 대해 알아보았습니다.

iOS를 개발하면서 한 번은 사용하게 될 개념인 것 같은데요 알아두면 유용할 것 같습니다 :)

 

혹시 잘못된 부분있으면 댓글남겨주세요~

 

반응형