系统的KVO的用法和现象
有一个KVOModel类,只要一个名字为name的属性,现在要监听name属性的变化,先调用系统的KVO方法来运行,如下
KVOModel *model = [KVOModel new];
model.name = @"1";
NSLog(@"---%@---",object_getClass(model));
/**
model:要监听的类
self:监听的回调方法在哪个类里面写
name:要监听的属性的名字
options:要监听的项目,这个地方就是新旧值
context:不是很明白,感觉可以用来区分一些东西
**/
[model addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
NSLog(@"---%@---",object_getClass(model));
model.name = @"2";
NSLog(@"---%@---",object_getClass(model));
看一下代码的打印结果
2018-07-24 22:29:37.365529+0800 Test[2532:260795] ---KVOModel---
2018-07-24 22:29:37.366037+0800 Test[2532:260795] ---NSKVONotifying_KVOModel---
2018-07-24 22:29:37.366246+0800 Test[2532:260795] ---NSKVONotifying_KVOModel---
可以看到,调用了系统的addObserver方法之后,model的类型就变了,变成了NSKVONotifying_KVOModel类型了。这个地方具体的内部实现方式,通过其他的KVO原理相关的文章都可以查询得到,在此做一个简单的说明:KVO是系统使用了runtime的机制,动态的生成了一个NSKVONotifying_KVOModel类,继承自KVOModel,重写了要监听属性的set方法,这个就是为什么通过成员变量改变属性的值是触发不了KVO的,只有通过存取方法才能触发KVO的原因。并且将原类的isa指针指向了新类,也即是为什么在调用了系统的Observer方法之后,mode的类型改变了。
自定义KVO的实现
弄清楚了上面的一些原理和步骤之后,现在自定义实现一个KVO,如下,自定义的KVODefine_addObserver来替代系统的addObserver的功能,冬天生成的类名的话没有系统那样NSKVONotifying_类这样的名字,使用了自己的KVODefine_类名字来命名
1:有一个NSObject的分类NSObject (KVODefine),如下,具体的注释在代码里面都有
//NSObject+KVODefine.h 头文件
#import <Foundation/Foundation.h>
@interface NSObject (KVODefine)
- (void)KVODefine_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
@end
#import "NSObject+KVODefine.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation NSObject (KVODefine)
/**
主要思路
1:使用runtime动态生成一个类B,继承自原类A
2:给B类添加了一个set方法
3:将原类A的isa指针指向B类
**/
- (void)KVODefine_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
//得到原类--> 等价于object_getClass(self);
Class oldClass = [self class];
//得到新类的名字
NSString *newClassName = [@"KVODefine_" stringByAppendingString:NSStringFromClass(oldClass)];
Class newClass = objc_getClass(newClassName.UTF8String);
if (!newClass){
//创建新类继承自oldClass,名字为 KVODefine_原类
newClass = objc_allocateClassPair(oldClass, newClassName.UTF8String, 0);
//注册新类
objc_registerClassPair(newClass);
}
//得到setter方法的名称
NSString *setterName = [NSString stringWithFormat:@"set%@%@:",[[keyPath substringToIndex:1]uppercaseString],[keyPath substringFromIndex:1]];
//获得原先setter方法的参数类型和返回值类型;
Method method = class_getInstanceMethod(oldClass, NSSelectorFromString(setterName));
const char *types = method_getTypeEncoding(method);
//动态添加一个方法,方法的实现就是下面的setKVOMethod
class_addMethod(newClass, NSSelectorFromString(setterName), (IMP)setKVOMethod, types);
//将self的isa指针指向新类,也就是为了在调用setter方法的时候,会去新类中查找
object_setClass(self, newClass);
/**下面几个方法,只是为了取数据方便而已**/
//在下面的setKVOMethod获取原类的旧值要使用
objc_setAssociatedObject(self, "keyPath", keyPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//设置新值的时候要使用
objc_setAssociatedObject(self, "setterName", setterName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//观察者,是在那个类里面实现的回调方法吧
objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//传进来的context,最后要传回去
objc_setAssociatedObject(self, "context", (__bridge id _Nullable)(context), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
void setKVOMethod(id self,SEL _cmd,id newValue){ //此时这个self的isa指针是指向新类的
NSString *keyPath = objc_getAssociatedObject(self, "keyPath");
NSString *setterName = objc_getAssociatedObject(self, "setterName");
id observer = objc_getAssociatedObject(self, "observer");
id context = objc_getAssociatedObject(self, "context");
//存一下新类,在最后的self的isa指针还是要指向这个新类
Class newClass = [self class];
//将self的isa指针指向原类,为了获得旧值
object_setClass(self, class_getSuperclass(newClass));
//获取到旧值
NSString *oldValue = objc_msgSend(self, NSSelectorFromString(keyPath));
//将新值赋值一下
objc_msgSend(self, NSSelectorFromString(setterName), newValue);
NSMutableDictionary *change = @{}.mutableCopy;
//设置旧值
if (oldValue){
change[NSKeyValueChangeOldKey] = oldValue;
}
//设置新值
if (newValue){
change[NSKeyValueChangeNewKey] = newValue;
}
//调用observer里面的回调方法
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),keyPath,observer,change,context);
//self的isa指针指向新类
object_setClass(self, newClass);
}
最后这句话,将self的isa指针指向了新类,要是就但从实现KVO的功能来说,是不需要的,但是从上面可以知道,系统最终的那个对象的isa指针是指向新类的,所以这个地方为了和系统保持统一,就将isa指针的指向修改了一下。
测试代码:
- (void)viewDidLoad {
[super viewDidLoad];
KVOModel *model = [KVOModel new];
model.name = @"1";
NSLog(@"---%@---",object_getClass(model));
/**
model:要监听的类
self:监听的回调方法在哪个类里面写
name:要监听的属性的名字
options:要监听的项目,这个地方就是新旧值
context:不是很明白,感觉可以用来区分一些东西
**/
[model KVODefine_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld context:nil];
NSLog(@"---%@---",object_getClass(model));
model.name = @"2";
NSLog(@"---%@---",object_getClass(model));
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"name"]){
NSLog(@"new:%@--old:%@",change[NSKeyValueChangeNewKey],change[NSKeyValueChangeOldKey]);
}
}
最终的运行结果:
2018-07-24 22:48:40.797558+0800 Test[2798:304064] ---KVOModel---
2018-07-24 22:48:40.797827+0800 Test[2798:304064] ---KVODefine_KVOModel---
2018-07-24 22:48:40.798004+0800 Test[2798:304064] new:2--old:1
2018-07-24 22:48:40.798270+0800 Test[2798:304064] ---KVODefine_KVOModel---
总的来说,1:动态生成一个继承自原类的类,2:重写set方法,3:修改isa指针的指向。最后一步不可少,因为重写的是新类的set方法,要是没有最后一步的话,就没法触发调用动态添加的那个set方法了。一点点小认识,如有不正确的地方,欢迎指正。