KVOController源码阅读:一款好用的KVO管理库

系统KVO

  1. KVO 观察者模式的应用; 运用了isa混写技术。能监听属性的变化;响应式的代表。
  2. 释放是个问题,移除多次会闪退,内存泄漏导致dealloc没走,也会没释放。
  3. KVOController是为了解决这些问题。
使用
- (void)bind:(BLMeetingItemModel *)model{@weakify(self); [self.KVOController observe:model keyPath:FBKVOKeyPath(model.isOpenMicro) options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew block:^(id_Nullable observer, id_Nonnull object, NSDictionary * _Nonnull change) { @strongify(self); BOOL isOpenMicro = [change[@"new"] boolValue]; NSLog(@"%@-%@麦克风",model.nickName,isOpenMicro?@"开启":@"关闭"); [self.bottomView updateMicroStatus]; }]; [self.KVOController observe:model keyPath:FBKVOKeyPath(model.isOpenVideo) options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew block:^(id_Nullable observer, id_Nonnull object, NSDictionary * _Nonnull change) { @strongify(self); BOOL isOpenMicro = [change[@"new"] boolValue]; NSLog(@"%@-%@视频-%@",model.nickName,isOpenMicro?@"开启":@"关闭",NSStringFromClass(self.class)); [self loadVideoSource]; }]; [self.KVOController observe:model keyPath:FBKVOKeyPath(model.isHost) options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew block:^(id_Nullable observer, id_Nonnull object, NSDictionary * _Nonnull change) { @strongify(self); [self.bottomView updateView:model]; //更新主持人 }]; }- (void)unbind:(BLMeetingItemModel *)model{ [self.KVOController unobserve:model keyPath:FBKVOKeyPath(model.isOpenMicro)]; [self.KVOController unobserve:model keyPath:FBKVOKeyPath(model.isOpenVideo)]; [self.KVOController unobserve:model keyPath:FBKVOKeyPath(model.isHost)]; }

FBKVOKeyPath与FBKVOClassKeyPath
  1. keyPath直接使用字符,当修改属性时,编译时不会报错,而这两个宏能够在编译时检查出错误。
  2. FBKVOKeyPath针对一些简单场景,如上述model; 如果出现FBKVOKeyPath(self.lectureView.showType)就会出现异常,复杂情况可以采用FBKVOClassKeyPath(BLMeetingVCRotateControl, orientation)
FBKVOController
//类名:NSObject (FBKVOController) - (FBKVOController *)KVOController { id controller = objc_getAssociatedObject(self, NSObjectKVOControllerKey); // lazily create the KVOController if (nil == controller) { controller = [FBKVOController controllerWithObserver:self]; self.KVOController = controller; }return controller; }- (void)setKVOController:(FBKVOController *)KVOController { objc_setAssociatedObject(self, NSObjectKVOControllerKey, KVOController, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }

【KVOController源码阅读:一款好用的KVO管理库】FBKVOController关联对象存储的对象,方便使用。同时原对象释放,FBKVOController也会释放。
//FBKVOController - (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block { NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block); if (nil == object || 0 == keyPath.length || NULL == block) { return; }// create info _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block]; // observe object with info [self _observe:object info:info]; }- (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]; 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]; }

  1. 每一个[observe:keyPath:options:block]生成一个_FBKVOInfo对象。_FBKVOInfo仅仅是一个model对象。
  2. 每个kvoController都有一个_objectInfosMap; 对应多个object,object是被观察者。
  3. 每个object都会生成一个NSMutableSet * infos,用来存放_FBKVOInfo对象。而生成的infos以object地址为key存储在_objectInfosMap。
  4. 如果_FBKVOInfo已存在,直接return返回。如不存在,则创建NSMutableSet * infos,infos 添加_FBKVOInfo * info,存储在_objectInfosMap中。
_FBKVOSharedController
//_FBKVOSharedController - (void)observe:(id)object info:(nullable _FBKVOInfo *)info { if (nil == info) { return; }// register info pthread_mutex_lock(&_mutex); [_infos addObject:info]; pthread_mutex_unlock(&_mutex); // add observer [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info]; if (info->_state == _FBKVOInfoStateInitial) { info->_state = _FBKVOInfoStateObserving; } else if (info->_state == _FBKVOInfoStateNotObserving) { // this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions, // and the observer is unregistered within the callback block. // at this time the object has been registered as an observer (in Foundation KVO), // so we can safely unobserve it. [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info]; } }- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary *)change context:(nullable void *)context { NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change); _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 *changeWithKeyPath = change; // add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed if (keyPath) { NSMutableDictionary *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey]; [mChange addEntriesFromDictionary:change]; changeWithKeyPath = [mChange copy]; } info->_block(observer, object, changeWithKeyPath); } else if (info->_action) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [observer performSelector:info->_action withObject:change withObject:object]; #pragma clang diagnostic pop } else { [observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context]; } } } } }

  1. _FBKVOSharedController是个单例类,真正的kvo实现都在这个类中实现。
  2. [observeValueForKeyPath:ofObject:change:context:]是kvo的回调,info = [_infos member:(__bridge id)context]; 找到info对象。
  3. 取出info对象中的FBKVOController *controller,观察者,block或action属性都在_FBKVOInfo *info对象中。对block和action进行回调。
释放
//FBKVOController - (void)dealloc { [self unobserveAll]; pthread_mutex_destroy(&_lock); }- (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]; } }

//_FBKVOSharedController - (void)unobserve:(id)object infos:(nullable NSSet<_FBKVOInfo *> *)infos { if (0 == infos.count) { return; }// unregister info pthread_mutex_lock(&_mutex); for (_FBKVOInfo *info in infos) { [_infos removeObject:info]; } pthread_mutex_unlock(&_mutex); // remove observer for (_FBKVOInfo *info in infos) { if (info->_state == _FBKVOInfoStateObserving) { [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info]; } info->_state = _FBKVOInfoStateNotObserving; } }

  1. FBKVOController是以关联对象的形式释放,原对象释放则FBKVOController释放,dealloc方法会执行。
  2. 在dealloc中调用了unobserveAll。
  3. 首先会[_objectInfosMap removeAllObjects],遍历objectInfoMaps移除_FBKVOSharedController的对象,移除系统的观察者。
小结
  1. KVOController的[observe:keyPath:options:block]即使调用多次,系统也只添加一次kvo。即使_FBKVOInfo对象不同,keyPath相同,也算同一个观察者,在_FBKVOInfo中重写了isEqual方法。
  2. 使用系统kvo不用移除,不用担心释放问题,也不用担心移除多次操作闪退问题。如果原本观察者出现内存泄漏导致dealloc方法没有执行,需要读者自己检查原因了。
  3. 此库支持keyPaths,支持action和block回调,一个keyPath观察者一个block,比系统observeValueForKeyPath判断要清晰很多。

    推荐阅读