iOS开发

ios kvo 的实现原理,自定义kvo

2019-09-25  本文已影响0人  BlackStar暗星

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的回调

上一篇下一篇

猜你喜欢

热点阅读