美文网首页
iOS 最全面KVO实现原理详解

iOS 最全面KVO实现原理详解

作者: MiniCoder | 来源:发表于2020-03-10 23:49 被阅读0次
  • KVO简介
      KVO是Objective-C对观察者设计模式的一种实现,它提供一种机制,指定一个被观察对象(如A类),当对象中的某个属性发生变化的时候,对象就会接收到通知,并作出相应的处理。在MVC设计架构下的项目,KVO机制很适合实现mode模型和view视图之间的通讯。例如:代码中,在模型类A创建属性数据,在控制器中创建观察者,一旦属性数据发生改变就收到观察者收到通知,通过KVO再在控制器使用回调方法处理实现视图B的更新;
  • 实现原理
      KVO的实现依赖于Objective-C强大的runtime,KVO的底层实现是监听setter方法。当观察某对象A时,KVO动态机制会动态创建一个A类的子类,并为这个新的子类重写父类的的setter方法。setter方法随后负责通知观察对象属性的变化。
  • 深入理解
      Apple使用了isa混写(isa-swizzling)来实现KVO,当观察对象A的时候(也就是调用对象A注册观察者的方法的时候),KVO机制会动态创建一个A的新的子类:NSKVONotifying_A的新,该类继承自对象A的本类。且KVO会为NSKVONotifying_A重写其父类的setter方法,setter方法会负责在调用原setter方法之前或之后,通知所有观察对象属性值的更改状况。
    addobserver方法内部实现:
      在这个方法中,被观察对象的isa指针从指向原来的A类,被KVO机制修改为指向A类的子类-NSKVONotifying_A,来实现当前类属性值改变的监听。
      isa指针的作用:每个对象都有一个isa指针,指向该对象的类。它告诉Runtime系统这个对象的类是什么,所以对象注册为观察者的是时候,isa指针会指向新的子类,那么这个对象就会变成新的子类的对象了。因此,该对象调用setter就会调用已经重写的setter了,从而激活键值通知机制。
    子类setter方法剖析:
      KVO的键值观察通知依赖于NSObject的两个方法:willChangeValueForKey didChangeValueForKey,在存取值的前后分别调用的方法。被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该 keyPath 的属性值即将变更;当改变发生后, didChangeValueForKey: 被调用,通知系统该 keyPath 的属性值已经变更;之后, observeValueForKey:ofObject:change:context: 也会被调用。且重写观察属性的setter 方法这种继承方式的注入是在运行时而不是编译时实现的。
  • 自己实现KVO
    首先创建一个NSObject的类扩展,.h文件中自定一个一个方法,由于目标是自定义实现KVO,所以只需要在系统添加观察者的方法名前面添加一个前缀,参数值不变。方法实现如下
    创建一个NSObject 的分类,并在分类中添加自定义的添加观察者方法:
- (void)yb_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(YB_NSKeyValueObservingOptions)options context:(void *)context {
    if (!observer || !keyPath) return;
    
    @synchronized(self){
        //给 keyPath 链条最终类做逻辑
        NSArray *keyArr = [keyPath componentsSeparatedByString:@"."];
        if (keyArr.count <= 0) return;
        id nextTarget = self;
        for (int i = 0; i < keyArr.count-1; i++) {
            nextTarget = [nextTarget valueForKey:keyArr[i]];
        }
        if (![self yb_coreLogicWithTarget:nextTarget getterName:keyArr.lastObject]) {
            return;
        }
        //给目标类绑定信息
        WXKVOInfoModel *info = [WXKVOInfoModel new];
        info.target = self;
        info.observer = observer;
        info.keyPath = keyPath;
        info.options = options;
        [info setContext:context];
        [self yb_bindInfoToTarget:nextTarget info:info key:keyArr.lastObject options:options];
    }
}

这里有几个关键方法

///创建被观察类的派生类
-(void)wx_createClassPairWithTarget:(id)target{
    Class class = object_getClass(target);
    NSString * originClass = NSStringFromClass(class);
    ///表示本类已经是派生类
    if ([originClass hasPrefix:kPrefixOfYBKVO]) {
        return;
    }
    
    NSString * classPairClass = [NSString stringWithFormat:@"%@_%@",kPrefixOfYBKVO,originClass];
    Class classPair = NSClassFromString(classPairClass);
    //如果派生类存在
    if (classPair) {
        object_setClass(target, classPair);
        return;
    }
    ///创建派生类
    classPair =  objc_allocateClassPair(class, classPairClass.UTF8String, 0);
    const char * types = method_getTypeEncoding(class_getInstanceMethod(class, @selector(class)));
    IMP class_imp = imp_implementationWithBlock(^Class(id target){
        return class_getSuperclass(object_getClass(target));
    });
    
    class_addMethod(classPair, @selector(class), class_imp, types);
    objc_registerClassPair(classPair);
    object_setClass(target, classPair);
}

生成的派生类是被观察类的子类,被观察的实例对象的isa指向派生类,并重写该类的class方法。
添加被观察属性的set方法到派生类中。

- (BOOL)yb_coreLogicWithTarget:(id)target getterName:(NSString *)getterName {
    //若 setter 不存在
    NSString *setterName = setterNameFromGetterName(getterName);
    SEL setterSel = NSSelectorFromString(setterName);
    Method setterMethod = class_getInstanceMethod(object_getClass(target), setterSel);
    if (!setterMethod) return NO;
    
    //创建派生类并且更改 isa 指针
    [self wx_createClassPairWithTarget:target];
    
    //给派生类添加 setter 方法体
    if (!classHasSel(object_getClass(target), setterSel)) {
        const char *types = method_getTypeEncoding(setterMethod);
        return class_addMethod(object_getClass(target), setterSel, (IMP)yb_kvo_setter, types);
    }
    return YES;
}

