In the previous article, we used FBKVOController to implement the MVVM pattern. Now let’s analyze the implementation principle of FBKVOController.

When we usually use KVO, we directly add a listener to the listener to achieve this. But in FBKVOController it is managed by a singleton _FBKVOSharedController.

The process is roughly as shown below

The listener creates and holds a FBKVOController object. When the listener method is called, a _FBKVOInfo object is generated.

- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options action:(SEL)action { NSAssert(0 ! = keyPath.length && NULL ! = action, @"missing required parameters observe:%@ keyPath:%@ action:%@", object, keyPath, NSStringFromSelector(action)); NSAssert([_observer respondsToSelector:action], @"%@ does not respond to %@", _observer, NSStringFromSelector(action)); if (nil == object || 0 == keyPath.length || NULL == action) { return; } // create info _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options action:action]; // observe object with info [self _observe:object info:info]; }Copy the code

The _FBKVOInfo object contains the following

{ @public __weak FBKVOController *_controller; //controller,conttoller holds listener NSString *_keyPath; / / be listening path NSKeyValueObservingOptions _options; // Sel_action; // Listener callback method void *_context; // FBKVONotificationBlock _block; // Listener callback block _FBKVOInfoState _state; // The status of the listener is divided into initial listener not listening}Copy the code

2 pass the listener object and the generated _FBKVOInfo object to the singleton _FBKVOSharedController. _FBKVOSharedController is stored in hash table _infos with object as key and _FBKVOInfo as value. And perform KVO listening on object

3 When the object changes, the apple method is executed

- (void)observeValueForKeyPath:(nullable NSString *)keyPath
                      ofObject:(nullable id)object
                        change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
                       context:(nullable void *)context
Copy the code

In this method, the hash table _infos in _FBKVOSharedController is used to obtain the corresponding value, the _FBKVOInfo object, with object as the key. Get the listener inside and the method to call, and execute the method

4 This parameter is called when listening needs to be removed

- (void)unobserve:(id)object info:(nullable _FBKVOInfo *)info { if (nil == info) { return; } // unregister info pthread_mutex_lock(&_mutex); [_infos removeObject:info]; pthread_mutex_unlock(&_mutex); // remove observer if (info->_state == _FBKVOInfoStateObserving) { [object removeObserver:self forKeyPath:info->_keyPath  context:(void *)info]; } info->_state = _FBKVOInfoStateNotObserving; }Copy the code

This method first removes _FBKVOInfo from the hash table of _infOS, then removes KVO from the listener object of _FBKVOInfo, and removes it from the global hash table.

5 In fact, FBKVOController, a third-party library, does not need to manually move the listener, which is also the biggest advantage of this library. Because when the listener holding the FBKVOController object is released, the FBKVOController object is also released. The dealloc method executed when the FBKVOController object is released is as follows

- (void)dealloc
{
  [self unobserveAll];
  pthread_mutex_destroy(&_lock);
}
Copy the code

**[self unobserveAll]** will loop through the unobserve:(id)object info:(nullable _FBKVOInfo *)info method to remove kvo.

The FBKVOController library implementation process is basically like this.

In addition, FBKVOController is thread-safe because pthread_mutex_lock is used for thread-safe control of listening, removing listening, adding and deleting _FBKVOInfo messages.

In fact, like MGJRouter, FBKVOController is relatively simple to implement. It stores the key information that needs to be stored in the dictionary in the way of objects.