KVO & KVC

147 阅读2分钟

KVO

1. KVO的本质是什么?

    KVO的本质实际上是系统在运行时,动态生成了一个类对象NSKVONotifying_Person,让实例对象的isa指向这个新生成的类对象,并且修改了NSKVONotifying_Person中属性set方法的实现。当修改instance对象的属性时,会调用Foundation框架的_NSSetIntValueAndNotify函数。NSKVONotifying_Person是Person的子类,isa指向自己的元类,和Person类对象的元类不同。

    Person *p = [[Person alloc] init];
    
    NSLog(@"p.class = %@",object_getClass(p));
    
    // 打印结果为p.class = Person
    
    NSLog(@"selector = %p",[p methodForSelector:@selector(setAge:)]);
    
    /*打印结果为 selector = 0x10497a598
    这里通过lldb命令打印方法的IMP 
     p (IMP)0x10497a598
     $1 = 0x000000010497a598 (LinkDemo`-[Person setAge:] at Person.h:14)
    */
    
    [p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    p.age = 10;
    
    NSLog(@"p.class = %@",object_getClass(p));
    
    // 打印结果为 p.class = NSKVONotifying_Person
    
    NSLog(@"selector = %p",[p methodForSelector:@selector(setAge:)]);
    
    /* 打印结果为 selector = 0x1807973ac
     这里通过lldb命令打印方法的IMP 
     p (IMP)0x1807973ac
     $0 = 0x00000001807973ac (Foundation`_NSSetIntValueAndNotify)
     */

  _NSSetIntValueAndNotify函数里面的实现包括:

  1. willChangeValueForKey
  2. [super set方法];
  3. didChangeValueForKey : ==在这个方法中通知observer==

2. 子类NSKVONotifying_Person内部方法?

   利用runtime函数打印类对象的方法列表可以得到如下结果:

  1. 重写了class方法,实际上返回的是Person类对象,但是如果使用runtime函数object_getClass(instance),返回的是NSKVONotifying_Person这个新生成的类对象。
  2. 重写了set方法,set方法里面调用了C函数_NSSetIntValueAndNotify。
  3. 重写了dealloc方法。
  4. 重写了_isKVOA方法。
- (void)printMethodWithClass:(Class) cls {
    unsigned int num;
    Method *methodList = class_copyMethodList(cls, &num);
    
    NSMutableString *result = [NSMutableString string];
    
    for (int i = 0; i < num; i++) {
        Method method = methodList[i];
        
        NSString *methodName = NSStringFromSelector(method_getName(method));
        
        [result appendFormat:@"%@, ",methodName];
    }
    
    free(methodList);
    
    NSLog(@"%@  %@",cls,result);
}

/*分别传入添加KVO之前之后的类对象
  打印结果为
  
  Person  setAge:, age
  
  NSKVONotifying_Person  setAge:, class, dealloc, _isKVOA,

*/

3. 如何手动出发KVO?

  手动调用willChangeValueForKeydidChangeValueForKey方法。

KVC

1. 什么的KVC?

  KVC的全称是Key-Value-Coding键值编码,可以通过一个key来访问对象的某个属性。常用的API有:

- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (nullable id)valueForKeyPath:(NSString *)keyPath;

2. 通过KVC修改属性的值会触发KVO吗?赋值取值过程是什么?

  通过KVC修改属性的值 会触发KVO

  1. 赋值的过程为:
graph TD
setValueForKey: --> Node1{判断是否有key这个属性}
Node1 -->|存在| Node2[ 传递参数,调用方法--willChangeValueForKey,set方法,didChangeValueForKey,这里会触发KVO]
Node1 -->|不存在| Node3{ 调用这个accessInstanceVariablesDirectly类方法,判断是否可以直接访问成员变量,默认返回的YES}
Node3 -->|YES允许访问| Node4{ 按照_key,_isKey,key,isKey顺序查找成员变量}
Node3 -->|NO不允许访问| Node5[ 调用valueForUndefinedKey方法,抛出异常NSUnknownKeyException]
Node4 -->|找到成员变量| Node6[ 调用方法willChangeValueForKey]
Node4 -->|没到成员变量| Node5
Node6 --> Node7{ 是否实现setKey,_setKey方法}
Node7 -->|YES,按照setKey,_setKey这个顺序调用方法,仅调用一个| Node8[ 调用方法didChangeValueForKey,这里会触发KVO]
Node7 -->|NO,给成员变量赋值| Node8
  1. 取值的过程为:
graph TD
valueForKey --> Node1{ 按照getKey,key,isKey,_age顺序查找方法}
Node1 -->|找到了方法| Node2[ 调用方法]
Node1 -->|没有找到方法| Node3{ 查看accessInstanceVariablesDirectly类方法,默认返回的YES}
Node3 -->|YES允许访问| Node4{ 按照_key,_isKey,key,isKey顺序查找成员变量}
Node3 -->|NO不允许访问| Node5[ 调用valueForUndefinedKey方法,抛出异常NSUnknownKeyException]
Node4 -->|找到成员变量| Node6[ 直接取值]
Node4 -->|没到成员变量| Node5