美文网首页
iOS KVO KVC

iOS KVO KVC

作者: gaookey | 来源:发表于2021-07-22 11:03 被阅读0次

    KVO

    基本使用

    给 person 对象添加KVO监听

    Person *person = [[Person alloc] init];
    person.age = 10;
    self.person = person;
     
    [self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    

    当监听对象的属性值发生改变时,就会调用。

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
        NSLog(@"属性值更改了 %@", change);
    }
    

    移除监听

    - (void)dealloc {
        [self.person removeObserver:self forKeyPath:@"age"];
    }
    
    本质分析
    Person *person1 = [[Person alloc] init];
    person1.age = 1;
    self.person1 = person1;
    
    Person *person2 = [[Person alloc] init];
    person2.age = 2;
    self.person2 = person2;
    
    [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    
    image.png

    person1 使用KVO监听后,person1isa 会指向一个全新的类 NSKVONotifying_PersonNSKVONotifying_Person 是使用 Runtime 动态创建的一个 Person 类的子类,即 NSKVONotifying_Personsuperclass 指向的是 Person 类对象。

    • 如果对实例对象 person2age 进行赋值,person2 会通过 isa 指针找到类 Person,然后找到方法列表中的 setAge 进行调用。
    • 如果对实例对象 person1age 进行赋值, person1 会通过 isa 指针找到类 NSKVONotifying_Person,然后找到方法列表中的 setAge 进行调用。但是 NSKVONotifying_Person 中的 setAge 方法内大致的逻辑是:执行 Foundation 框架中的 _NSSetIntValueAndNotify 函数,而 _NSSetIntValueAndNotify 函数内部会陆续调用 willChangeValueForKey[super setAge:age](原来的setter实现)、didChangeValueForKey 等方法,didChangeValueForKey 方法会执行 observeValueForKeyPath: ofObject: change: context: 通知监听器,最终执行代理方法 observeValueForKeyPath: ofObject: change: context:

    因为 ageint 类型,所以是 _NSSetIntValueAndNotify。如果属性 age 是其它类型,相对应的还有 _NSSetBoolValueAndNotify_NSSetCharValueAndNotify_NSSetObjectValueAndNotify_NSSetFloatValueAndNotify_NSSetSizeValueAndNotify、 等。

    NSKVONotifying_Person 类中重写了 - (Class)class 方法,使得 [self.person1 class]Person,而通过运行时 object_getClass(self.person1) 获取时是 NSKVONotifying_Person

    获取 NSKVONotifying_Person 类中的方法:

    Class class = object_getClass(self.person1);
    NSMutableArray *methodNames = [NSMutableArray array];
        
    unsigned int count;
    Method *methodList = class_copyMethodList(class, &count);
    for (NSInteger i = 0; i < count; i ++) {
        Method method = methodList[i];
        NSString *methodName = NSStringFromSelector(method_getName(method));
        [methodNames addObject:methodName];
    }
    free(methodList);
    /*
     (
         "setAge:",
         class,
         dealloc,
         "_isKVOA"
     )
     */
    NSLog(@"%@", methodNames);
    
    验证
    NSLog(@"person1 添加KVO监听之前 类对象(self.person1.isa): %@ 元类对象(self.person1.isa.isa): %@ 方法内存地址: %p", object_getClass(self.person1), object_getClass(object_getClass(self.person1)), [self.person1 methodForSelector:@selector(setAge:)]);
    NSLog(@"person2 添加KVO监听之前 类对象(self.person2.isa): %@ 元类对象(self.person2.isa.isa): %@ 方法内存地址: %p", object_getClass(self.person2), object_getClass(object_getClass(self.person2)), [self.person2 methodForSelector:@selector(setAge:)]);
    
    [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    
    NSLog(@"person1 添加KVO监听之后 类对象(self.person1.isa): %@ 元类对象(self.person1.isa.isa): %@ 方法内存地址: %p", object_getClass(self.person1), object_getClass(object_getClass(self.person1)), [self.person1 methodForSelector:@selector(setAge:)]);
    NSLog(@"person2 添加KVO监听之后 类对象(self.person2.isa): %@ 元类对象(self.person2.isa.isa): %@ 方法内存地址: %p", object_getClass(self.person2), object_getClass(object_getClass(self.person2)), [self.person2 methodForSelector:@selector(setAge:)]);
    
    image.png image.png

    所以 person1对象的 setAge: 具体实现是 _NSSetIntValueAndNotify


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

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

    手动调用 willChangeValueForKey:didChangeValueForKey: 可以手动触发KVO

    直接修改成员变量不会触发KVO

    KVC

    KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性。

    常见的API有:

    • (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
    • (void)setValue:(id)value forKey:(NSString *)key;
    • (id)valueForKeyPath:(NSString *)keyPath;
    • (id)valueForKey:(NSString *)key;
    setValue:forKey: 的原理
    1. 首先按照 setKey:_setKey: 的顺序查找方法。如果找到了方法就传递参数,调用方法。

    2. 如果没找到,则查看 accessInstanceVariablesDirectly 方法 的返回值(默认值是 YES),如果返回 NO,调用 setValue: forUndefinedKey: 并抛出异常 NSUnknownKeyException

    3. 如果 accessInstanceVariablesDirectly 返回 YES,按照 _key_isKeykeyisKey 的顺序查找成员变量,找到了成员变量直接赋值。

    4. 如果没找到成员变量,调用 setValue: forUndefinedKey: 并抛出异常 NSUnknownKeyException

    valueForKey: 的原理
    1. 首先按照 getKeykeyisKey_key 的顺序查找方法,找到了就调用方法。

    2. 如果没找到,则查看 accessInstanceVariablesDirectly 方法 的返回值(默认值是 YES),如果返回 NO,调用 valueForUndefinedKey: 并抛出异常 NSUnknownKeyException

    3. 如果 accessInstanceVariablesDirectly 返回 YES,按照 _key_isKeykeyisKey 的顺序查找成员变量,找到了成员变量直接取值。

    4. 如果没找到成员变量,调用 valueForUndefinedKey: 并抛出异常 NSUnknownKeyException

    相关文章

      网友评论

          本文标题:iOS KVO KVC

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