回顾

在前面的几篇博客中,已经介绍了KVO的基本使用,如何自定义 KVO,那么本篇博客将分析一下FBKVOController这个优秀的KVO三方库。

iOS底层探索之KVO(一)—KVO简介
iOS底层探索之KVO(二)—KVO原理分析
iOS底层探索之KVO(三)—自定义KVO
iOS底层探索之KVO(四)—自定义KVO

FBKVOController是一个函数式编程实现,不用移除观察者者。

1. FBKVOController简单介绍

FBKVOControllerFacebook开源的一个基于系统KVO实现的框架。支持Objective-CSwift语言。
GitHub地址

键值观察是一种特别有用的技术,用于在模型-视图-控制器应用程序中的层之间进行通信。KVOController 建立在 Cocoa久经考验的键值观察实现之上。它提供了一个简单、现代的 API,这也是线程安全的。

KVOController优点如下:

1.1 KVOController优点

  • 使用blocks、自定义操作或NSKeyValueObserving 回调通知。
  • 不需要额外的移除观察者
  • 在控制器 dealloc 的时候隐式的把观察者移除。
  • 具有防止观察者复活的特殊线程安全的保护机制
  • 有关 KVO 的更多信息,请参阅 Apple 的键值观察简介。

1.2 FBKVOController 使用

FBKVOController的使用起来非常的简单,代码很少,FBKVOController简单使用如下代码所示:

  • FBKVOController 使用
 self.person = [[JPPerson alloc] init];self.person.name = @"RENO";self.person.age = 18;self.person.mArray = [NSMutableArray arrayWithObject:@"1"];[self.kvoCtrl observe:self.person keyPath:@"age" options:0 action:@selector(jp_observerAge)];[self.kvoCtrl observe:self.person keyPath:@"name" options:(NSKeyValueObservingOptionNew) block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {NSLog(@"****%@****",change);}];[self.kvoCtrl observe:self.person keyPath:@"mArray" options:(NSKeyValueObservingOptionNew) block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {NSLog(@"****%@****",change);}];

代码非常的简洁,不用向我们之前使用系统的 KVO那样在dealloc 里面移除观察者,这一波使用就很爽啊!

  • 懒加载初始化
#pragma mark - lazy
- (FBKVOController *)kvoCtrl{if (!_kvoCtrl) {_kvoCtrl = [FBKVOController controllerWithObserver:self];}return _kvoCtrl;
}

2. KVOController 实现分析

2.1 中介者模式

我们平时买房、租房都会找中介,通过中介可以更快更高效的找到合适的房子,也就很多事情中介帮我们去做了,不用我们自己去找房源。

KVOController主要是使用了中介者模式,官方kvo使用麻烦的点在于使用需要三部曲。KVOController核心就是将三部曲进行了底层封装,上层只需要关心业务逻辑。

FBKVOController会进行注册、移除以及回调的处理(回调包括blockaction以及兼容系统的observe回调)。是对外暴露的交互类。使用FBKVOController分为两步:

  • 使用 controllerWithObserver 初始化FBKVOController实例。
  • 使用observe:进行注册。

2.2 FBKVOController 初始化

- (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;
}

_observer是观察者,是FBKVOController的属性,用 weak来修饰

@property (nullable, nonatomic, weak, readonly) id observer;

因为FBKVOController本身被观察者持有了,所以是weak类型的修饰。

_objectInfosMap根据retainObserved进行NSMapTable内存管理/初始化配置,FBKVOController的成员变量。其中保存的是一个被观察者对应多个_FBKVOInfo(也就是被观察对象对应多个keyPath):

  NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;

_FBKVOInfo是放在NSMutableSet中的,说明是去重的。

2.3 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];
}
  • 首先第一步就是做一些判断,容错判断。

  • 构造_FBKVOInfo,保存FBKVOController、keyPath、options以及block

  • 调用_observe:(id)object info:(_FBKVOInfo *)info

  • _FBKVOInfo

@implementation _FBKVOInfo
{@public__weak FBKVOController *_controller;NSString *_keyPath;NSKeyValueObservingOptions _options;SEL _action;void *_context;FBKVONotificationBlock _block;_FBKVOInfoState _state;
}

_FBKVOInfo中保存了一些相关的数据信息

  • 重写isEqualhash方法
- (NSUInteger)hash
{return [_keyPath hash];
}- (BOOL)isEqual:(id)object
{if (nil == object) {return NO;}if (self == object) {return YES;}if (![object isKindOfClass:[self class]]) {return NO;}return [_keyPath isEqualToString:((_FBKVOInfo *)object)->_keyPath];
}

只要_keyPath相同就认为是同一对象

  • _observe: info:
