结构

Facebook出品的这个KVOController算上.h文件一共只有5个文件,如图:

其中NSObject+FBKVOController 之中只是制作了两个属性

@property (nonatomic, strong) FBKVOController *KVOController;
@property (nonatomic, strong) FBKVOController *KVOControllerNonRetaining;
复制代码

这两个属性关联的FBKVOController都是通过懒加载创建的,关于这两个属性存放的数据差异,我们先放一下。

而在另一个文件FBKVOController中,则存在三个Class,分别为

@implementation _FBKVOInfo
{
@public// 简单的值保存__weak FBKVOController *_controller;// KVO所监听的属性NSString *_keyPath;// KVO的可选项NSKeyValueObservingOptions _options;// 下面试三种不同的处理方式 优先级为 Block > SEL > context// 这三种方式为互斥的,优先执行优先级高的SEL _action;void *_context;FBKVONotificationBlock _block;// 当前Info对应的KVO行为状态_FBKVOInfoState _state;
}@implementation _FBKVOSharedController
{// 通过直接的地址比较而进行保存的一个NSHashTable 保存的是全部的KVO信息NSHashTable<_FBKVOInfo *> *_infos;// 访问上面的_infos的锁pthread_mutex_t _mutex;
}@implementation FBKVOController
{// 使用hash和isEqual来进行校对的MapTable // 其中保存// Key为监听的对象// Value的Set中保存的是KVO信息NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;// 同理也是锁pthread_mutex_t _lock;
}
// 这个对象对应的那个回调响应者(俗称:监听者)
@property (nullable, nonatomic, weak, readonly) id observer;
复制代码

我们先从_FBKVOInfo开始

_FBKVOInfo

_FBKVOInfo 一共8个方法,其中5个是初始化方法,一个Debug文本,一个Hash一个equal判断。
初始化方法就不谈,关于Hash和equal有一篇文章说的很好点这里
总的来说:

Hash是在判断isEqual的必要非充分条件

按照官方的话来说

Returns an integer that can be used as a table address in a hash table structure.

If two objects are equal (as determined by the isEqual: method), they must have the same hash value. This last point is particularly important if you define hash in a subclass and intend to put instances of that subclass into a collection.

If a mutable object is added to a collection that uses hash values to determine the object’s position in the collection, the value returned by the hash method of the object must not change while the object is in the collection. Therefore, either the hash method must not rely on any of the object’s internal state information or you must make sure the object’s internal state information does not change while the object is in the collection. Thus, for example, a mutable dictionary can be put in a hash table but you must not change it while it is in there. (Note that it can be difficult to know whether or not a given object is in a collection.)


非官方译文

返回一个在HashTable结构中用作表地址的integer(没有符号的long)

如果你有两个对象是通过isEqual:来判断的相等,那么你必须要满足两者的hash值为相同。如果你在子类中定义了hash方法,并且把这个子类放到了一个容器集合中,那么这后面的一点对你来说尤为重要(两者hash要相同)。

如果一个可变对象在一个集合中使用hash值来进行定位的话,那么当对象在容器中,可千万不要让这个对象的hash方法返回结果出现改变。(因为hash是索引线索)

所以hash方法的返回结果不应该与任何的内部信息出现依赖,并且在这个对象在集合中的时候应该保证对象的内部信息不要出现改变。

比如:你在HashTable中存在一个可变的字典,那么当这个字典存在于HashTable中的时候,你就不要去改变他(这样做会让判断这个集合中是否存在这个指定对象变得很困难)(PS:经过不准确的测试,set、Dictionary等类型的Mut类型他们的hash值为count)


在这里要提一下在创建 option的描述文本的时候调用了一个这样的方法

__builtin_ctzl

