KVO的底层实现原理
KVO的定义(Key-Value Observing)
俗称键值监听。它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
KVO是“观察者”设计模式的一种应用,利用它可以很容易实现视图组件和数据模型的分离,当数据模型的属性值改变之后作为监听器的视图组件就会被激发,激发时就会回调监听器自身。这种模式有利于两个类间的解耦合,尤其是对于业务逻辑与视图控制 这两个功能的解耦合。
和KVC类似,在ObjC中要实现KVO则必须实现NSKeyValueObServing协议,但不用担心,因为NSObject已经实现了该协议,因此几乎所有的ObjC对象都可以使用KVO.
KVO常用的方法
1>注册指定Key路径的监听器
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options context:(void *)context
相关参数:
observer:观察者,也就是KVO通知的订阅者。订阅着必须实现
observeValueForKeyPath:ofObject:change:context:方法
keyPath:描述将要观察的属性,相对于被观察者。
options:KVO的一些属性配置;有四个选项。
options所包括的内容:
NSKeyValueObservingOptionNew:change字典包括改变后的值
NSKeyValueObservingOptionOld: change字典包括改变前的值
NSKeyValueObservingOptionInitial:注册后立刻触发KVO通知
NSKeyValueObservingOptionPrior:值改变前是否也要通知(这个key决定了是否在改变前改变后通知两次)
context: 上下文,这个会传递到订阅着的函数中,用来区分消息,所以应当是不同的。
KVO的本质就是通过重写setter方法来实现的
KVO的本质是通过重写setter方法来实现的,Objective-C是一门动态的语言,苹果在底层帮我生成了一个被观察对象类的子类,假如当前类是Apple类,通过运行时机制,生产了一个叫做NSKVONotifying_Apple的子类,让后将被观察者对象(apple)的isa指针指向新创建的类,这样,在子类中重写setter方法,并且在setter方法之中通过发送消息告诉观察者所观察的对象的属性值发生了变化,这样就可以根据变化做对应的处理。下面是自己实现的KVO底层,通过创建一个NSObject类的分类,不多说,直接上仿照KVO实现的代码。
.h文件
#import
@interface NSObject (AJieKVO)
- (void)jie_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@end
.m文件
#import "NSObject+AJieKVO.h"
#import
@implementation NSObject (AJieKVO)
- (void)jie_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
// 1. 获取调用当前方法的对象所属的类的类名
NSString *oldClassName = NSStringFromClass([self class]);
// 创建子类的类名,是通过在父类类名前面加上AJieKVONotifing_前缀
NSString *newClassName = [NSString stringWithFormat:@"%@%@", @"AJieKVONotifing_", oldClassName];
// 2. 新建一个OldClass的子类AJieKVONotifing_
Class newClass = objc_allocateClassPair([self class], [newClassName UTF8String], 0);
// 3. 为子类的被观察的属性添加setter方法
// 获取当前所被观察的属性的名称,即keyPath的值,假设我们这儿只观察一级,不涉及xx.xx.xx
NSString *setterName = [NSString stringWithFormat:@"set%@%@:", [[keyPath substringToIndex:1] uppercaseString], [keyPath substringFromIndex:1]];
NSLog(@"setterName === %@", setterName);
SEL setterSEL = NSSelectorFromString(setterName);
class_addMethod(newClass, setterSEL, (IMP)setterMethod, "v@:@");
// 4. 注册子类
objc_registerClassPair(newClass);
// 5. 修改isa指针指向,将当前对象的isa指针指向新创建的子类
object_setClass(self, newClass);
// 6. 保存observer, keypath, options context 等信息,用于在setter方法发送消息用
objc_setAssociatedObject(self, "jie_observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, "jie_keyPath", keyPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, "jie_options", @(options), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, "jie_context", CFBridgingRelease(context), OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, "jie_setter", setterName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// 1. 重写setter方法,首先要调用super的setter方法
// 2. 通知外界
void setterMethod(id self, SEL _cmd, NSString *args) {
// 调用super的setter方法,通过运行时发送消息
// 获取当前类
Class currentClass = [self class];
// 获取当前对象类的父类
Class fatherClass = class_getSuperclass(currentClass);
// 修改isa指针指向,指向父类,然后发送消息
object_setClass(self, fatherClass);
// 发送消息,调用setter方法
// 1. 获取setter方法名称
id setterName = objc_getAssociatedObject(self, "jie_setter");
SEL setterSEL = NSSelectorFromString((NSString *)setterName);
objc_msgSend(self, setterSEL, args);
// 获取保存的observer, keypath, options context 等信息
id observer = objc_getAssociatedObject(self, "jie_observer");
id keyPath = objc_getAssociatedObject(self, "jie_keyPath");
id options = objc_getAssociatedObject(self, "jie_options");
id context = objc_getAssociatedObject(self, "jie_context");
// 通知外界的观察者 observeValueForKeyPath
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), self, keyPath, [(NSNumber *)options unsignedIntegerValue], context);
// 修改回isa指针指向
object_setClass(self, currentClass);
}
@end
了解了KVO的底层实现原理,就可以直接进行调用了
Student *s = [[Student alloc] init];
_student = s;
[s jie_addObserver:self forKeyPath:@"stu_name" options:0 context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
self.view.backgroundColor = [UIColor colorWithRed:arc4random_uniform(255) / 255.0 green:arc4random_uniform(255) / 255.0 blue:arc4random_uniform(255) / 255.0 alpha:1];
}
github源码链接