본문 바로가기
iOS/Combine

#12 Key-Value Observing

by HaningYa 2020. 10. 30.
728x90

[출처:www.raywenderlich.com/books/combine-asynchronous-programming-with-swift/v2.0]

변화를 다루는 것은 Combine 의 핵심기능 입니다.

subscriber를 통해 Publisher 를 구독(subscribe) 하여 비동기적 event 를 처리할 수 있게 해줍니다.

이전 챕터에서 assign(to:on) 을 배웠습니다.

assign()은 주어진 객체에 대한 프로퍼티의 값(value)을 publisher가 매번 새로운 값을 emit 할 때 update 시켜줄 수 있습니다.

하지만 단 하나의 변수에 대해 변화를 관찰하려는 경우는 어떨까요?

Combine 은 이런 경우에 있어 몇가지 옵션을 제공합니다.

  • KVO-compliant를 만족하는 모든(Any) 객체의 프로퍼티를 위한 publisher를 제공합니다.
  • ObservableObject protocol 은 여러개의 변수들이 바뀔 수 있는 상황을 처리합니다.

What is KVO

 

Guide to KVO in Swift 5 with code examples

Everything you need to know about Key-Value Observing in Swift and Objective-C

nalexn.github.io

 

 

Swifty하게 KVO 사용하기

이번 ‘네이버 핵데이 2018’에서 멘토님께 배우게 된 Swift스럽게 KVO를 사용하는 방법을 정리하려 한다. 기존의 방법기존의 방법은 감지하고 싶은 객체에 addObserver(_:forKeyPath:options:context:) 메서드

wnstkdyu.github.io

KVO란 Key-Value Observing 의 약자로 program의 state 변화를 관찰하는 Swift와 Objective-C의 테크닉입니다.

몇개의 instance 변수들이 있는 객체 A를 다룬다고 했을 때 KVO 는 다른 객체들이 객체 A 의 instance 변수에 대한 변화를 관찰할 수 있게 해줍니다.

KVO 는 Observer pattern 의 전형적인 예시이고 Objective-C에서는 기본적으로 class 에 추가하는 모든 instance 변수들이 KVO 를 통해 바로 observable 하게 되지만 Swift의 경우 default로 disabled 되어 있습니다. 

Objective-C class 가 Swift 에서 KVO 를 가능하게 하기 때문에 Swift class 의 base class 는 NSObject로 해야하고 @objc dynamic attribute 를 변수에 지정해줘야 한다. (이전방식)

새로운 API를 사용하면 observe(_:options:changeHandler) 메서드를 통해 KVO 를 할 수 있습니다. 


Introducing publisher(for:options:)

KVO는 Objective-C에서 필수적인 기능이였습니다.

Foundation, UIKit, AppKit class의 많은 프로퍼티들이 KVO 적용이 가능합니다.

그래서 그것들에 변화를 KVO 를 통해 관찰할 수 있습니다.

KVO를 따르는 프로퍼티의 경우 관찰하기 쉬운데 아래 예제를 보자면

queue의 operationCount value 를 observing 하는 코드입니다.

매번 새로운 operation이 queue에 추가된다면 operationCount는 증가할 것이고 그 변화는 sink를 통해 출력될 것입니다.

그리고 queue가 작업을 끝내면 operationCount 는 감소하고 그 변화 또한 sink를 통해 출력됩니다.

 

OperationQueue 말고도 다른 많은 framework class들이 KVO 를 사용가능합니다.

해당 프로퍼티에 대해 publisher(for:)을 사용해 관찰을 시작하고 publisher를 통해 emitting value 에 대한 작업을 할 수 있게 됩니다.

* Apple 은 KVO가 가능한 프로퍼티 리스트를 정리해서 제공하지 않습니다. 각각의 class 문서의 경우 어떤 프로퍼티가 KVO가 가능한지 나와있지만 몇가지 프로퍼티의 경우 문서의 quick note만 정리되있습니다.

Preparing and subscribing to your own KVO-compliant properties

Apple 이 제공하는 Framework class 에서 KVO 를 사용해봤다면 이제는 custom 한 KVO-compliant property 를 observing 해봅니다.

이를 위해 두가지를 제공해야 하는데

  • 객체는 class 여야 하고 NSObject를 상속받아야 합니다.
  • properties를 observable 하게 만들기 위해 @objc dynamic attribute 를 붙여줍니다.

이렇게 하면 Combine 에서 관찰할 수 있는 KVO 사용이 가능한 프로퍼티를 가진 객체를 만들 수 있습니다.

*Swift 언어는 직접적으로 KCO 를 지원하지 않기 떄문에 @objc dynamic 을 통해 강제로 compiler 가 KVO 를 위한 숨겨진 trigger 메소드를 만들게 합니다. 이런 과정을 설명하는건 책에서 벗어나는 범위인데 이 과정은 NSObject protocol 과 관련이 있기 때문에 NSObject 를 inherit 하는 것 입니다.

아니 근데 NSObject 는 class 인데 왜 protocol 이라고 하지

NSObject protocol 이름은 NSObjectProtocol 이다.


Swift 에서 class name : XXX 일때
Conforming protocol 인지 inheriting superclass 인지 어떻게 알지?

 

How can you tell if a Swift class is inheriting from another class or is conforming to a protocol?

For example, if we have a class Foo that inherits from class Bar, and conforms to the protocol Baz: class Bar {} protocol Baz {} class Foo: Bar, Baz {} What if we don't know ahead of time that ...