/**返回最后一个1 然后修改传递进来的flags@param ptrFlags 整体的flags@return 获得的最后一个1*/
static NSUInteger enumerate_flags(NSUInteger *ptrFlags)
{NSCAssert(ptrFlags, @"expected ptrFlags");if (!ptrFlags) {return 0;}NSUInteger flags = *ptrFlags;if (!flags) {// 如果全是0 表示解析完成了 返回0return 0;}// 这个相当于把最后一个1 拿到NSUInteger flag = 1 << __builtin_ctzl(flags);// 然后把这个东西从flags中删除掉flags &= ~flag;*ptrFlags = flags;return flag;
}
复制代码

这是处理二进制位的内置函数,关于更多的此类函数相关,可以通过这个链接进行查看 点这里


而关于其中的一个枚举类型,我们先不讲,留到后面再说。

typedef NS_ENUM(uint8_t, _FBKVOInfoState) {_FBKVOInfoStateInitial = 0,// whether the observer registration in Foundation has completed_FBKVOInfoStateObserving,// whether `unobserve` was called before observer registration in Foundation has completed// this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions_FBKVOInfoStateNotObserving,
};
复制代码

其他的貌似没什么难点了 _(:з」∠)_

总体来说: _GFKVOInfo 是一个用以储存KVO信息的包括三种回调、观察的对象信息(包括KVO属性,观察路径等)同时还维护了当前对应KVO的状态信息。

FBKVOController

这个我想先把KVOController先讲一下,然后再回到KVOShareController。见谅

前面我们就能在KVOInfo中找到一个弱指针是这个类型,我们前面大概提了一下这个东西保存的KVO观察者信息的。老套路我们看下他的方法。(属性在前面已经梳理了一遍了啊)

一共18个方法,如下图:

大体分为了四类:

  1. 创建等生命周期
  2. debug信息
  3. 非公开类
  4. 公开类API

我们一类一类来看 首先生命周期中,我们在最开始NSObject那个拓展的时候,有说过存在两种不同的Controller,一种为普通,一种为Noretain。

在这里,就得说一下NSMapTable和NSMutableDictionary的区别了,在一定程度上这两者的感觉是一样的,都是键值存储。但是NSMapTable比NSMutableDictionary 在定制性上要比NSMutableDictionary要好。(PS:NSMapTable 就是可变类型)

但其实在储存性能上来说,NSMutableDictionary或者前面说的哈希表对于NSSet来说都是略微逊色的。所以在非特殊情况下还是以使用NSMutableSet和NSMutableDictionary为主。

- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
{self = [super init];if (nil != self) {_observer = observer;// retainObserved将会影响到Key值的保存特性NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;// Key值都是基于地址进行比较的 而对于Value则是使用hash和equal来进行比较的_objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];pthread_mutex_init(&_lock, NULL);}return self;
}
复制代码

之所以需要一个Weak的,是为了防止在NSObject中监听自身属性的时候出现循环引用,无法释放这一类情况。

再提一次注意在dealloc中释放锁

第二类我们直接跳过

第三类私有API 以及第四类 开放API 其实就是创建对应的KVOInfo然后进行Set的插入。

总体来说:
FBKVOController是通过一个观察者创建的,同时按照不同的需求可以选择对Key值进行retain类型保存还是weak类型保存。
在对象中维护了一张从监听目标到监听属性列表的映射表.
表中包括自身观察者创建的FBKVOController,以及各种KVO属性都存有(_FBKVOInfo)。

_FBKVOSharedController

这个对象是一个单例,虽然infos的创建写了一大堆其实就是创建一个弱类型保存的哈希表.(至于为什么用哈希表 前面讨论MapTable已经说过了)
在其中这个方法中

- (void)observe:(id)object info:(nullable _FBKVOInfo *)info
{if (nil == info) {return;}// 这个哈希表存放的都是_FBKVOInfo// register infopthread_mutex_lock(&_mutex);[_infos addObject:info];pthread_mutex_unlock(&_mutex);// add observer[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];// TOCHECKOUT: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];}
}
复制代码

init表示刚初始化出来,当然每一个创建的都是init状态,但是其中会有在GFKVOController中进行一个射靶,我想各位没有忘记在_GFKVOInfo中的hash和isEqual。
而如果通过射靶得到的还是init说明以前完全没有创建过这个info说明是一个全新的那么就修改他的状态,并添加监听。
但是还存在一种情况。
当在多种线程中操作这个KVO的监听和取消监听的时候,cancel在进行observer之前进行了,也就是说当info传入这个方法的时候info的state发生了变化,变为了_FBKVOInfoStateNotObserving,cacel的代码如下

- (void)unobserve:(id)object info:(nullable _FBKVOInfo *)info
{if (nil == info) {return;}// unregister infopthread_mutex_lock(&_mutex);[_infos removeObject:info];pthread_mutex_unlock(&_mutex);// remove observerif (info->_state == _FBKVOInfoStateObserving) {[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];}info->_state = _FBKVOInfoStateNotObserving;
}
复制代码

当然因为没有发生init变Observing这个行为所以也就不会出现remove行为,但是在逻辑上这个KVO就不应该再存在了,所以我们通过NotObserving属性判断当前的KVO是错误添加。然后进行移除操作。

最后是KVO的系统回调,按照三种不同的回调方法按照优先级进行先后确认,拿到最高优先级的方法并进行回调。

最终梳理

_FBKVOInfo 作为信息承载体使用(Model)使用keypath的hash值进行校对。认定所有keypath相同的对象都相等。
FBKVOController 作为对一个特定观察者的观察对象管理类存在,自身保存一个Map用来做通过监听对象作为键值获取一个_FBKVOInfo的集合用以射靶,如果存在,获取保存在_FBKVOSharedController的哈希表中信息的备份。
_FBKVOSharedController 真正的KVO行为执行者,是KVO行为的派发者。自身保存了所有通过KVOController进行的所有的KVO行为,与众多FBKVOController中保存的_FBKVOInfo是对等的(PS:当出现没有添加KVO就被移除的可能就会出现比FBKVOController多的情况)
NSObject+FBKVOController 定义了两个不同种类的FBKVOController的属性,当对自己属性进行KVO的时候最好使用noreatin版本。

零碎

其实KVOController并不是我最近的主要关注点,一切也都是因缘际会吧。KVOController一直卡住我的点,就是两个宏定义。(我删掉了注释不然看起来实在太多)

#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))
复制代码

((void)(NO && ((void)KEYPATH, NO))
这一块第一个NO和第三个NO是为了让中间的KEYPATH不进行运算,这样就能防止,这里进行了getter方法而产生不必要的问题。相关文档在这里Compile-time Key Paths Verification

至于其他的

NS_ASSUME_NONNULL_BEGIN
// 这中间的所有参数默认为NONULL
NS_ASSUME_NONNULL_END// 表明这个文件必须要在ARC环境下
#if !__has_feature(objc_arc)
#error This file must be compiled with ARC. Convert your project to ARC or specify the -fobjc-arc flag.
#endif
复制代码

后记

严格意义上这是我第一篇完成的博文吧,可能算不上太好,但总是个开始。
我也很庆幸我第一个完成的是KVOController,篇幅之类的也更加容易控制吧。
共勉。

相关文档

关于hash和Equal
二进制位内置函数
基础集合类
Compile-time Key Paths Verification

转载于:https://juejin.im/post/5a31e95ef265da432840de64

62. KVOController详解相关推荐

  1. 欧盟包装指令94/62/EC详解

    欧盟包装指令94/62/EC详解 欧盟包装指令94/62/EC详解,欧盟包装和废弃包装物指令对包装材料进行管制,主要原因在于包装材料常在使用过后,被消费者任意地丢弃至环境土壤中,其中所含的危害物质将会 ...

  2. Android 性能优化(62)---存检测、卡顿优化、耗电优化、APK瘦身——详解篇

    Android 性能优化,内存检测.卡顿优化.耗电优化.APK瘦身--详解篇 自2008年智能时代开始,Android操作系统一路高歌,10年智能机发展之路,如今 Android 9.0 代号P  都 ...

  3. 尺度不变特征变换匹配算法详解

    尺度不变特征变换匹配算法详解 Scale Invariant Feature Transform(SIFT) Just For Fun 对于初学者,从David G.Lowe的论文到实现,有许多鸿沟, ...

  4. 【linux】top命令详解

    1.参数详解 $ top -helpprocps-ng version 3.3.9 Usage:top -hv | -bcHiOSs -d secs -n max -u|U user -p pid(s ...

  5. spring boot 实战 / 可执行war启动参数详解

    概述   上一篇文章<spring boot 实战 / mvn spring-boot:run 参数详解>主要讲解了spring boot 项目基于maven插件启动过程中借助profil ...

  6. 以SIGSEGV为例详解信号处理(与栈回溯)

    以SIGSEGV为例详解信号处理(与栈回溯) 信号是内核提供的向用户态进程发送信息的机制, 常见的有使用SIGUSR1唤醒用户进程执行子程序或发生段错误时使用SIGSEGV保存用户错误现场. 本文以S ...

  7. Hadoop学习之Mapreduce执行过程详解

    一.MapReduce执行过程 MapReduce运行时,首先通过Map读取HDFS中的数据,然后经过拆分,将每个文件中的每行数据分拆成键值对,最后输出作为Reduce的输入,大体执行流程如下图所示: ...

  8. ICLR2020放榜 687篇入选34篇得满分! 且看OpenReview数据图文详解

    深度学习的顶级会议ICLR 2020将于明年 4 月 26 日于埃塞俄比亚首都亚的斯亚贝巴举行. 今日ICLR 2020放榜,最终2594篇论文中共有687篇被接收,其中48篇orals,108篇sp ...

  9. 【OpenCV 4开发详解】分割图像——分水岭法

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  10. 【OpenCV 4开发详解】深度神经网络应用实例

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

最新文章

  1. 雅虎公司C#笔试题,看看你能解答多少? [含答案]
  2. SoapUI实践:自动化测试、压力测试、持续集成 1
  3. 数据库各个派系的起源和应用场景
  4. Backpropagation 总结
  5. HDU4532(组合DP)
  6. 在玩客云上部署code-server
  7. Linux之iostat命令
  8. c语言程序设计实例220,C语言程序设计实例大全(220个例子)
  9. 【NOIP2017】李电下棋
  10. Tukey‘s test方法 异常值
  11. Carryon 数数字——小米 OJ 编程比赛 02 月常规赛(思维)
  12. 搜狗浏览器屏蔽广告插件_搜狗浏览器屏蔽芒果TV视频广告:被判不正当竞争,赔了12万...
  13. 添加权限,获取到用户信息,就用户当前部门进行下拉选择
  14. 服务器系统防火墙设置在哪里设置方法,服务器怎么设置防火墙设置在哪里
  15. Seurat-单细胞文献复现第二弹-02
  16. Trustzone安全内核Open Virtualization SierraTEE向Xilinx ZC702移植手册
  17. 关于YY1139-2013心电诊断设备的共模抑制测试项的理解
  18. 民宿预订小程序开发方案
  19. 根据IP获取坐标(经纬度)
  20. 小Q书桌的下载、安装和使用

热门文章

  1. html/css静态网页制作
  2. Message no. C6015--No valuation variant found for valuation area xxxx
  3. 【意见征集补充】09'博客园T恤设计
  4. CRM中多张关联表join的报表
  5. 2014浙大ACM网络省赛-----Talented Chef问题(Problem -C)
  6. Steinberg Cubase Elements 11 for Mac(音频处理软件)
  7. 通过密钥 SFTP(二)限定用户根目录
  8. react-router 低版本的路由API
  9. 如何将Mac外部驱动器映射到CrossOver容器
  10. Scrum实践:每日站会