To get started, Apple offers two kinds of KVO

The second one is convenient

1. By calling the method,addObserver(

  • Listening object
class Cate: NSObject{
    
    @objc dynamic var age = 51
    
    var k: String{
        "age"
    }
}


class ViewController: UIViewController {
    let cat = Cate()
}
Copy the code
  • Add observer
cat.addObserver(self, forKeyPath: cat.k, options: .new, context: nil)
Copy the code
  • The response to observe
override func observeValue(forKeyPath keyPath: String? , of object: Any? , change: [NSKeyValueChangeKey : Any]? , context: UnsafeMutableRawPointer?) { if let dict = change{ print("age : ", dict[.newKey] ?? "ha ha") } }Copy the code
  • Remove observer
cat.removeObserver(self, forKeyPath: cat.k, context: nil)
Copy the code

2. convenient closure

  • Observe the behavior as an object
var moneyObservation: NSKeyValueObservation?
Copy the code
  • Add observer, closure response

Observed objects, see above

Write path directly, no longer hard coded string (error prone)

         moneyObservation = cat.observe(\.age, changeHandler: { cat, change in
            print("money :  ", cat.age)
        })
Copy the code
  • Remove observer
moneyObservation = nil
Copy the code

Enter theFBKVOController, FB self-developed part

FBKVOController has a wrapper around the system’s KVO methods,

From KVO implementation 1 provided by Apple to KVO implementation 2

And automatically remove observation as the owning controller is released

FBKVOControllerThe idea is very consistent with some tripartite libraries

  • There’s a singleton _FBKVOSharedController that does things

  • There is an associated object, FBKVOController, that dynamically adds events to the singleton

(Also, dynamically unlog events)

FBKVOController, a property of its own, can also be created and used

FBKVOControllerConcrete implementation of

The feature is to transform the general observation of the controller CTRL into the observation of the unified singleton object

Add observer

  • Save the information
@implementation FBKVOController - (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block { if (nil == object || 0 == keyPath.length || NULL == block) { return; } // Create information to save // create info _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block]; // observe object with info [self _observe:object info:info]; } @endCopy the code
  • To achieve observer transfer,

It’s usually CTRL, looking at some property of the object

into

Singleton _FBKVOSharedController, observing a property of the object

@implementation FBKVOController - (void)_observe:(id)object info:(_FBKVOInfo *)info { // lock pthread_mutex_lock(&_lock); NSMutableSet *infos = [_objectInfosMap objectForKey:object]; // check for info existence _FBKVOInfo *existingInfo = [infos member:info]; // Security check if (nil! = existingInfo) { // observation info already exists; do not observe it again // unlock and return pthread_mutex_unlock(&_lock); return; } lazilly create set of infos if (nil == infos) {infos = [NSMutableSet set]; [_objectInfosMap setObject:infos forKey:object]; } // add info and oberve [infos addObject:info]; // unlock prior to callout pthread_mutex_unlock(&_lock); [[_FBKVOSharedController sharedController] observe:object info:info]; } @endCopy the code

And the rest is

Singleton _FBKVOSharedController, which calls apple’s KVO implementation one

Responding to change

Very comprehensive

  • block

  • target – action

  • Apple’s KVO implements a response method

@implementation _FBKVOSharedController - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context { _FBKVOInfo *info; { // lookup context in registered infos, taking out a strong reference only if it exists pthread_mutex_lock(&_mutex); info = [_infos member:(__bridge id)context]; pthread_mutex_unlock(&_mutex); } if (nil ! = info) { // take strong reference to controller FBKVOController *controller = info->_controller; if (nil ! = controller) {// take strong reference to observer id observer = controller. Observer; if (nil ! = observer) { // dispatch custom block or action, fall back to default action if (info->_block) { NSDictionary<NSKeyValueChangeKey, id> *changeWithKeyPath = change; // add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed if (keyPath) { NSMutableDictionary<NSString *, id> *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey]; [mChange addEntriesFromDictionary:change]; changeWithKeyPath = [mChange copy]; } // Block response info->_block(observer, object, changeWithKeyPath); } else if (info->_action) { // target - action [observer performSelector:info->_action withObject:change withObject:object]; } else {// Apple KVO implementation one, Response method [observer observeValueForKeyPath: keyPath ofObject: object change: change the context: the info - > _context]; } } } } } @endCopy the code

Automatic release

Object of FBKVOController, as a property of CTRL,

CTRL release, its property FBKVOController object, release

Call the dealloc method of FBKVOController

@implementation FBKVOController - (void)dealloc {[unobserveAll]; pthread_mutex_destroy(&_lock); } // Specific release logic // two layers, Void (unobserveall) {// lock pthread_mutex_lock(&_lock); NSMapTable *objectInfoMaps = [_objectInfosMap copy]; // clear table and map [_objectInfosMap removeAllObjects]; // unlock pthread_mutex_unlock(&_lock); _FBKVOSharedController *shareController = [_FBKVOSharedController sharedController]; for (id object in objectInfoMaps) { // unobserve each registered object and infos NSSet *infos = [objectInfoMaps objectForKey:object]; [shareController unobserve:object infos:infos]; } } @endCopy the code

githud demo