当被观察的key,调用set方法的时候,我们会接受到:

static void yb_kvo_setter (id taget, SEL sel, id p0) {
    //拿到调用父类方法之前的值
    NSString *getterName = getterNameFromSetterName(NSStringFromSelector(sel));
    id old = [taget valueForKey:getterName];
    callBack(taget, nil, old, getterName, YES);
    
    //给父类发送消息
    struct objc_super sup = {
        .receiver = taget,
        .super_class = class_getSuperclass(object_getClass(taget))
    };
    ((void(*)(struct objc_super *, SEL, id)) objc_msgSendSuper)(&sup, sel, p0);
    
    //回调相关
    callBack(taget, p0, old, getterName, NO);
}

此时我们会调用call方法进行回调,并向派生类的父类(被观察对象的本类)发送消息,调用set方法进行赋值。

static void callBack (id taget, id nValue, id oValue, NSString *getterName, BOOL notificationIsPrior) {
    NSMutableDictionary * dic = objc_getAssociatedObject(taget, &observerKeyValues);
    if (dic && [dic valueForKey:getterName]) {
        NSMutableArray * arr = [dic valueForKey:getterName];
        for (YbKVOInfoModel * model in arr) {
            if (model && model.observer && [model.observer respondsToSelector:@selector(yb_observeValueForKeyPath:ofObject:change:context:)]) {
                NSMutableDictionary * dic = [NSMutableDictionary dictionary];
                if (model.options & NSKeyValueObservingOptionNew && nValue){
                    [dic setValue:nValue forKey:@"new"];
                }
                if (model.options & NSKeyValueObservingOptionOld && nValue){
                    [dic setValue:nValue forKey:@"new"];
                }
                if (notificationIsPrior) {
                    if (model.options & NSKeyValueObservingOptionPrior) {
                        [dic setObject:@"1" forKey:@"notificationIsPrior"];
                    } else {
                        continue;
                    }
                }
                [model.observer yb_observeValueForKeyPath:model.keyPath ofObject:model.target change:dic context:model.getContext];
            }
        }
    }
}

最后我们移除观察者的时候,请调用下面的方法:

- (void)wx_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context {
    @synchronized(self) {
        //移除配置信息
        NSArray *keyArr = [keyPath componentsSeparatedByString:@"."];
        if (keyArr.count <= 0) return;
        id nextTarget = self;
        for (int i = 0; i < keyArr.count-1; i++) {
            nextTarget = [nextTarget valueForKey:keyArr[i]];
        }
        NSString *getterName = keyArr.lastObject;
        NSMutableDictionary *dic = objc_getAssociatedObject(nextTarget, keyOfYbKVOInfoModel);
        if (dic && [dic valueForKey:getterName]) {
            NSMutableArray *tempArr = [dic valueForKey:getterName];
            @autoreleasepool {
                for (WXKVOInfoModel *info in tempArr.copy) {
                    if (info.getContext == context && info.observer == observer && [info.keyPath isEqualToString:keyPath]) {
                        [tempArr removeObject:info];
                    }
                }
            }
            if (tempArr.count == 0) {
                [dic removeObjectForKey:getterName];
            }
            //若无可监听项,isa 指针指回去
            if (dic.count <= 0) {
                Class nowClass = object_getClass(nextTarget);
                NSString *nowClass_name = NSStringFromClass(nowClass);
                if ([nowClass_name hasPrefix:kPrefixOfYBKVO]) {
                    Class superClass = [nextTarget class];
                    object_setClass(nextTarget, superClass);
                }
            }
        }
    }
}

此时,一个我们自己重写的KVO便实现了,如果有什么疑问或者不对的地方,可以联系我;

相关文章

  • iOS - KVO

    [toc] 参考 KVO KVC 【 iOS--KVO的实现原理与具体应用 】 【 IOS-详解KVO底层实现 】...

  • 底层原理

    iOS底层原理总结 - Category的本质 KVO详解及底层实现青少年一定要读的KVO指南 iOS 底层解析w...

  • iOS 最全面KVO实现原理详解

    KVO简介KVO是Objective-C对观察者设计模式的一种实现,它提供一种机制,指定一个被观察对象(如A类),...

  • iOS日记15-KVC

    1.iOS开发技巧系列---详解KVC 2.漫谈 KVC 与 KVO 3.KVC/KVO原理详解及编程指南 关键点...

  • IOS底层(三) KVO底层实现原理

    @[TOC](IOS底层(三) KVO底层实现原理 ) 一,KVO简述 KVO的全称 Key-Value Obse...

  • iOS 自定义KVO

    自己实现kvo之前,需要知道iOS系统对kvo的实现。 系统实现kvo的原理 这依赖了OC强大的runtime特性...

  • iOS 关于KVO的一些总结

    本文参考链接: iOS KVO详解 Foundation: NSKeyValueObserving(KVO) KV...

  • APNS消息推送的实现(完整步骤)

    1. 原理及代码实现 iOS远程推送原理及实现过程 苹果远程推送通知 APNs 详解,官方,iOS | Swift...

  • iOS-底层原理-自定义KVO

    1.自定义KVO 1.上一篇博客了解了iOS 系统KVO的底层实现原理,那么这里进行自定义KVO,更好的理解原理和...

  • iOS Objective-C KVO 详解

    iOS Objective-C KVO 详解 1. KVO KVO即Key-Value Observing是苹果提...

网友评论

      本文标题:iOS 最全面KVO实现原理详解

      本文链接:https://www.haomeiwen.com/subject/cqjodhtx.html