2017-04-21

KVC/KVO原理

KVC

KVC访问属性时尽可能尝试使用存取方法,当KVC访问属性时,它内部其实做了很多事:

以一个属性icon为例

  1. 首先查找模型中有没有setIcon方法,如果有有,直接调用[self setIcon:dict[@"icon"]];
  2. 如果找不到set方法,直接寻找有没有icon属性,如果有,就直接访问模型中icon = dict[@"icon"];
  3. 如果找不到icon属性,就寻找_icon属性,如果有,直接_icon = dict[@"icon"]
  4. 如果都找不到,就会报错
    [ setValue:forUndefinedKey:]

当然,KVC提供了键值检测机制(KVV)

- (BOOL)validateValue:(inout id *)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;  

当然,这个方法是需要手动调用的,并不会自动调用,KVC的实现原理还是比较简单的,这里重点是要说明一下KVO

KVO

当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中被观察属性的 setter 方法,在setter方法里使其具有通知机制。因此,要想KVO生效,必须直接或间接的通过setter方法访问属性(KVC的setValue就是间接方式)。直接访问成员变量KVO是不生效的。

同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。

举个

比如,我们要对一个Dog做一个监测,那么,runtime就会创建一个名为NSKVONotifying_Dog

新的NSKVONotifying_Dog类会重写一下方法:
增加了监听属性对应的set,class,dealloc,_isKVOA

  1. class

    重写class方法是为了我们调用它的时候返回跟重写继承类之前同样的内容。

    打印如下内容:

    NSLog(@"self->isa:%@",self->isa);  
    NSLog(@"self class:%@",[self class]);  
    

    在建立KVO监听之前,打印的结果为:

    self->isa:Dog 
    self class:Dog  
    
    

    在建立监听之后,打印的结果为:

    self->isa:NSKVONotifying_Dog  
    self class:Dog
    
  2. set

    重写set方法,是为了在set方法中增加另外两个方法的调用:

    - (void)willChangeValueForKey:(NSString *)key  
    - (void)didChangeValueForKey:(NSString *)key
    

    其中,didChangeValueForKey负责调用:

    - (void)observeValueForKeyPath:(NSString *)keyPath  
                          ofObject:(id)object  
                            change:(NSDictionary *)change  
                           context:(void *)context  
    

    这个就是你在监听中写的回调函数,这样就实现了KVO,这里有几点需要注意的东西:

如果没有任何的访问器方法,-setValue:forKey方法会直接调用:

- (void)willChangeValueForKey:(NSString *)key  
- (void)didChangeValueForKey:(NSString *)key  

**比如说我们没有自动生成setter 和 getter 方法,我们只是通过下划线命名了 _icon,那么,我们只要手动调用这两个方法,也会实现键值监听 **

  1. _isKVOA

    这个私有方法应该是用来标识该类是一个 KVO 机制声称的类,不去关心

到此,一个KVO/KVC的基本实现就是这么多了

你可能感兴趣的:(2017-04-21)