美文网首页
底层探索--KVO、KVC的本质

底层探索--KVO、KVC的本质

作者: 永断阎罗 | 来源:发表于2021-10-13 14:41 被阅读0次

    KVO

    原理

    • 基本使用

        //添加监听
        [self addObserver:<#(nonnull NSObject *)#> forKeyPath:<#(nonnull NSString *)#> options:<#(NSKeyValueObservingOptions)#> context:<#(nullable void *)#>]
        //参数详解:
        Observer:观察者对象,一般self;
        keyPath:观察的属性,字符串属性,一般采用系统的反射机制 NSStringFromSelector(@selector(keypath))
        options:枚举
            NSKeyValueObservingOptionNew //接受新值,默认
            NSKeyValueObservingOptionOld //接受旧值
            NSKeyValueObservingOptionInitial //在注册时立即接受一次回调,改变是也会
            NSKeyValueObservingOptionPrior //改变之前发送一次,改变之后再发一次
            可采用如下形式:NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        
        //实现监听方法
        - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
        //参数详解:
        keyPath:监听属性的名称
        Object:被观察对象
        Change:字典,key一般为:new、old
        Context:入口,用于传值,添加监听时原样传来的
      
        //移除监听对象
        [self removeObserver:<#(nonnull NSObject *)#> forKeyPath:<#(nonnull NSString *)#>]
      
    • 对象添加监听之后,运行时新增了类NSKVONotifying_对象的类名,并会重写对应的set方法:

        NSKVONotifying_对象的类名的组成结构:
        isa
        superclass
        set对象方法---内部调用 _NSSet*ValueAndNotify(如:_NSSetObjectValueAndNotify,*代表监听对象对应的类型,如:int double object等)--详情见下图
        class:[类名 class],重写后返回本身类(屏蔽内部实现,隐藏实际类NSKVONotifying_对象的类名的存在),
        dealloc:做一些销毁操作,
        _isKVOA
        
        
        //实际证明方法如下:
        
        //监听方法
        - (void) observeFunction {
            NSLog(@"class = %@, metaclass = %@ \n",object_getClass(self.model),object_getClass(object_getClass(self.model)));
            //class = ZTKVORootM, metaclass = ZTKVORootM
            
            NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionPrior;
            [self.model addObserver:self forKeyPath:@"name" options:options context:nil];
            
            //打印 类对象 和 元类对象(注意:不能使用[self.model class],因为内部已经被重写,不是实际isa指向的类,需用运行时直接获取才是真正的类)
            NSLog(@"class = %@, metaclass = %@ \n",object_getClass(self.model),object_getClass(object_getClass(self.model)));
            //打印结果:class = NSKVONotifying_ZTKVORootM, metaclass = NSKVONotifying_ZTKVORootM
            
            //打印方法
            NSLog(@"Method = %p",[self.model methodForSelector:@selector(setName:)]);
            //打印结果:p (IMP)0x223413e04
            //(IMP) $3 = 0x0000000223413e04 (Foundation`_NSSetObjectValueAndNotify)
            
            //证明:添加监听后,运行时新增了类NSKVONotifying_ZTKVORootM,并重新了监听的属性的set方法
            
            //打印监听后的类 和 没监听类的对象方法
            ZTKVORootM *notModel = [[ZTKVORootM alloc]init];
            
            [self getAllMethodListWithClass:object_getClass(notModel)];
            [self getAllMethodListWithClass:object_getClass(self.model)];
            /** 打印如下
             ZTKVORootM下所有的方法列表:
             .cxx_destruct
             name
             setName:
        
             2020-09-23 14:25:06.560550+0800 testApp[862:180083] NSKVONotifying_ZTKVORootM下所有的方法列表:
             setName:
             class
             dealloc
             _isKVOA
             */
        }
    
        
        //添加依赖:返回(对象中,需监听的属性)
        + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
            NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
            if ([key isEqualToString:@"person"]) {
                //此时如果person这个对象中的name或age值改变就会触发
                keyPaths = [[NSSet alloc] initWithObjects:@"person.name",@"person.age", nil];
            }
            return keyPaths;
        }
        
        //KVO是否 开启自动监听(值变后自动通知消息)
        + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
            return YES;
        }
        
        //获取类-所有的对象方法
        - (void) getAllMethodListWithClass:(Class)class {
            unsigned int count = 0;
            Method *methodList = class_copyMethodList(class, &count);
            
            NSMutableString *methodStr = [[NSMutableString alloc] init];
            for (NSInteger i = 0; i < count; i++) {
                Method method = methodList[i];
                SEL sel = method_getName(method);
                [methodStr appendFormat:@"%@ \n",NSStringFromSelector(sel)];
            }
            NSLog(@"%@下所有的方法列表:\n %@",NSStringFromClass(class),methodStr);
        }
    
    KVO重新的set方法.png
    • set内部实现如下图
    KVO的set内部实现.png

    面试题

    1、 iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)

    利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
    当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
    willChangeValueForKey:
    父类原来的setter
    didChangeValueForKey:
    内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)

    2、 如何手动触发KVO?

    手动调用willChangeValueForKey:和didChangeValueForKey:

    3、 直接修改成员变量会触发KVO么?

    不会触发KVO,只有调用了setter方法才会触发

    KVC

    • 主要方法

        //get方法
        - (id)valueForKeyPath:(NSString *)keyPath;
        - (id)valueForKey:(NSString *)key;
        - (id)valueForUndefinedKey:(NSString *)key;
        
        //set方法
        - (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
        - (void)setValue:(id)value forKey:(NSString *)key;
        - (void)setValue:(id)value forUndefinedKey:(NSString *)key;  
      
    • - (void)setValue:(id)value forKey:(NSString *)key;方法解析:

      • 1.按照setKey: or _setKey: 顺序查方法赋值,找到直接调用,否则往下 走2;
      • 2.查找+ (BOOL)accessInstanceVariablesDirectly;//是否允许查找成员变量,默认返回YES返回值:YES-走3,NO-走4
      • 3.按照_key _isKey key isKey顺序查找成员变量,找到了直接取值,否则往下 走4
      • 4.- (void)setValue:(id)value forUndefinedKey:(NSString *)key; 并抛出异常NSUnknownKeyException
    KVC的set方法原理.png
    • - (id)valueForKey:(NSString *)key;方法解析:
      • 1.按照getKeykeyisKey_key: 顺序查方法,找到直接调用,否则往下 走2;
      • 2.查找+ (BOOL)accessInstanceVariablesDirectly;//是否允许查找成员变量,默认返回YES返回值:YES-走3,NO-走4
      • 3.按照_key _isKey key isKey顺序查找成员变量,找到了直接取值,否则往下 走4
      • 4.- (id)valueForUndefinedKey:(NSString *)key; 并抛出异常NSUnknownKeyException
    KVC的get方法原理.png

    面试题

    1、 通过KVC修改属性会触发KVO么?

    会触发KVO。
    原理:不论有没有实现setter方法,都会触发KVO,内部应该实现了:调用willChangeValueForKey:和didChangeValueForKey:

    相关文章

      网友评论

          本文标题:底层探索--KVO、KVC的本质

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