KVO自定义的思路
-
isa
指向动态子类 - 子类 很多方法
-
dealloc
-class
-setName
-LGPerson
- 消息回调 remove
KVO自定义动态生成子类
上篇文章,我们对KVO
的底层进行了详细的分析,那么这篇文章,就仿照 KVO 的实现
进行自定义。因为在底层中,KVO是以 NSObject+NSKeyValueObserving
分类的形式展示出来的,那么我们自定义,也先创建 NSObject+LGKVO
的分类。
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
- 验证是否存在
setter方法
#pragma mark - 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
Class superClass = object_getClass(self);
SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
if (!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"老铁没有当前%@的setter",keyPath] userInfo:nil];
}
}
下面验证judgeSetterMethodFromKeyPath
方法
//LGPerson.h文件,name为成员变量
@interface LGPerson : NSObject{
@public
NSString *name;
}
//添加KVO属性监听
[self.person lg_addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
//更改name的值
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person->name = [NSString stringWithFormat:@"%@ *", self.person.kcName];
}
运行工程发生崩溃,成功验证judgeSetterMethodFromKeyPath
方法
- KVO核心-
isa_siwziling
动态生成子类
思路:
1、验证动态子类是否已经创建
如果是已创建了,就直接返回
如果是未创建,就进入第二步
2、创建子类
先申请类,需要父类、新类的名称、开辟新空间
再注册类
最后添加方法,需要 class 、 setter
方法
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// 防止重复创建生成新类
if (newClass) return newClass;
/**
* 如果内存不存在,创建生成
* 参数一: 父类
* 参数二: 新类的名字
* 参数三: 新类的开辟的额外空间
*/
// 2.1 : 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 : 注册类
objc_registerClassPair(newClass);
// 2.3.1 : 添加class : class的指向是LGPerson
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
// 2.3.2 : 添加setter
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
return newClass;
}
#pragma mark - 从get方法获取set方法的名称 key ===>>> setKey:
static NSString *setterForGetter(NSString *getter){
if (getter.length <= 0) { return nil;}
NSString *firstString = [[getter substringToIndex:1] uppercaseString];
NSString *leaveString = [getter substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}
下面测试添加的方法是否能够执行,这里测试的是lg_setter
方法
// NSObject+LGKVO.m文件
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"来了:%@",newValue);
}
// LGPerson.h文件
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *nickName;
// 添加KVO监听
[self.person lg_addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];
// 给属性变量设值
self.person.nickName = [NSString stringWithFormat:@"%@ +", self.person.nickName];
// 运行工程,控制台成功打印,执行到lg_setter方法
2021-09-06 23:18:16.159342+0800 003---自定义KVO[23254:14997428] 来了:(null) +
自定义KVO的setter
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"来了:%@",newValue);
// 4: 消息转发 : 转发给父类
// 改变父类的值 --- 可以强制类型转换
void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
//objc_msgSendSuper(&superStruct,_cmd,newValue)
lg_msgSendSuper(&superStruct,_cmd,newValue);
// 既然观察到了,下一步不就是回调 -- 让我们的观察者调用
// - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
// 1: 拿到观察者
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
// 2: 消息发送给观察者
SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
}
下面验证LGPerson
的setNickName
方法是否执行?
// LGPerson.m文件
- (void)setNickName:(NSString *)nickName{
NSLog(@"来到 LGPerson 的setter方法 :%@",nickName);
_nickName = nickName;
}
//同上给person添加nickName属性的KVO监听,运行工程,控制台打印如下
2021-09-06 23:36:41.956811+0800 003---自定义KVO[23351:15010309] 来了:(null) +
2021-09-06 23:36:41.957009+0800 003---自定义KVO[23351:15010309] 来到 LGPerson 的setter方法 :(null) +
2021-09-06 23:36:41.957269+0800 003---自定义KVO[23351:15010309] {
nickName = "(null) +";
}
从打印信息中可以看出,执行了setNickName
方法
自定义KVO的多元素观察
查看isa
的指向
这步需要新建一个LGKVOInfo
继承于NSObject
,用来保存观察者信息,将多个LGKVOInfo
实例添加到数组中,再通过关联对象
保存到self中进行相关的操作。 详细代码如下
// LGKVOInfo.h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_OPTIONS(NSUInteger, LGKeyValueObservingOptions) {
LGKeyValueObservingOptionNew = 0x01,
LGKeyValueObservingOptionOld = 0x02,
};
@interface LGKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) LGKeyValueObservingOptions options;
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options;
@end
NS_ASSUME_NONNULL_END
// LGKVOInfo.m文件
#import "LGKVOInfo.h"
@implementation LGKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options{
self = [super init];
if (self) {
self.observer = observer;
self.keyPath = keyPath;
self.options = options;
}
return self;
}
@end
// 4: 保存观察者信息
LGKVOInfo *info = [[LGKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options];
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (!observerArr) {
observerArr = [NSMutableArray arrayWithCapacity:1];
[observerArr addObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
实现子类方法
在动态生成子类时,会重写class和setter
方法,它们的IMP
分别指向lg_class和lg_setter
函数地址。通过关联对象保存到self
中,这个就和上篇在分析KVO
时那样,使用中间类重写后的class方法,获取的还是原始类对象
,就好像KVO所做的一切都不存在似的。
lg_class
Class lg_class(id self,SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
-
lg_setter
思路:
1、消息转发:转发给父类,改变父类的值
2、获取观察者
3、对新、旧值进行处理
4、发送消息
给观察者
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"来了:%@",newValue);
// 1: 消息转发 : 转发给父类
// 改变父类的值 --- 可以强制类型转换
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
//objc_msgSendSuper(&superStruct,_cmd,newValue)
lg_msgSendSuper(&superStruct,_cmd,newValue);
// 2: 拿到观察者
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
for (LGKVOInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
//3 对新旧值进行处理
if (info.options & LGKeyValueObservingOptionNew) {
[change setObject:newValue forKey:NSKeyValueChangeNewKey];
}
if (info.options & LGKeyValueObservingOptionOld) {
[change setObject:@"" forKey:NSKeyValueChangeOldKey];
if (oldValue) {
[change setObject:oldValue forKey:NSKeyValueChangeOldKey];
}
}
// 4: 消息发送给观察者
SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:change:context:);
objc_msgSend(info.observer,observerSEL,keyPath,self,change,NULL);
});
}
}
}
移除观察者
思路:
1.、移除观察者;
2、isa指回原始类对象
// 移除观察者方法
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (observerArr.count<=0) {
return;
}
for (LGKVOInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
[observerArr removeObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
break;
}
}
if (observerArr.count<=0) {
// 指回给父类
Class superClass = [self class];
object_setClass(self, superClass);
}
}
那么到了这里,KVO 的自定义
就完成了,那么接下来就使用下
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[LGPerson alloc] init];
NSLog(@"注册观察者之前:%s",object_getClassName(self.person));
[self.person lg_addObserver:self forKeyPath:@"nickName" options:(LGKeyValueObservingOptionNew|LGKeyValueObservingOptionOld) context:NULL];
NSLog(@"注册观察者之后:%s",object_getClassName(self.person));
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person.nickName = @"kc";
}
#pragma mark - KVO回调
- (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
- (void)dealloc{
NSLog(@"移除观察者之前:%s",object_getClassName(self.person));
[self.person lg_removeObserver:self forKeyPath:@"nickName"];
NSLog(@"end");
NSLog(@"移除观察者之后:%s",object_getClassName(self.person));
}
// 控制台打印信息
2021-09-07 21:23:24.849907+0800 003---自定义KVO[29817:15438088] 注册观察者之前:LGPerson
2021-09-07 21:23:24.850664+0800 003---自定义KVO[29817:15438088] 注册观察者之后:LGKVONotifying_LGPerson
2021-09-07 21:23:26.342697+0800 003---自定义KVO[29817:15438088] 来了:kc
2021-09-07 21:23:26.342972+0800 003---自定义KVO[29817:15438088] 来到 LGPerson 的setter方法 :kc
2021-09-07 21:23:26.343194+0800 003---自定义KVO[29817:15438180] {
new = kc;
old = "";
}
2021-09-07 21:23:28.312150+0800 003---自定义KVO[29817:15438088] 移除观察者之前:LGKVONotifying_LGPerson
2021-09-07 21:23:28.312708+0800 003---自定义KVO[29817:15438088] 移除观察者之后:LGPerson
自定义KVO的函数式
函数式编程是把运算过程
写成一系列嵌套的函数
,然后进行调用。函数式编程支持函数
作为第一类对象,也可以被称为闭包或者仿函数(functor)对象
。
上文通过lg_observeValueForKeyPath:ofObject:change:context:
这种调用方式很不方便。所以我们引入函数式思想
,通过block
来实现监听的回调。接下来我们就进行优化。
- 在
LGKVOInfo.h
中,添加LGKVOBlock
@interface LGLKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, copy) LGKVOBlock handleBlock;
@end
@implementation LGLKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(LGKVOBlock)block {
if (self=[super init]) {
_observer = observer;
_keyPath = keyPath;
_handleBlock = block;
}
return self;
}
@end
- 在
NSObject+LGKVO.h
中,lg_addObserver
方法也添加 block
typedef void(^LGKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
@interface NSObject (LGKVO)
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block;
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
- 在
NSObject+LGKVO.m
中,lg_addObserver
方法调整
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block{
// 1: 验证是否存在setter方法 : 不让实例进来
[self judgeSetterMethodFromKeyPath:keyPath];
// 2: 动态生成子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3: isa的指向 : LGKVONotifying_LGPerson
object_setClass(self, newClass);
// 4: 保存信息
LGKVOInfo *info = [[LGKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath handleBlock:block];
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (!mArray) {
mArray = [NSMutableArray arrayWithCapacity:1];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[mArray addObject:info];
}
- 在
NSObject+SSLKVO.m
中,lg_removeObserver
方法调整
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (observerArr.count<=0) {
return;
}
for (LGKVOInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
[observerArr removeObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
break;
}
}
if (observerArr.count<=0) {
// 指回给父类
Class superClass = [self class];
object_setClass(self, superClass);
}
}
- 在
NSObject+LGKVO.m
中,lg_setter
方法调整无需调用KVO
回调方法,改为调用info中保存的block
即可
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"来了:%@",newValue);
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
// 消息转发 : 转发给父类
// 改变父类的值 --- 可以强制类型转换
void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
//objc_msgSendSuper(&superStruct,_cmd,newValue)
lg_msgSendSuper(&superStruct,_cmd,newValue);
// 信息数据回调
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
for (LGKVOInfo *info in mArray) {
if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
info.handleBlock(info.observer, keyPath, oldValue, newValue);
}
}
}
- 优化后再在控制器里面调用就会简洁很多,代码如下
@implementation LGViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[LGPerson alloc] init];
[self.person lg_addObserver:self forKeyPath:@"nickName" block:^(id _Nonnull observer, NSString * _Nonnull keyPath, id _Nonnull oldValue, id _Nonnull newValue) {
NSLog(@"回调方法%@-%@",oldValue,newValue);
}];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person.nickName = @"kc";
}
- (void)dealloc{
[self.person lg_removeObserver:self forKeyPath:@"nickName"];
}
//控制台成功打印回调方法
2021-09-07 21:47:01.604882+0800 004---自定义函数式KVO[30135:15462500] 来了:kc
2021-09-07 21:47:01.605142+0800 004---自定义函数式KVO[30135:15462500] 来到 LGPerson 的setter方法 :kc
2021-09-07 21:47:01.605320+0800 004---自定义函数式KVO[30135:15462500] 回调方法(null)-kc
自定义KVO自动销毁
在使用KVO
的时候,必须手动移除观察者
,有时候可能会把这个环节给忽略掉,那么我们是否可以重写子类的dealloc
方法,在需要其销毁的时候自动移除观察者?
- 动态生成子类:首先在
createChildClassWithKeyPath
方法里面添加dealloc
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// 防止重复创建生成新类
if (newClass) return newClass;
/**
* 如果内存不存在,创建生成
* 参数一: 父类
* 参数二: 新类的名字
* 参数三: 新类的开辟的额外空间
*/
// 2.1 : 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 : 注册类
objc_registerClassPair(newClass);
// 2.3.1 : 添加class : class的指向是LGPerson
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
// 2.3.2 : 添加setter
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
// 2.3.3 : 添加dealloc
SEL deallocSEL = NSSelectorFromString(@"dealloc");
Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
const char *deallocTypes = method_getTypeEncoding(deallocMethod);
class_addMethod(newClass, deallocSEL, (IMP)lg_dealloc, deallocTypes);
return newClass;
}
- 动态生成子类时添加的
dealloc
方法,IMP指向lg_dealloc
函数地址
static void lg_dealloc(id self,SEL _cmd) {
Class superClass = [self class];
object_setClass(self, superClass);
}
- 自动销毁机制的使用,直接在控制器里面调用
dealloc
方法
- (void)dealloc{
NSLog(@"销毁 走你");
}
运行工程,进行lldb调试
成功执行到lg_dealloc
销毁方法中,并且isa指针重新指向LGPerson
FBKVO的实现思想
FBKVOController
是Facebook
开源的一个基于系统KVO
实现的框架。支持Objective-C
和Swift
语言。gitHub 的下载地址
FBKVOController 的优点:
- 不需要手动移除观察者,框架
自动
帮我们移除观察者
-
函数式编程
,可以一行代码实现系统KVO的三个步骤。 - 实现
KVO
与事件发生处的代码上下文相同,不需要跨方法传参数; - 增加了
block
和SEL
自定义操作对NSKeyValueObserving
回调的处理支持,提升使用 KVO的体验; - 每一个
keyPath
会对应一个block
或者SEL
,不需要在block
中使用 if 判断 keyPath,也就是说,不需要统一的observeValueForKeyPath
方法里写 if 判断; - 可以同时对一个对象的
多个属性进行监听
,写法简洁。 -
线程安全
。
FBKVOController
的使用,这里是使用的block回调
代码如下
#import "ViewController.h"
#import <FBKVOController.h>
#import "LGPerson.h"
@interface ViewController ()
@property (nonatomic, strong) FBKVOController *kvoCtrl;
@property (nonatomic, strong) LGPerson *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[LGPerson alloc] init];
[self.kvoCtrl observe:self.person keyPath:@"name" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(@"****%@****",change);
}];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person.name = @"cooci";
}
- (FBKVOController *)kvoCtrl{
if (!_kvoCtrl) {
_kvoCtrl = [FBKVOController controllerWithObserver:self];
}
return _kvoCtrl;
}
@end
除了block方式
调用,还可以使用其他方式
实现
- 使用
action回调
[self.kvoCtrl observe:self.person keyPath:@"age" options:NSKeyValueObservingOptionNew action:@selector(lg_observerAge)];
- 对
可变数组
的监听
[self.kvoCtrl observe:self.person keyPath:@"mArray" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(@"****%@****",change);
}];
FBKVOController
源码解析
- 初始化
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
{
self = [super init];
if (nil != self) {
// 一般情况下observer会持有FBKVOController,为了避免循环引用,此处的_observer的内存管理语义是弱引用
_observer = observer;
// 定义NSMapTable key的内存管理策略,默认情况传入的参数retainObserved = YES
NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
// 创建NSMapTable key为id类型,value 为 NSMapTableSet类型
_objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
// 初始化互斥锁,避免多线程间的数据竞争
pthread_mutex_init(&_lock, NULL);
}
return self;
}
- 注册观察者
- 在
_FBKVOInfo
类中,存储了观察者信息,包含:FBKVOController
、keyPath
、options
、block
等; - 将
observe
和info
进行关联
- (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];
}
-
observe
和info
的关联详情
- 在
FBKVOController
初始化的时候,就持有NSMapTable
,使用NSMapTable
代替关联对象; - 然后把
object
作为key
,获取NSMutableSet
类型的value
,同时NSMutableSet
中存储_FBKVOInfo
; - 如果
infos
为空,进行创建,并添加到NSMapTable
中; - 当
infos
不为空之后,将info对象
添加到NSMutableSet
类型的infos
中; - 中间者
_FBKVOSharedController
是单例模式,负责真正的KVO操作
;
- (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];
}
中间者_FBKVOSharedController分析
- 注册系统KVO
- 传入的
object
为实例对象,调用系统KVO
的注册观察者,开始对属性监听; - 传入
addObserver
方法的self
为当前单例的中介者; - 中间者持有
NSHashTable
,以弱引用的方式存储_FBKVOInfo
; - 通过
_FBKVOInfo的_state
成员变量,根据枚举值_FBKVOInfoStateInitial
、_FBKVOInfoStateObserving
、_FBKVOInfoStateNotObserving
决定新增或删除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];
}
}
- 实现回调
- 通过
info
获取FBKVOController
; - 从
FBKVOController
中,获取observer
,也就是真正的VC
; - 通过
info
获取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];
}
}
}
}
}
- 销毁
- (void)dealloc
{
[self unobserveAll];
pthread_mutex_destroy(&_lock);
}
- 拷贝自身持有的
NSMapTable
到临时表,然后将其清空,再遍历临时表,从单例的中介者中,取消实例对象
和infos
的关联
- (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];
}
}
- 移除系统KVO的监听
- 调用系统KVO的
removeObserver
方法,移除观察者 - 销毁流程:
VC销毁 --> FBKVOController销毁 --> 中间者的unobserve方法 --> 移除观察者
- (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;
}
}
GNU关于KVO源码
- 从
NSKeyValueObserving.m
文件入口开始查看
// GNU源码入口
@implementation NSObject (NSKeyValueObserving)
- (void) observeValueForKeyPath: (NSString*)aPath
ofObject: (id)anObject
change: (NSDictionary*)aChange
context: (void*)aContext
{
[NSException raise: NSInvalidArgumentException
format: @"-%@ cannot be sent to %@ ..."
@" create an instance overriding this",
NSStringFromSelector(_cmd), NSStringFromClass([self class])];
return;
}
@end
// 具体实现
@implementation NSObject (NSKeyValueObserverRegistration)
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
GSKVOInfo *info;
GSKVOReplacement *r;
NSKeyValueObservationForwarder *forwarder;
NSRange dot;
// 准备阶段
setup();
[kvoLock lock];
// Use the original class isa_siwziling动态生成子类,主要是类的开辟的一些处理
r = replacementForClass([self class]);
/*
* Get the existing observation information, creating it (and changing
* the receiver to start key-value-observing by switching its class)
* if necessary.
*/
info = (GSKVOInfo*)[self observationInfo];
if (info == nil)
{
info = [[GSKVOInfo alloc] initWithInstance: self];
// 关联info
[self setObservationInfo: info];
object_setClass(self, [r replacement]);
}
/*
* Now add the observer. 多个键值的keyPath的处理
*/
dot = [aPath rangeOfString:@"."];
if (dot.location != NSNotFound)
{
forwarder = [[NSKeyValueObservationForwarder alloc]
initWithKeyPath: aPath
ofObject: self
withTarget: anObserver
context: aContext];
[info addObserver: anObserver
forKeyPath: aPath
options: options
context: forwarder];
}
else
{
// 重写当前set方法
[r overrideSetterFor: aPath];
[info addObserver: anObserver
forKeyPath: aPath
options: options
context: aContext];
}
[kvoLock unlock];
}
- 移除KVO源码如下
/*
* removes the observer
*/
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
GSKVOPathInfo *pathInfo;
[iLock lock];
pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
if (pathInfo != nil)
{
unsigned count = [pathInfo->observations count];
pathInfo->allOptions = 0;
while (count-- > 0)
{
GSKVOObservation *o;
o = [pathInfo->observations objectAtIndex: count];
if (o->observer == anObserver || o->observer == nil)
{
[pathInfo->observations removeObjectAtIndex: count];
if ([pathInfo->observations count] == 0)
{
NSMapRemove(paths, (void*)aPath);
}
}
else
{
pathInfo->allOptions |= o->options;
}
}
}
[iLock unlock];
}
- GNU使用如下