stackoverflow.com

objective-c 에서는 angle bracket으로 Protocol 감싸기 때문에 알기 쉬웠지만 Swift의 경우 동일하다. 

어떻게 구분할까

  • protocol 일 경우 필요한 메서드를 쓰라고 하기 때문에 알기 쉬울듯
  • naming convention 에 따라서 뒤에 Protocol 이나 Delegate를 쓰면 되잖냐

Swift 는 protocol 을 type을 다룰때와 비슷하게 처리합니다.

왜 이렇게 디자인 됬냐면 많은 instance들 가운데서 protocol conformance 와 class inheritance를 즉시 구분하기가 힘들었기 때문입니다.

실제로 구분을 하려면 option-click 을 통해서 나오는 Declaration을 참고하면 됩니다.


예제 코드를 보면

  1. NSObject protocol 을 inherits 하는 class 를 만듭니다.
  2. 관찰하고 싶은 프로퍼티에 대해 @objc dynamic attribute를 표시합니다.
  3. publisher 를 만든후 integerProperty 에 대해 subscribe 하여 관찰을 시작합니다.
  4. 관찰하는 프로퍼티에 값을 바꿔줘봅니다.

초기값인 0을 출력하는게 좀 이상합니다. 만약에 이 초기값을 받고싶지 않으면 어떻게 해야 할까요?

TestObject는 평범한 Swift Int type을 사용하고 있고 Objective-C 의 KVO 기능을 사용하고 있는데 동작이 됩니다.

KVO 는 아무 Objective-C type과 동작되며 그 뜻은 Objective-C에 연결된 Swift type 과도 잘 동작한다는 것이고 이러한 Swfit type에는 거의 모든 Swift native type들이 포함되어 있습니다.

다른 타입의 프로퍼티를 추가해주고 값을 바꾸고 관찰해봅니다.

초기값들이 출력되는 걸 볼 수 있습니다.

만약 Objective-C로 bridged 되지 않은 Swift type을 쓰게 된다면 오류가 발생합니다.

*system frameworks object 에 대한 KVO 를 진행할 때는 주의해야 합니다. 문서가 해당 프로퍼티가 Observable 한지 확인해야 하며 왜냐하면 system object property list 를 보고 알 수 없기 때문입니다. Foundation, UIKit AppKit 등 모두 해당됩니다.

Observation options

변화를 관찰하는 full 메소드 시그니쳐는 publisher(for:options:) 입니다.

option 파라이터에는 4가지 값이 들어갈 수 있습니다. (디폴트 값은 [.inital] 임 - 모든 변화를 다 출력, 초기값도 출력]

  • .inital : 초기값을 emit
  • .prior : 변화가 발생했을 때 이전값과 새로운 값을 동시에 emit
  • .old : 이번 publisher 에서는 사용안하고 아무일도 안함
  • .new : 이번 publisher 에서는 사용안하고 아무일도 안함

만약 초기값을 원하지 않으면 아래와 같이 configure 하면 된다.

obj.publisher(for: \.stringProperty, option: [])

만약 .prior 를 사용하면 변화가 있을 때마다 2개씩 값을 받아오게 된다. (0,100) (100, 200)

ObservableObject

Combine의 ObservableObject 프로토콜은 NSObject를 상속받은 Object 뿐만 아니라 Swift Object 에서도 동작합니다.

@Published property wrapper를 사용해 compiler 가 만드는 objectWillChange publisher를 가지는 class 를 만들 수 있습니다.

이 방법을 통해 많은 양의 boilerplate 코드를 작성하지 않아도 됩니다.

예시를 들자면

ObservableObject protocol를 준수하는 것은 compiler가 자동으로 objecetWillChange 프로퍼티를 만들어 줄 수 있게 해줍니다.

그것은 ObservableObjectPublisher로써 Void 를 emit 하며 에러 타입은 Never 입니다.

매번 object 의 @Published 변수가 바뀔 때 마다 objectWillChange 는 trigger 되지만 어떤 특정 프로퍼티가 변화했는지는 알 수 없습니다. 

이것은 화면 업데이트를 간소화하기 위해 이벤트를 통합하는 SwiftUI와 매우 잘 작동하도록 설계되었습니다.

Key points

  • KVO는 대부분 Objective-C 런타임과 NSObject protocol 메소드에 의존합니다.
  • Apple framework 상의 많은 Objective-C class들은 KVO 가 가능한 프로퍼티들을 제공합니다.
  • observable 한 프로퍼티를 가지는 자신만의 Object를 만들고 싶다면 NSObject 를 상속하는 class 를 만들고 관찰을 원하는 변수에 @objc dynamic attribute 를 표시하면 됩니다.
  • @Published를 통해 ObservableObject를 상속받아 objectWillChange publisher를 compiler 가 만들게 하여 해당 property 값이 가뀔 때 마다 동작을 trigger 할 수 있습니다. (근데 어떤게 바꼈는지는 특정할 수 없습니다.)
728x90

'iOS > Combine' 카테고리의 다른 글

#17 Schedulers  (0) 2020.11.03
#Chapter15 In Practice: Combine & SwiftUI  (0) 2020.11.02
#9 Networking  (0) 2020.10.28
"UI events are asynchronous"  (0) 2020.10.28
#3 Transforming Operators  (1) 2020.10.28

댓글