preface

KVO is a technology frequently used in daily iOS development, through which it can be very convenient to monitor attributes. This paper does not involve the underlying principles, but mainly introduces the application of the above methods in the business layer, and summarizes their characteristics. We can focus on the use of Swift KeyPath and the interface encapsulated by ReactiveCocoa. Our project is gradually shifting to Swift, so the following four types are commonly used in our project

  • Foundation KVO
  • ReactiveObjC KVO
  • Swift KeyPath
  • ReactiveCocoa KVO

1. Foundation KVO

The KVO mechanism provided by Foundation is the underlying Foundation for most OF the KVO interfaces, and its rough implementation can be seen in point 2 of ObjC

Example Code:

deinit {
    // Listeners must be removed manually when objects are released
    removeObserver(self, forKeyPath: #keyPath(model.currentValue))
}
 
override func setupObserver(a) {
    super.setupObserver()
    // Listen on the currentValue property of model
    addObserver(self, forKeyPath: #keyPath(model.currentValue), options: .new, context: nil)}override func observeValue(forKeyPath keyPath: String? .of object: Any?.change: [NSKeyValueChangeKey : Any]?.context: UnsafeMutableRawPointer?). {
    // Listen for changes, you can get the initial value/old value/new value from change
}
Copy the code

Advantages:

  • Strong versatility, does not rely on other frameworks

Disadvantages:

  • The key path can only be hardcoded with strings and cannot be aware of changes in the monitored attribute name
  • Manually remove the object; otherwise, the object may crash when it is released
  • For one-to-one monitoring and callback, the logic will be scattered, which is not as convenient as the block based interface

2. ReactiveObjC KVO

This method is the one most used in our project. The underlying method is based on Foundation KVO, which provides a RACObserve macro to abstract a monitor into a signal

Example Code:

@weakify(self); [RACObserve(self.model, currentValue) subscribeNext:^(id _Nullable x) {@strongify(self);Copy the code

Advantages:

  • Solved the key path hardcoding problem
  • Automatically binds the life cycle of the current object without manual removal
  • The Block Base interface is very friendly for single attribute listening and processing

Disadvantages:

  • You need to rely on ReactiveObjC
  • The type in the callback is erased
  • You need to be careful with circular references

3. Swift KeyPath

KeyPath is a KVO interface for Swift introduced in Swift 4 that supports validation of key path names and data type derivation in callbacks

Note that the K is uppercase, and the other thing called #keyPath essentially gets the string corresponding to the keypath (see Foundation KVO for an example), as well as the @keypath macro in ReactiveObjC

Example Code:

// Listen on the currentValue property of model
observation = model.observe(\.currentValue, options: .new, changeHandler: { [weak self] _, change in
    // access change.newValue to obtain the newValue, although the type can be derived, but Optional
})
Copy the code

The observe method above can also omit the options argument so that change in the callback will not contain the property value and will need to access the object’s property to get the new value

Original document: newValue and oldValue will only be non-nil if .new/.old is passed to “observe()”. In general, get the most up to date value by accessing it directly on the observed object instead.

Example Code 2:

observation = model.observe(\.currentValue) { [weak self] _._ in
    // Access model.currentValue to get the new value
}
Copy the code

Observe that the Observe method returns an NSKeyValueObservation object. Can this return object be ignored like RACDisposable?

The answer is no, according to the documents:

when the returned NSKeyValueObservation is deinited or invalidated, it will stop observing

Obviously, if the returned NSKeyValueObservation object is released, the listening is over, so the Observe method doesn’t flag @discardableresult, and you’ll be warned if you don’t receive the return value

Advantages:

  • Optimized for Swift calls with support for type derivation

Disadvantages:

  • Only supports the Swift,
  • I need to maintain another observation
  • The class being listened on needs to be of objC, and the property needs to be marked dynamic

4. ReactiveCocoa KVO

The usage logic of ReactiveCocoa is similar to that of ReactiveObjC in that it abstracts a listener into a signal at a time. Both Foundation KVO and Swift KeyPath implementations are internally encapsulated

Example of Foundation KVO based interface call:

// Listen on the currentValue property of model
model.reactive.signal(forKeyPath: "currentValue").observeValues { [weak self] in
    // The type cannot be inferred and needs to be unpacked
}
Copy the code

Examples of Swift KeyPath based interface calls:

model.reactive.signal(for: \.currentValue).observeValues { [weak self] in
    // The type can be inferred automatically without unpacking
}
Copy the code

The compiler validates the object, the property name, and the property type. It also helps us do a stack of observations. Crucially, it doesn’t have to maintain observations

Advantages:

  • Swift call friendly

  • Type derivation is the best

Disadvantages:

  • Rely on ReactiveCocoa & Reactivesswift

5. Summary

KVO way External dependencies Life cycle management key path Attribute type inference in callback ObjC Swift
Foundation KVO There is no Manually stop listening String hardcoding (Swift #keyPath) NO YES YES
Swift KVO There is no Bind an Observation object Use the \ operator YES (but degraded to Optional) NO YES
ReactiveObjC KVO ReactiveObjC Automatic management @ keypath RACObserve NO YES NO
ReactiveCocoa KVO ReactiveCocoa Automatic management String or \ operators are supported YES(KeyPath based interface) NO YES