概述
KVO是苹果提供的一套事件通知机制,允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。
KVO和NSNotification都是iOS中对观察者模式的一种实现。区别在于,相对于观察者和被观察者的关系,KVO是一对一的,而不是一对多的。KVO对被监听对象无侵入性,不需要修改其内部代码就可以实现监听。
KVO可以监听单个属性的变化,也可以监听集合对象的变化。
实现原理
KVO是通过isa-swizzling技术实现的,在运行时根据原类创建一个中间类,这个中间类是原类的子类,并动态修改当前对象的isa指针指向中间类。将Class方法重写,返回原类的Class,所以苹果建议在开发中不应该依赖isa指针,而是通过class实例方法来获取对象类型。
缺点
苹果提供的KVO自身存在很多问题,首先问题在于,KVO如果使用不当很容易崩溃,比如说重复的add和remove导致的崩溃,Observer被释放导致的崩溃,keypath传错导致的崩溃等等。
由于keyPath是字符串的形式,所以在其对象的属性名发生改变之后,字符串没有被改变很容易导致崩溃。我们可以利用系统的反射机制将keyPath反射出来,这样编译器可以在@selector()中进行合法性检查。
自己实现KVO
- 可以在NSObject的分类中实现
- 实现addObserver:forKey:with:方法
- 检查对象的类有没有响应的setter方法,如果没有则抛出异常。
- 检查对象isa指指针指向的类是不是一个KVO类,如果不是,新建一个继承当前类的子类,并把isa指针指向这个类。
- 检查对象的KVO类是否重写过setter方法,如果没有,添加重写的setter方法
- 添加观察者
具体来说,有以下步骤:
- 通过setterFirGeter方法获得相应的setter的方法名,也就是key的首字母大写,然后再前面加载set后面加上:,变成setKey:。然后再用class_getInstanceMethod去获取setKey:的实现,如果没有,再抛出异常。
- 看类名有没有我们定义的前缀,如果没有,我们去创建新的子类,并通过object_setClass()去修改isa指针。
动态创建新的类需要runtime中定义的objc_allocateClassPair()函数。传一个父类名以及额外的空间,会返回一个雷,然后给这个类添加方法和变量。
- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName
{
NSString *kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClazzName];
Class clazz = NSClassFromString(kvoClazzName);
if (clazz) {
return clazz;
}
// class doesn't exist yet, make it
Class originalClazz = object_getClass(self);
Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);
// grab class method's signature so we can borrow it
Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
const char *types = method_getTypeEncoding(clazzMethod);
class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
objc_registerClassPair(kvoClazz);
return kvoClazz;
}
- 重写setter方法。新的setter在调用原setter方法后,通知每个观察者。
- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName
{
NSString *kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClazzName];
Class clazz = NSClassFromString(kvoClazzName);
if (clazz) {
return clazz;
}
// class doesn't exist yet, make it
Class originalClazz = object_getClass(self);
Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);
// grab class method's signature so we can borrow it
Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
const char *types = method_getTypeEncoding(clazzMethod);
class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
objc_registerClassPair(kvoClazz);
return kvoClazz;
}
- 把观察的相关信息存在associatedObject中。
@interface PGObservationInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) PGObservingBlock block;
@end
网友评论