- (void)_observe:(id)object info:(_FBKVOInfo *)info
{// lockpthread_mutex_lock(&_lock);//从TableMap中获取 object(被观察者) 对应的 setNSMutableSet *infos = [_objectInfosMap objectForKey:object];// check for info existence//判断对应的keypath info 是否存在_FBKVOInfo *existingInfo = [infos member:info];if (nil != existingInfo) {//存在直接返回,这里就相当于对于同一个观察者排除了相同的keypath// observation info already exists; do not observe it again// unlock and returnpthread_mutex_unlock(&_lock);return;}// lazilly create set of infos//TableMap数据为空进行创建设置if (nil == infos) {infos = [NSMutableSet set];//<被观察者 - keypaths info>[_objectInfosMap setObject:infos forKey:object];}// add info and oberve//keypaths info添加 keypath info[infos addObject:info];// unlock prior to calloutpthread_mutex_unlock(&_lock);//注册[[_FBKVOSharedController sharedController] observe:object info:info];
}
  • 首先判断kayPath是否已经被注册了,注册了直接返回,这里也就是进行了去重的处理,这一波操作就非常细节。
  • 将构造的_FBKVOInfo信息添加进_objectInfosMap中。
  • 调用_FBKVOSharedController进行真正的注册。
  • member:说明
    member会调用到_FBKVOInfo中的hash以及isEqual进行判断对象是否存在,也就是判断keyPath对应的对象是否存在。

这里注册 [[_FBKVOSharedController sharedController] observe:object info:info]是使用了单例

为什么这里使用单例呢?而不是在外面的调用初始化的时候使用单例呢?

这方法里面使用单例,下次再次使用就不会重复创建了,就是相当于保活了,我们在VC中使用的是FBKVOController的实例对象,会随着VC的销毁而销毁,这个单例观察者会在内部移除,移除不是销毁的意思,只是告诉这个单例,移除对某个对象的观察,例如观察了self.person的属性,最后的dealloc是移除对self.person的观察的意思。这一波操作,又是非常的细节,厉害了!

这里的object参数传入的是什么呢?是 self吗?

不是 selfself.person,why ?小朋友,你现在是否有很多问号?

在我们的印象中,使用KVO添加观察者传入的都是 self啊!但是靓仔,我们这里不是哦!


我们的VC需要的是block的回调,添加观察者是观察self.person的属性变化,所以传入self.person就好了。你内部怎么操作,我VC不管,你只要把改变之后结果告诉我就好了,丢个block 的回调通知我VCOK了!

如图这里的self是指前面的那个单例,就是为了复用,就是:只要添加属性的观察都是使用这个单例,这里通过 keyPath来区分,观察的是不同的属性。

2. 4 KVOController销毁

KVOController的销毁,其实是内部帮我们实现了,所以不用我们手动去销毁。

  • dealloc
  • unobserveAll
- (void)unobserveAll
{[self _unobserveAll];
}
  • _unobserveAll
  • _unobserve:(id)object info:
- (void)_unobserve:(id)object info:(_FBKVOInfo *)info
{// lockpthread_mutex_lock(&_lock);// get observation infosNSMutableSet *infos = [_objectInfosMap objectForKey:object];// lookup registered info instance_FBKVOInfo *registeredInfo = [infos member:info];if (nil != registeredInfo) {[infos removeObject:registeredInfo];// remove no longer used infosif (0 == infos.count) {[_objectInfosMap removeObjectForKey:object];}}// unlockpthread_mutex_unlock(&_lock);// unobserve[[_FBKVOSharedController sharedController] unobserve:object info:registeredInfo];
}

单例去调用内部的移除观察者方法


图中红色框起来的代码,其实就是调用的系统的移除观察者的方法removeObserver: forKeyPath :

由于FBKVOController的实例是VC持有的,所以我们的 VC被dealloc销毁的时候FBKVOController实例也就dealloc了。在这里调用就相当于在 VC 中dealloc中调用了移除是一样的。

单例的生命周期是随着程序的生命周期的,不会销毁,只是传入的观察的对象在内部做了销毁,也就是移除操作,就是说 老铁 我不需要观察了,后续别给我发送消息了。

3. 通过gnustep探索KVO

kvokvc是属于Foundation框架里面的,由于Foundation相关的代码苹果并没有开源,对于它们的探索可以通过gnustep查看原理,gnustep中有一些苹果早期底层的实现。

那么FBKVOController分析就介绍到这了。

通过【gnustep】具体的探索,这里就不过多的描述了,感兴趣的老铁,可以自行去下载Foundation的源码,看看里面的实现,思路都是差不多的!

4. 总结

  • FBKVOController使用了中介者模式,通过函数式编程的思想,把对属性的变化的观察,使用 block通知回调
  • FBKVOController 注册,内部使用了单例,进行复用,通过 keyPath来区分,观察的是不同的属性。
  • 在控制器dealloc 的时候隐式的把观察者移除,其实内部还是调用了系统的移除方法。

更多内容持续更新

