KVO-KVC

2018/8/19 posted in  iOS基础概念

KVC

Objective-C中有个显式的NSKeyValueCoding类别名,所以对于所有继承了NSObject的类型,都能使用KVC.

KVC的找寻方式
setter,getter方法,_name,name

  • KVC来访问和修改私有变量
    对类里的私有属性,Objective-C是无法直接访问的,但KVC是可以的。(嗯。这个功能很强大)。

  • Model和字典的转换
    KVC和Object的runtime组合可以很容易实现Model和字典的转换

  • 修改一些控件的内部属性
    比如在很多UI控件都有很多内部UI控件组合而成的。但是Apple没有提供这些控件的API。这样我们就无法正常的访问和修改控件的样式。

KVO

Objective-C中有个显式的NSKeyValueObserving类别名,所以对于所有继承了NSObject的类型.

  • 关闭KVO + (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key { return NO; }
  • KVO的行为是同步的
    发生与所有观察的值发生变化的同样的线程上。
    KVO的同步运行这个特性是非常强大的,只要我们在单一线程上面运行,KVO就能保证setter方法在执行完之前被通知到。

  • KVO的实现
    实现思路
    编译器自动为被观察的对象创造一个派生类,并将被观察对象的isa指向这个派生类。若果用户注册了对某个目标对象的某一个属性的观察,那么此派生类会重写这个方法,并在其中添加通知的代码。Object-c在发送消息的时候,会通过isa指针找到当前对象所属的类对象。而类对象中保存着当前对象的实例方法。因此在向此对象发送消息时候,实际上是发送到了派生类对象的方法。又由于编译器对派生类的方法进行了重写,并添加了通知代码,因此会向注册的对象发送通知。

#import "NSObject+RAOKVO.h"
#import <objc/message.h>
const char *key;
@implementation NSObject (RAOKVO)

- (void)rao_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
    /*
     动态创建一个新类
     */
    NSString *oldName = NSStringFromClass([self class]);
    NSString *newName = [@"RAO" stringByAppendingString:oldName];
    
    Class myClass = objc_allocateClassPair([self class], [newName UTF8String], 0);
    objc_registerClassPair(myClass);//注册类(相当于加载吧)
    object_setClass(self, myClass);//更改本类类型(修改isa指向)
    
    //重写setName,实际是给子类添加方法(因为如果本类没方法,实际是找到父类方法)
    class_addMethod(myClass, @selector(setName:), (IMP)setName,"v@:@");
    
    //将观察者绑定到子类对象
    objc_setAssociatedObject(self, @"key", observer, OBJC_ASSOCIATION_ASSIGN);//(用ASSIGN防止循环引用)
}

void setName(id self,SEL sel,NSString * name) {
    struct objc_super person = {self,class_getSuperclass([self class])};
    objc_msgSendSuper(&person,sel,name);
    
    //拿出观察者
    id observer = objc_getAssociatedObject(self,@"key");
    objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),"name",self,@{@"name":name});
}

@end