概述
前言
前段时间刚到公司,公司在使用FBKVOController,本人一直在使用系统的KVO,没有使用过Facebook的这个框架,使用起来挺方便的,所以安利一波,并且读读源码,本文只是略读,了解了FBKVOController的结构和基本实现,可能他的设计思想还没有深入理解,以后慢慢探讨。
NSKeyValueObservingOptionNew:提供更改前的值
NSKeyValueObservingOptionOld:提供更改后的值
NSKeyValueObservingOptionInitial:观察最初的值(在注册观察服务时会调用一次触发方法)
NSKeyValueObservingOptionPrior:分别在值修改前后触发方法(即一次修改有两次触发)
FBKVOController的使用
// // ViewController.m // FBKVOControllerDemo // // Created by 李林 on 2017/5/17. // Copyright © 2017年 lee. All rights reserved. // #import "ViewController.h" #import <KVOController/KVOController.h> @interface ViewController (){ FBKVOController *_kvoCtrl; } @property (weak, nonatomic) IBOutlet UIButton *button; @property (weak, nonatomic) IBOutlet UIView *colorView; @property (nonatomic, assign) NSInteger index; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.index = 0; [self.button addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside]; // FBKVOController _kvoCtrl = [FBKVOController controllerWithObserver:self]; [_kvoCtrl observe:self keyPath:@"index" options:0 action:@selector(changeColor)]; } - (void)buttonClick { self.index++; } - (void)changeColor { self.colorView.backgroundColor = [UIColor redColor]; } @end
使用很简单,监测某个对象的值,然后将selector写入observe函数中,当值发生改变,就会调用通知的函数。效果如下图。
代码地址: https://github.com/lilianmao/FBKVOControllerDemo源码简析
1. 系统KVO的问题
- 需要手动移除观察者,且移除观察者的时机必须合适;remove时机很重要
- 注册观察者的代码和事件发生处的代码上下文不同,传递上下文是通过 void * 指针;不懂
- 需要覆写 -observeValueForKeyPath:ofObject:change:context: 方法,比较麻烦;覆盖写observeValueForKeyPath方法
- 在复杂的业务逻辑中,准确判断被观察者相对比较麻烦,有多个被观测的对象和属性时,需要在方法中写大量的 if 进行判断;当业务复杂的时候,覆盖写observeValueForKeyPath方法里有大量的if需要判断
2. FBKVOController
在系统KVO存在很多问题的情况下,FBKVOController应运而生,这个KVOController是Facebook的开源框架,github地址。
- 不需要手动移除观察者;框架自动帮我们移除观察者
- 实现 KVO 与事件发生处的代码上下文相同,不需要跨方法传参数;依旧不懂
- 使用 block 来替代方法能够减少使用的复杂度,提升使用 KVO 的体验;block或者selector的方式,方便使用
- 每一个 keyPath 会对应一个属性,不需要在 block 中使用 if 判断 keyPath;一个keyPath对应一个SEL或者block,不需要统一的observeValueForKeyPath方法里写if判断
3. 角色反转
FBKVOController实现了观察者和被观察者的角色反转,系统的KVO是被观察者添加观察者,而FBKVO实现了观察者主动去添加被观察者,实现了角色上的反转,其实就是用的比较方便。
// FBKVOController _kvoCtrl = [FBKVOController controllerWithObserver:self]; [_kvoCtrl observe:_person keyPath:@"age" options:0 action:@selector(changeColor)]; // 系统的KVO [_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
4. KVOController的实现
4.1 结构图
4.2 NSObject分类和KVOController的初始化
给NSObject添加一个FBKVOController分类,用关联对象动态添加属性。
#import <Foundation/Foundation.h> #import "FBKVOController.h" @interface NSObject (FBKVOController) @property (nonatomic, strong) FBKVOController *KVOController; @property (nonatomic, strong) FBKVOController *KVOControllerNonRetaining; @end
其中有一个是KVOControllerNonRetaining,防止循环引用。实现如下,要用weak指针。下文做试验证明如果一直只用KVOController,引起的循环引用内存泄漏问题。
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved { self = [super init]; if (nil != self) { _observer = observer; NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality; _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0]; pthread_mutex_init(&_lock, NULL); } return self; }
4.3 KVOController介绍
在KVOController.h文件,发现三个类:FBKVOInfo、FBKVOSharedController和FBKVOController。
- 其中FBKVOInfo主要是对需要观测的信息的包装,包含了action、block、options等等,改类中重写了hash,isEqual等方法。_FBKVOInfo覆写了
-isEqual:
方法用于对象之间的判等以及方便 NSMapTable 的存储。@implementation _FBKVOInfo { @public __weak FBKVOController *_controller; NSString *_keyPath; NSKeyValueObservingOptions _options; SEL _action; void *_context; FBKVONotificationBlock _block; _FBKVOInfoState _state; }
- FBKVOController是核心类,包含MapTable和pthread_mutex_lock(貌似原来用的是OSSpinLock),其中_objectInfosMap是存储一个对象对应的KVOInfo的映射关系,也就是说这里<id, NSMutableSet<_FBKVOInfo *> *> 中的id就是对象,
MutableSet就是KVOInfos,各种键值观测的包装。@implementation FBKVOController { NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap; pthread_mutex_t _lock; }
这张图表现了就是每个被观测者对象和KVOInfo的关系。
- FBKVOSharedController是一个实际操作类,负责将FBKVOController发送过来的信息转发给系统的KVO处理。这里实现了KVO的观测和remove。
@implementation _FBKVOSharedController { NSHashTable<_FBKVOInfo *> *_infos; pthread_mutex_t _mutex; }
4.4 KVO过程
我们从写的例子开始走起,可以把FBKVOCOntroller的流程看一遍。
(PS:这也是我现在看框架原码的方法)[_kvoCtrl observe:self keyPath:@"index" options:0 action:@selector(changeColor)];
这个函数
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options action:(SEL)action
是FBKVOController的,首先是断言判断是否为空,然后创造一个FBKVOInfo,最后调用本身的observe方法,将包装的FBKVOInfo的info传过去。- (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]; }
这个函数
- (void)_observe:(id)object info:(_FBKVOInfo *)info
是FBKVOController的,首先是加锁,防止读写干扰。然后我们查找一下这个object对应的MutableSet,如果有对应的KVOInfo的话,那么就不需要再添加入_objectInfosMap中了;如果没有,则创建info,并且加入_objectInfosMap中。最后解锁,将object传给_FBKVOSharedController处理。- (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]; }
这个
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info
函数应该是调用系统的addObserver,添加观察者。其中[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
这句话就是系统的KVO观测,object添加观测者,这里添加的是FBKVOShare,最后KVO的相应函数也在这里,正好呼应。- (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]; } }
这里是KVO的相应函数,有木有很熟悉?这里将当初我们传入的一些action或者block执行以下,完美。
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString *, id> *)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<NSString *, 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]; } 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]; } } } }
4.4 循环引用的分析
VC1持有_kvoCtrl,_kvoCtrl持有一个
_objectInfosMap
,这是一个可以存放弱指针的NSDictionary,这个函数[_objectInfosMap setObject:infos forKey:object];
就是将object和其需要监听的info加入map中。故VC1持有KVOCtrl,KVOCtrl持有map,map持有VC2,也就是VC1持有VC2。这是要如果我们VC2里观测VC1,就会VC2持有VC1。造成循环引用。致谢
本文主要参考的博客:
https://github.com/facebook/KVOController
https://github.com/Draveness/Analyze/blob/master/contents/KVOController/KVOController.md
http://chaosgsc.com/kvo.html
https://satanwoo.github.io/2016/02/27/FBKVOController/
链接:http://www.jianshu.com/p/1f7d70ff2002
最后
以上就是活泼乌冬面为你收集整理的FBKVOController详解前言FBKVOController的使用源码简析致谢的全部内容,希望文章能够帮你解决FBKVOController详解前言FBKVOController的使用源码简析致谢所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复