FBKVOController是对KVO的封装,本文会分为两大部分:java
1、针对FBKVOController进行源码解读,剖析其封装思路android
2、针对源码,抽取其精要,模仿学习,变为己用ios
相对于原生 API 优点objective-c
一、能够以数组形式,同时对 model 的多个 不一样成员变量进行 KVO。swift
二、利用提供的 block,将 KVO 相关代码集中在一块,而不是四处散落。比较清晰,一目了然。数组
三、不须要在 dealloc 方法里取消对 object 的观察,当 FBKVOController 对象 dealloc,会自动取消观察。ruby
//一、在当前类建立一个KVO的控制器,而且指明监听者为当前类
// create KVO controller with observer
FBKVOController *KVOController = [FBKVOController controllerWithObserver:self];
self.KVOController = KVOController;
//二、监听对象
// observe clock date property
[self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(ClockView *clockView, Clock *clock, NSDictionary *change) {
// 更新UI
// update clock view with new value
clockView.date = change[NSKeyValueChangeNewKey];
}];
复制代码
使用步骤很简短,咱们关键是理解里面的封装。bash
1、咱们先看一下建立KVO controller实例的方法,以及销毁方法--(生命周期)app
#pragma mark Lifecycle -
//一、
+ (instancetype)controllerWithObserver:(nullable id)observer
{
return [[self alloc] initWithObserver:observer];
}
//二、初始化observer,并依据retainObserved值决定内存策略
- (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;
}
//三、
- (instancetype)initWithObserver:(nullable id)observer
{
return [self initWithObserver:observer retainObserved:YES];
}
//四、在dealloc注销全部监听而且销毁上面的互斥锁
- (void)dealloc
{
[self unobserveAll];
pthread_mutex_destroy(&_lock);
}
复制代码
总结:一、NSPointerFunctionsStrongMemory建立了一个retain/release对象的集合,很是像常规的NSSet或NSArray。 NSPointerFunctionsWeakMemory使用等价的__weak来存储对象并自动移除被销毁的对象。ide
二、比较陌生的是 NSMapTable 。简单来讲,它与 NSDictionary 相似。不一样之处是 NSMapTable 能够自主控制 key / value 的内存管理策略。而 NSDictionary 的内存策略是固定为 copy。当 key 为 object 时, copy 的开销可能比较大!所以,在这里只能使用相对比较灵活的 NSMapTable。具体能够移步关于 NSMapTable
三、pthread_mutex:这是一种超级易用的互斥锁,使用的时候,只须要初始化一个 pthread_mutex_t,用 pthread_mutex_lock 来锁定, pthread_mutex_unlock 来解锁,当使用完成后,记得调用 pthread_mutex_destroy 来销毁锁
2、接下来看一下注册监听对象的方法
- (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;
}
//建立FBKVOInfo
// create info
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
//利用FBKVOInfo观察对象
// observe object with info
[self _observe:object info:info];
}
- (void)observe:(nullable id)object keyPaths:(NSArray<NSString *> *)keyPaths options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
{
NSAssert(0 != keyPaths.count && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPaths, block);
if (nil == object || 0 == keyPaths.count || NULL == block) {
return;
}
//遍历每一个keyPath,再递归
for (NSString *keyPath in keyPaths) {
[self observe:object keyPath:keyPath options:options block:block];
}
}
复制代码
使用断言,提示用户缺乏必要参数; 为了不保留循环,该block必须避免引用KVO控制器或其全部者。观察已经观察到的对象keyPath或nil的结果是没有操做的。
看一下FBKVOInfo的init方法
- (instancetype)initWithController:(FBKVOController *)controller
keyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
block:(nullable FBKVONotificationBlock)block
action:(nullable SEL)action
context:(nullable void *)context
{
self = [super init];
if (nil != self) {
_controller = controller;
_block = [block copy];
_keyPath = [keyPath copy];
_options = options;
_action = action;
_context = context;
}
return self;
}
复制代码
重写init方法,把值分别赋值给属性,对于为何要
if (nil != self)
,我认为,当应用程序在更有限的内存中运行,这是一个传统的编码建议。具体请看各位大神的回答--> In Objective-C why should I check if self = [super init] is not nil?
看一下观察FBInfo的方法
- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
// lock
pthread_mutex_lock(&_lock);
//1
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
//2
// 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;
}
//3
// 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);
//4
[[_FBKVOSharedController sharedController] observe:object info:info];
}
复制代码
NSMutableSet是一个集合,它有几个特色: 一、没有顺序,全部元素并不是按照加入顺序排列 二、重复元素只会添加一个,所以不用担忧里面的元素有重复
NSMapTable是比Dicitionary更强大的一个类。咱们定义一个Person类,用来记录人名,咱们再建立一个Favourite类用来建立爱好对象,如今有Rose和Jack两我的,分别的爱好是ObjC和Swift,人和爱好必需要用对象实现,并且必须关联起来在一个表里,以便咱们进行查询和记录。若是是之前的话须要本身创建一个Dictionary,把人名的name字段做为key,favourite的对象做为value。可是这样有一个问题,若是忽然某一天,我Person里面增长了个字段age,我这个表还要记录每一个人的年龄,供我之后来查询不一样年龄段的人统计使用呢?这下就很尴尬了,由于Dicitionary没办法实现咱们要的这个效果,不过不要紧NSMapTable能够实现,详细请移步关于 NSMapTable
一、根据被观察的object获取其对应的infos set。这个主要做用在于避免屡次对同一个keyPath添加屡次观察,避免crash。由于每调用一次addObserverForKeyPath就要有一个对应的removeObserverForKey。
二、从infos set判断是否是已经观察这次info了,避免重复观察。
三、若是infos为空,就把object当作Key、infos当作Object存入 NSMapTable,
[infos addObject:info];
再把info与infos关联起来。这里听起来可能有点别扭,我作个比喻:object是上面所说的是Rose,infos爱好ObjC,而info则是他的age
四、使用了单例,将观察的信息及关系注册到_FBKVOSharedController中,而且调用iOS自带的KVO方法观察
_FBKVOSharedController做为一个传达者,用来接收和转发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);
//1
// 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];
}
}
复制代码
根据info的状态来选择添加或移除观察者
一、表明全部的观察信息都首先由FBKVOSharedController进行接收,随后进行转发。
//当属性的值发生变化时,自动调用此系统KVO方法
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSKeyValueChangeKey, 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<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];
}
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];
}
}
}
}
}
复制代码
根据info的block回调或者actioin等等进行消息转发。
至此,对FBKVOController的源码剖析基本结束,下面是剖析后的学习
+ (instancetype)personWithName:(NSString *)name
{
DWPerson *person = [[DWPerson alloc] init];
person.name = name;
//一、待会替换
person.family = [[NSMutableArray alloc] init];
[person.family addObject:person];
return [person autorelease];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"%@'s retainCount is %lu",self.name,[self retainCount]];
}
- (void)dealloc
{
self.name = nil;
self.family = nil;
[super dealloc];
}
复制代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
DWPerson *person_1 = [DWPerson personWithName:@"iOS"];
DWPerson *person_2 = [DWPerson personWithName:@"swift"];
DWPerson *person_3 = [DWPerson personWithName:@"android"];
DWPerson *person_4 = [DWPerson personWithName:@"java"];
DWPerson *person_5 = [DWPerson personWithName:@"ruby"];
id list = @[person_1, person_2, person_3, person_4, person_5];
NSLog(@"%@",list);
}
return 0;
}
复制代码
打印:
( "iOS's retainCount is 3", "swift's retainCount is 3", "android's retainCount is 3", "java's retainCount is 3", "ruby's retainCount is 3" )
能够看出每一个person的retainCount为3,由于family持有person,person持有family,若是咱们运用NSHashTable
,则能够完美解决此问题
咱们替换1中的代码,
+ (instancetype)personWithName:(NSString *)name
{
DWPerson *person = [[DWPerson alloc] init];
person.name = name;
person.family = [NSHashTable hashTableWithOptions:NSHashTableWeakMemory];
[person.family addObject:person];
return [person autorelease];
}
复制代码
打印:
( "iOS's retainCount is 2", "swift's retainCount is 2", "android's retainCount is 2", "java's retainCount is 2", "ruby's retainCount is 2" ) 可看出,已解决循环引用
先看一下系统的KVO方法
[testPerson addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
复制代码
这样写keyPath,若是age属性不存在,也不会告知,致使后续的排查困难,但这种低级错误在FBKVOController不复存在,由于其使用了宏定义
FBKVOController中的宏定义
#define FBKVOKeyPath(KEYPATH) \
@(((void)(NO && ((void)KEYPATH, NO)), \
({ const char *fbkvokeypath = strchr(#KEYPATH, '.'); NSCAssert(fbkvokeypath, @"Provided key path is invalid."); fbkvokeypath + 1; })))
#define FBKVOClassKeyPath(CLASS, KEYPATH) \
@(((void)(NO && ((void)((CLASS *)(nil)).KEYPATH, NO)), #KEYPATH))
复制代码
该宏定义使用了C语言的逗号表达式,(3+5,6+8)称为逗号表达式,其求解过程先表达式1,后表达式2,整个表达式值是表达式2的值,如:(3+5,6+8)的值是14,a=(a=3 x 5,a x 4)的值是60,而(a=3 x 5,a x 4)的值是60, a的值是15。
使用逗号表达式,我以为主要是为了FBKVOClassKeyPath
FBKVOClassKeyPath(DWPerson, name)
==(((void)(NO && ((void)((DWPerson *)(nil)).name, NO)), #KEYPATH))
,其会检查DWPerson
中是否有name
属性
FBKVOController经过自释放的机制来实现observer的自动移除,其实就是给observer的类中添加一个FBKVOController的成员变量,而后在FBKVOController中的dealloc移除observer,下面是个例子
#import "DWTestViewController.h"
#import "DWObserViewController.h"
@interface DWTestViewController ()
@property (nonatomic, strong) DWObserViewController *obserVC;
@end
@implementation DWTestViewController
- (instancetype)init
{
self = [super init];
if (nil != self) {
_obserVC = [[DWObserViewController alloc] init];
NSLog(@"DWTestVC建立");
NSLog(@"DWObserVC建立");
}
return self;
}
复制代码
#import "DWObserViewController.h"
@implementation DWObserViewController
- (void)dealloc {
NSLog(@"DWObserVC跟着销毁");
}
复制代码
打印:
2018-02-05 15:32:39.299859+0800 FBKVOController_Demo[6804:208216] DWTestVC建立 2018-02-05 15:32:39.300209+0800 FBKVOController_Demo[6804:208216] DWObserVC建立 2018-02-05 15:32:41.271585+0800 FBKVOController_Demo[6804:208216] DWTestVC销毁 2018-02-05 15:32:46.520148+0800 FBKVOController_Demo[6804:208216] DWObserVC跟着销毁
参考: