KVC & KVO

作者: 生产八哥 | 来源:发表于2021-02-01 11:21 被阅读0次

KVC

Key Value Coding,键值编码,是一种间接访问实例变量的方法。提供了一个使用字符串(Key)而不是访问器方法,去访问一个对象实例变量的机制。可以允许开发者通过Key名访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性,而不是在编译时确定,这也是iOS开发中的黑魔法之一。

会先查找对应的set或get方法是否实现,主要查找set<Key>:-> set<Key> -> setIs<Key>,主没有查找到的话会accessInstanceVariablesDirectly是否返回YES,如果返回YES,则查找间接访问的实例变量进行赋值,查找顺序为:<key> -> _is<Key> -> <key> -> is<Key>,如果setter方法 或者 实例变量都没有找到,系统会执行该对象的setValue:forUndefinedKey:方法,默认抛出NSUndefinedKeyException类型的异常。

KVO

  • KVO是用了isa-swizzling技术实现的。
  • KVO可以自动触发,也可以手动触发(willChangeValue & didChangeValue).。
  • KVO观察中的一对多,意思是通过注册一个KVO观察者,可以监听多个属性的变化。
  • KVC是动态的对属性赋值,而KVO是对属性的动态变化监听。
  • KVO只能用于监听对象属性的变化。KVO监听不了成员变量,属性和成员变量的区别在于属性多一个 setter 方法,而KVO恰好观察的是setter 方法。点语法的本质是方法的调用,而不是访问成员变量。

中间类

通过class_copyMethodList获取中间类的所有方法会发现,中间类重写了观察属性的setter方法、class、dealloc、_isKVOA方法。


简而言之,苹果使用了一种 isa 交换( object_setClass(old,new))的技术,当 student 被观察后,student 对象的 isa 指针被指向了一个新建的 Student 的子类 NSKVONotifying_Student,且这个子类重写了被观察值的 setter 方法和 class 方法,dealloc 和 _isKVO 方法,然后使 student 对象的 isa 指针指向这个新建的类,然后事实上 student 变为了NSKVONotifying_Student 的实例对象,执行方法要从这个类的方法列表里找。dealloc 方法:观察移除后使 class 变回去 Student(通过 isa 指向), _isKVO 方法判断被观察者自己是否同时也观察了其他对象。(同时我们得知,通过 isa 获取类的类型是不可靠的,底层可能动态变换isa指向,通过 class 方法才能得到正确的类)

注意:注册观察和移除观察要成对出现,移除是为了改变中间类isa指向,但没有移除这个中间类,会一直存在内存里。而NSNotification在iOS9之后会默认在dealloc的时候自动移除。

isa交换:object_setClass
申请类空间:objc_llocateClassPair
注册类:objc_registerClassPair

KVO只能用于监听对象属性的变化。如果对象属性是个NSMutableArray、NSMutableSet、NSMutableDictionary等可变集合类型时,你给它添加KVO时,你会发现当你添加或者移除元素时并不能接收到变化。因为KVO的本质是系统监测到某个属性的内存地址或常量改变时,会添加上- (void)willChangeValueForKey:(NSString *)key和- (void)didChangeValueForKey:(NSString *)key方法来发送通知,所以一种解决方法是手动调用者两个方法,但是并不推荐,你永远无法像系统一样真正知道这个元素什么时候被改变。另一种便是利用使用mutableArrayValueForKey:了。

[self.dataArray addObject:@"1”]; //不会回调观察者
   
[[self mutableArrayValueForKey:@"dataArray"] addObject:@"2”]; //会回调观察者

KVC和KVO

KVC是键值编码,KVO是键值观察。KVO是基于KVC的键值编码基础上对属性动态变化的监听。什么意思呢?就是上面代码中对可变数组addObject元素,是不会触发KVC的,自然也触发不了KVO。所以先要触发了KVC,才会有KVO的可能。

思考题:

现有一个继承于NSObject的实例对象,需要在不直接修改方法实现的情况下,改变一个方法的行为,并把这个方法的影响范围缩小到具体的某个实例里,怎么做?

答: 首先肯定要用到Method Swizzling,但是如何把范围缩小到某个实例呢? 这里要用到KVO的思想isa-swizzling了。

NSString *demo_getName(id self, SEL selector)
{
    return @"方天画戟";
}

    Person *person = [[Person alloc] init];
    NSLog(@"person name: %@", person.name);
    
    // 1.创建一个子类
    NSString *oldName = NSStringFromClass([person class]);
    NSString *newName = [NSString stringWithFormat:@"Subclass_%@", oldName];
    Class customClass = objc_allocateClassPair([person class], newName.UTF8String, 0);
    objc_registerClassPair(customClass);
    // 2.重写get方法
    SEL sel = @selector(name);
    Method method = class_getInstanceMethod([person class], sel);
    const char *type = method_getTypeEncoding(method);
    class_addMethod(customClass, sel, (IMP)demo_getName, type);
    // 3.修改修改isa指针(isa swizzling)
    object_setClass(person, customClass);

使用 isa-swizzlingperson对象name的行为改变了,而且是只对person对象,其他实例无影响。

相关文章

网友评论

      本文标题:KVC & KVO

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