ios kvo 的实现原理,自定义kvo
kvo的使用:
//注册
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
//回调
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
//移除
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
原理:首先要知道 kvo 是通过 runtime 实现的
在使用kvo观察某对象的时候(例:对象 Person),系统会通过 runtime 动态创建这个对象的子类 NSKVONotifying_Person,然后重写了 setter 和 class 方法 (重写一词不是很准确,我们可以认为是拦截或替换),重写的class方法会返回父类对象 Person 类,让我们以为就是对象Person, 并没有NSKVONotifying_Person这个类的存在。(重写class 方法有待考究,后面会说为什么有待考究)
我们猜测 在NSKVONotifying_Person重写setter方法的时候,会调用系统提供的两个方法willChangeValueForKey和didChangeValueForKey,大致如下
- (void)setXxx:(NSString *)xxx {
[self willChangeValueForKey:@"xxx"];
[super setValue:nameStr forKey:@"xxx"];
[self didChangeValueForKey:@"xxx"];
}
willChangeValueForKey 和 didChangeValueForKey
这两个方法会调用 observeValueForKeyPath ,用来通知 观察对象的value 变化(只有这两个方法都调用了,才会 触发 observeValueForKeyPath 的回调)
思路虽然是这么个思路,但是其实系统实现kvo 不可能只是拦截下setter方法这么简单,具体是怎么实现的我也没看过源码,不清楚,但是我们却可以根据这种思路去模拟一个kvo。
下面我们来验证大致理论,并根据其原理自定义KVO,以便更好地理解KVO实现原理。
首先验证是否产生新的子类,很简单,addObserver后断点查看控制台 image.png 然后在observeValueForKeyPath回调后我们看下堆栈信息 image.png可以发现 KVO 的实现,是有一套自己专属类的,NSSetObjectValueAndNotify 是 OC 对象类型的属性的封装,即监听的属性如果是 Object 类型,则使用NSSetObjectValueAndNotify,如果是 int,则使用NSSetIntValueAndNotify ,还有一些其他的不一一例举。然后通过NSKeyValueObservingPrivate 调用 changeValueForKeys,最终调用 observeValueForKeyPath 通知observer回调。
observeValueForKeyPath 其实就是一个协议,最终会用我们添加的observer去调用observeValueForKeyPath
接下来我们来模拟下KVO的实现过程,以更好的理解kvo的实现原理
首先runtime动态创建类,我们自定义一个类,以block的方式进行kvo的回调,这里我用的 SEL 作为 key (其实没多大意义,和用字符串差不多,本来是要用 SEL 做监听字段验证的,写着写着写忘了)
[BSNotifacation BSNotifacationAddObsever:self object:person1 keyForSel:@selector(name) callBack:^(id _Nonnull oldValue, id _Nonnull newValue) {
NSLog(@"\nnoti person1:\noldValue = %@\nnewValue = %@",oldValue,newValue);
}];
然后实现方法
+(void)BSNotifacationAddObsever:(id)obsever object:(id)object keyForSel:(SEL )keyForSel callBack:(void(^)(id oldValue,id newValue))change{
// ==================================================
// 新建 notiobject 类,用于处理回调,动态生成对象 等操作
// 思路:调用一次监听 就生成一个 自定义的监听对象
// 监听对象初始化后,已经重写了被监听对象object的class方法和setter方法
// 所以不要轻易调用 class ,应使用 object_getClass 获取 class ,
// 以免使用的时候混淆 class 的真正面目
// ==================================================
BSNotifacationObject *notiObj = [BSNotifacationObject notiObjectWithObsever:obsever object:object keyForSel:keyForSel callBack:change];
// ==================================================
// 将 notiObj(BSNotifacationObject) 添加到数组中,然后 关联给 objcCls
// 目的:这样一个实例对象的所有监听就都和他的 isa 相关联,
// 回调的时候,可以根据isa指向的class直接拿到数组,
// 再根据 方法名 去进行批量的block回调
// ==================================================
Class objcCls = object_getClass(object);
NSMutableArray *mutNotiObj = objc_getAssociatedObject(objcCls, "notiObj");
if (!mutNotiObj) {
mutNotiObj = [NSMutableArray array];
}
[mutNotiObj addObject:notiObj];
objc_setAssociatedObject(objcCls, "notiObj", mutNotiObj, OBJC_ASSOCIATION_RETAIN);
// NSLog(@"关联对象:%@",objc_getAssociatedObject(objcCls, "notiObj"));
}
然后对 自定义监听类 BSNotifacationObject.m 实现
+(instancetype)notiObjectWithObsever:(id)obsever object:(id)object keyForSel:(SEL)keyForSel callBack:(void (^)(id, id))change{
return [[[self class]alloc]initNotiObjectWithObsever:obsever object:object keyForSel:keyForSel callBack:change];
}
-(instancetype)initNotiObjectWithObsever:(id)obsever object:(id)object keyForSel:(SEL)keyForSel callBack:(void (^)(id, id))change{
self = [super init];
if (self) {
self.change = change;
self.obsever = obsever;
self.object = object;
self.keyForSel = keyForSel;
self.propertyName = NSStringFromSelector(keyForSel);
[self dynamicCreatObject];
[self exchangeDealloc:self.object];
}
return self;
}
#pragma mark - 获取 setter 和 getter 方法
// 根据 keyForSel 获取 setter name
-(SEL)selForGetter{
NSString *name = NSStringFromSelector(self.keyForSel);
NSString *firsWord = [name substringWithRange:NSMakeRange(0, 1)];
firsWord = firsWord.uppercaseString;
NSString *otherWord = [name substringWithRange:NSMakeRange(1, name.length - 1)];
NSString *selectorName = [@"get" stringByAppendingFormat:@"%@%@",firsWord,otherWord];
return NSSelectorFromString(selectorName);
}
// 根据 keyForSel 获取 getter name
-(SEL)selForSetter{
NSString *name = NSStringFromSelector(self.keyForSel);
NSString *firsWord = [name substringWithRange:NSMakeRange(0, 1)];
firsWord = firsWord.uppercaseString;
NSString *otherWord = [name substringWithRange:NSMakeRange(1, name.length - 1)];
NSString *selectorName = [@"set" stringByAppendingFormat:@"%@%@:",firsWord,otherWord];
return NSSelectorFromString(selectorName);
}
#pragma mark - 动态创建 子类 ,并为其添加方法
-(void)dynamicCreatObject{
/// 动态生成继承 self.object 的子类 BSNoti_object
/// 如果 noti 类 不存在,则创建 noti 类
Class class = object_getClass(self.object);
NSString *notiClassName = NSStringFromClass(class);
if (![notiClassName hasPrefix:@"BSNoti_"]) {
notiClassName = [NSString stringWithFormat:@"BSNoti_%@",NSStringFromClass(class)];
}
const char *charClassName = [notiClassName cStringUsingEncoding:NSUTF8StringEncoding];
Class notiClass = objc_getClass(charClassName);
if (!notiClass) {
notiClass = objc_allocateClassPair(class, charClassName, 0);
objc_registerClassPair(notiClass);
}
self.notiClass = notiClass;
// 将 object 的isa 指向 notiClass,
// 即 调用 object 的时候 其实object已经变成了BSNoti_object
object_setClass(self.object, notiClass);
/// 如果方法还没有,则 给 notiClass 增加 方法
if (![self notiClass:notiClass hasSel:[self selForSetter]]) {
// 将 keyForSel 的 IMP 改成 自定义 BSSetter 方法
Class superClass = class_getSuperclass(notiClass);
Method keyMethod = class_getInstanceMethod(superClass, [self selForSetter]);
const char *keyMethodType = method_getTypeEncoding(keyMethod);
char argType[128] = {};
method_getArgumentType(keyMethod, 2, argType, 128);
NSString *typeName = [NSString stringWithUTF8String:argType];
// NSLog(@"method arg type = %@",typeName);
// **********************************************************
// 如果是 id 类型的赋值,则IMP为setterNew,
// 如果是int类型,则是setterInt,
// 其他类型如float、double、bool也需要单独写(目前只写了int类型)
// int 的不能使用 float ,double 去赋值,数值会出错,暂不知道问题,
// 但是猜测 float和double,int和bool 可以使用同一个IMP
// **********************************************************
IMP targetImp = (IMP)setterNew;
if ([typeName isEqualToString:@"i"]) {
targetImp = (IMP)setterInt;
}
// **********************************************************
// IMP 可以使用 C method 和 OC method 两种方法获取
// Method instanceMethod = class_getInstanceMethod([self class], @selector(BSSetter:));
// IMP ocImp = (IMP)method_getImplementation(instanceMethod);
// **********************************************************
BOOL addSuccess = class_addMethod(notiClass, [self selForSetter], targetImp , keyMethodType);
if (!addSuccess) {
NSLog(@"添加方法失败");
}
}
// **********************************************
// 重写 class 方法,使子类的 class 方法返回 父类
// ***********************************************
SEL classSel = NSSelectorFromString(@"class");
if (![self notiClass:notiClass hasSel:classSel]) {
Class superClass = [self.object superclass];
Method keyMethod = class_getInstanceMethod(superClass, @selector(class));
const char *keyMethodType = method_getTypeEncoding(keyMethod);
IMP classImp = class_getMethodImplementation([self class], @selector(BSNotiClass));
class_addMethod(notiClass, classSel, classImp, keyMethodType);
}
}
#pragma mark 用于判断 class 是否含有 某方法
-(BOOL)notiClass:(Class)class hasSel:(SEL)sel{
unsigned int count;
Method *methodList = class_copyMethodList(class, &count);
NSMutableArray *methodMut = [NSMutableArray array];
for (int i = 0; i<count; i++) {
Method method = methodList[i];
SEL methodName = method_getName(method);
[methodMut addObject:NSStringFromSelector(methodName)];
}
// NSLog(@"\nmethodList=\n%@",methodMut);
NSString *oldSetName = NSStringFromSelector(sel);
if ([methodMut containsObject:oldSetName]) {
return YES;
}
return NO;
}
#pragma mark - 重写 class 方法 和 setter 方法
#pragma mark 重写 class 方法,返回父类的class
-(Class)BSNotiClass{
Class baseCls = object_getClass(self);
Class supperCls = class_getSuperclass(baseCls);
return supperCls;
}
//#pragma mark 重写setter方法 OC版本 setter 方法(对象赋值)
//-(void)BSSetter:(id)value{
//
// Class objcClass = object_getClass(self);
// NSMutableArray *oldMut = objc_getAssociatedObject(objcClass, "notiObj");
// NSMutableArray *tempArr = [oldMut copy];
//
//
// NSMutableArray *targetMut = [NSMutableArray array];
// id oldValue = nil;
// SEL selector = nil;
//
// for (BSNotifacationObject *bsNotiObj in tempArr) {
//
// if (bsNotiObj.obsever) {
// if (self==bsNotiObj.object) {
// [targetMut addObject:bsNotiObj];
// oldValue = [bsNotiObj.object valueForKey:bsNotiObj.propertyName];
// selector = [bsNotiObj selForSetter];
// }
// }else{
// [oldMut removeObject:bsNotiObj];
// }
// }
//
//
// if (selector) {
// // 通知后 赋值
// Class superClass = [self class];
//
// Method superSet = class_getInstanceMethod(superClass, selector);
// IMP superImp = method_getImplementation(superSet);
// ((void(*)(id, SEL, id))superImp)(self, selector, value);
//
// }else{
// NSLog(@"监听对象不存在");
// }
//
//
// for (BSNotifacationObject *bsNotiObj in tempArr) {
// bsNotiObj.change(oldValue, value);
// }
//}
#pragma mark id 类型的 setter 方法,C语言方法
void setterNew(id self , SEL _cmd , id value){
Class objcClass = object_getClass(self);
NSMutableArray *oldMut = objc_getAssociatedObject(objcClass, "notiObj");
NSMutableArray *tempArr = [oldMut copy];
NSMutableArray *targetMut = [NSMutableArray array];
id oldValue = nil;
SEL selector = nil;
for (BSNotifacationObject *bsNotiObj in tempArr) {
if (bsNotiObj.obsever) {
if (self==bsNotiObj.object && [bsNotiObj selForSetter]==_cmd) {
[targetMut addObject:bsNotiObj];
oldValue = [bsNotiObj.object valueForKey:bsNotiObj.propertyName];
selector = [bsNotiObj selForSetter];
}
}else{
[oldMut removeObject:bsNotiObj];
}
}
if (selector) {
// 通知后 赋值
Class superClass = [self class];
Method superSet = class_getInstanceMethod(superClass, selector);
IMP superImp = method_getImplementation(superSet);
((void(*)(id, SEL, id))superImp)(self, selector, value);
}else{
NSLog(@"监听对象不存在");
}
for (BSNotifacationObject *bsNotiObj in targetMut) {
bsNotiObj.change(oldValue, value);
}
}
#pragma mark int 类型的 setter 方法 C语言方法
void setterInt(id self , SEL _cmd , int value){
Class objcClass = object_getClass(self);
NSMutableArray *oldMut = objc_getAssociatedObject(objcClass, "notiObj");
NSMutableArray *tempArr = [oldMut copy];
NSMutableArray *targetMut = [NSMutableArray array];
NSString * oldValue = 0;
SEL selector = nil;
for (BSNotifacationObject *bsNotiObj in tempArr) {
if (bsNotiObj.obsever) {
if (self==bsNotiObj.object && [bsNotiObj selForSetter]==_cmd) {
[targetMut addObject:bsNotiObj];
oldValue = [bsNotiObj.object valueForKey:bsNotiObj.propertyName];
selector = [bsNotiObj selForSetter];
}
}else{
[oldMut removeObject:bsNotiObj];
}
}
if (selector) {
// 通知后 赋值
Class superClass = [self class];
Method superSet = class_getInstanceMethod(superClass, selector);
IMP superImp = method_getImplementation(superSet);
((void(*)(id, SEL, int))superImp)(self, selector, value);
}else{
NSLog(@"监听对象不存在");
}
for (BSNotifacationObject *bsNotiObj in targetMut) {
bsNotiObj.change(oldValue, [NSString stringWithFormat:@"%d",value]);
}
}
#pragma mark - 检测 obsever 是否存在,如果不存在,则把不需要监听的对象移除
-(void)exchangeDealloc:(id)object{
// SEL dealloc = NSSelectorFromString(@"dealloc");
// Method oldDealloc = class_getInstanceMethod([self.obsever class], dealloc);
// Method ObsDealloc = class_getInstanceMethod([self class], @selector(obseverDealloc));
// method_exchangeImplementations(oldDealloc, ObsDealloc);
//
// objc_setAssociatedObject(self.obsever, "noti_object", object, OBJC_ASSOCIATION_RETAIN);
// objc_setAssociatedObject(self.obsever, "bsnoti_obj", self, OBJC_ASSOCIATION_RETAIN);
}
/// 方法交换,obsever 的 dealloc 和 obseverDealloc 进行交换
/// 主动 调用 dealloc会闪退,没找到解决方案,暂时不做
-(void)obseverDealloc{
// id notiObj = objc_getAssociatedObject(self, "bsnoti_obj");
// [notiObj obseverDealloc];// 会闪退,因为主动调用了dealloc方法
//
// id object = objc_getAssociatedObject(self, "noti_object");
// Class objClass = object_getClass(object);
// NSMutableArray *mutArr = objc_getAssociatedObject(objClass, "notiObj");
// NSArray *copyArr = [mutArr copy];
//
// for (BSNotifacationObject *notiObj in copyArr) {
// if ([notiObj.obsever isEqual: self] || !notiObj.obsever) {
// [mutArr removeObject:notiObj];
// }
// }
}
出现的问题
1、首先是 dealloc 无法通过 方法交换 去进行 observe 的销毁监听。如果无法对 observe 进行 dealloc 监听,那么我们就不能在最合适的时间移除 已经不在监听范围内的 监听对象 了(在重写的 setter 方法里 也是可以移除的,但是移除的时间点并不是最正确的时间点)
for (BSNotifacationObject *bsNotiObj in tempArr) {
if (bsNotiObj.obsever) {
if (self==bsNotiObj.object && [bsNotiObj selForSetter]==_cmd) {
[targetMut addObject:bsNotiObj];
oldValue = [bsNotiObj.object valueForKey:bsNotiObj.propertyName];
selector = [bsNotiObj selForSetter];
}
}else{
[oldMut removeObject:bsNotiObj];
}
}
2、调用完 setClass 方法后,即便重写 class 方法,也不会达到系统 kvo 那样,只改变了 isa 指针,其他的不变。举例:
自定义kvo
系统kvo
原因猜测,可能是我的 setClass 方法用的不正确,或者还有别的什么地方需要完善。但是。。。我不知道!
3、由于kvo是需要重写setter 方法的,所以使用 _name = @"hello" 的方式是无法触发kvo的,我们必须使用 kvc方式或 self.name = @"hello"方式赋值才能触发kvo的回调