iOS底层探索之KVO(五)—FBKVOController分析相关推荐

  1. iOS 底层探索 - 消息转发

    一.动态方法解析流程分析 我们在上一章<消息查找>分析到了动态方法解析,为了更好的掌握具体的流程,我们接下来直接进行源码追踪. 我们先来到 _class_resolveMethod 方法, ...

  2. iOS底层探索二(OC 中 alloc 方法 初探)

    前言 相关文章: iOS底层探索一(底层探索方法) iOS底层探索三(内存对齐与calloc分析) iOS底层探索四(isa初探-联合体,位域,内存优化) iOS底层探索五(isa与类的关系) iOS ...

  3. iOS底层探索(二) - 写给小白看的Clang编译过程原理

    iOS底层探索(一) - 从零开始认识Clang与LLVM 写在前面 编译器是属于底层知识,在日常开发中少有涉及,但在我的印象中,越接近底层是越需要编程基本功,也是越复杂的.但要想提升技术却始终绕不开 ...

  4. iOS 底层探索篇 —— KVC 底层原理

    iOS 底层探索篇 -- KVC 底层原理 1. Method Swizzling的坑与应用 1.1 method-swizzling 是什么? 1.2 坑点 坑点1:method-swizzling ...

  5. iOS底层原理之KVO本质

    面试中常会问道: iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?) 如何手动触发KVO 什么是KVO 首先需要了解KVO基本使用,KVO的全称 Key-Value Observing, ...

  6. iOS 底层原理之 类的原理分析(上)

    首先推荐一下alloc流程中篇,alloc流程下篇,alloc流程上篇,这些对于本文的理解是很有帮助的哦 一.分析类对象的ISA 1.获取ISA地址 首先我们还是先自定义一个对象YCXPerson,打 ...

  7. 【iOS底层】11:消息转发

    一.msgSend消息发送监听 在探索了很多次了lookUpImpOrForward方法中,查找完成后会写入cache 在写入cache中发现有个打印log的操作 我们来看下是否可以通过这个输出到本地 ...

  8. iOS底层-KVO分析与自定义

    KVO分析与自定义 背景 准备 KVO一些细节 KVO探索分析 KVO 底层原理 小结: KVO自定义 自定义KVO要知道: 1,KVO是对setter方法进行观察,过滤实例方法 2,添加KVO(核心 ...

  9. OC底层探索(七) cache_t分析

    cache_t 源码 在OC底层探索(五) 类的结构分析文章中,我们分析objc_class源码时,有提到过其属性 cache_t cache属性的大小为16字节. 那么我们今天就着重分析以下cach ...

  10. 并发编程五:java并发线程池底层原理详解和源码分析

    文章目录 java并发线程池底层原理详解和源码分析 线程和线程池性能对比 Executors创建的三种线程池分析 自定义线程池分析 线程池源码分析 继承关系 ThreadPoolExecutor源码分 ...

最新文章

  1. Best jQuery Plugins of 2010
  2. 开课吧python好吗-开课吧9.9元学Python课程适合哪些人?开课吧靠谱吗?
  3. mysql 以数组的形式插入更新表
  4. html+单选+回显,VUE+elementUI表格多选框实现单选以及数据回显时toggleRowSelection失效问题...
  5. python pprint_如何美观地打印 Python 对象?这个标准库可以简单实现
  6. 云计算安全之CCSKv4.0(201910考的)
  7. 方维分享系统修改,本地安装失败,后台无法登陆
  8. 跳蚤 BZOJ 4310
  9. ios上传图片遇见了一个TimeoutError(DOM Exception 23)异常
  10. 专业管理系统-包含VB源代码(数据库)
  11. 数据库系统概论第五版(第 4 章 数据库安全性)笔记
  12. 电脑开机蓝屏时要怎么解决修复?哪种方便比较好?
  13. Android网络框架(三)——iptables
  14. 单页面应用与多页面的区别与优缺点
  15. js Date 获取 年 月 日
  16. kali2020进入单模式_Kali Linux 2020.3稳定版已发布 支持自动启用HiDPI模式
  17. Matlab龚珀兹曲线模型预测,指数曲线模型的讲解=.pptx
  18. wwdc2019_wwdc 20愿望清单
  19. POWER BI学习之EARLIER()函数
  20. 教程 参数设置_高分辨质谱教程汇总

热门文章

  1. mysql查看当前连接数据
  2. Web负载均衡与分布式架构
  3. 李刚-我的全栈之路导师之一
  4. 在《Windows程序设计》中出现过的消息
  5. WinCE Display驱动开发介绍(转载)
  6. DataSet里的数据写入XML文件
  7. 如何检查Windows网络通信端口占用
  8. 「专题训练」k-Tree(CodeForces Round #247 Div.2 C)
  9. CDH-5.9.2整合spark2
  10. HDOJ 2071 Max Num