想要回答这个问题,首先须要弄明白当给一个对象的属性添加KVO后,系统作了哪些事情?git
因此,KVO的本质是修改属性的setter方法,在属性的setter方法里添加调用监听方法的逻辑,为了避免破坏原始类,系统又增长了动态建立子类并修改对象的isa指针的机制。github
想要知道如何手动触发KVO,首先须要弄明白系统是如何修改setter方法以调用监听方法的。数组
_NSSetXXXValueAndNotify
函数_NSSetXXXValueAndNotify
函数内部会调用 willChangeValueForKey:
和 didChangeValueForKey:
方法didChangeValueForKey:
内部会调用KVO监听者的监听方法因此,想要手动触发KVO,能够经过手动调用willChangeValueForKey:
和didChangeValueForKey:
来实现,须要强调这两个方法都必须调用才会起做用bash
手动实现KVO的过程就是把系统实现KVO的过程本身用代码实现一下。大体流程以下:app
objc_allocateClassPair
函数动态建立子类class_addMethod
函数重写setter方法详细内容可参考iOS_KVO_Study 框架
不会触发,直接修改为员变量并不会触发setter方法,所以也就不会触发KVO函数
KVO全称 Key-Value Observing,键值监听。 ui
[p1 addObserver:self
forKeyPath:@"age"
options:NSKeyValueObservingOptionNew
context:nil];
复制代码
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context {
//...实现监听处理
}
复制代码
[self removeObserver:self forKeyPath:@“age"]; 复制代码
在添加监听后,age属性的值在发生改变时就会通知监听者,执行监听者的observeValueForKeyPath
方法。接下来咱们就一步步探究为什么会在age值发生改变后通知监听者。spa
赋值操做会调用set方法,咱们经过重写Person类的setAge:方法,观察是不是KVO在set方法内部作了一些操做来通知监听者。指针
// ViewController类
- (void)viewDidLoad {
[super viewDidLoad];
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];
p1.age = 1;
p1.age = 2;
p2.age = 2;
// self 监听 p1的 age属性
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[p1 addObserver:self forKeyPath:@"age" options:options context:nil];
p1.age = 10;
[p1 removeObserver:self forKeyPath:@"age"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"监听到%@的%@改变了%@", object, keyPath,change);
}
// Person类
- (void)setAge:(NSInteger)age {
_age = age;
}
复制代码
经过观察发现p1和p2一样调用了setAge:方法,p1除了调用setAge:方法外还会执行监听者的observeValueForKeyPath方法。显然这些并非在setAge:方法中调用的。
既然不是经过修改setAge:方法来实现监听的,那addObserver方法对p1对象作了什么特殊处理呢?咱们经过打印isa指针来进行对比。
NSKVONotifying_原类
NSLog(@"添加KVO监听以前 - p1 = %p, p2 = %p", [p1 methodForSelector:@selector(setAge:)], [p2 methodForSelector:@selector(setAge:)]);
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[p1 addObserver:self forKeyPath:@"age" options:options context:nil];
[self printMethods:object_getClass(p2)];
[self printMethods:object_getClass(p1)];
NSLog(@"添加KVO监听以后 - p1 = %p, p2 = %p", [p1 methodForSelector:@selector(setAge:)], [p2 methodForSelector:@selector(setAge:)]);
复制代码
经过打印的地址信息,咱们发如今添加KVO监听以前,p1和p2的setAge:方法实现的地址相同,而通过KVO监听以后,p1的setAge方法实现的地址发生了变化,p1的setAge:方法的实现转换为了C语言的Foundation框架的 _NSSetLongLongValueAndNotify
函数。
NSKVONotifying_Person
的内部结构- (void)printMethods:(Class)cls {
unsigned int count;
Method *methods = class_copyMethodList(cls, &count);
NSMutableString *methodNames = [NSMutableString string];
[methodNames appendFormat:@"%@ - ", cls];
for (int i = 0; i < count; i++) {
Method method = methods[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
[methodNames appendString:methodName];
[methodNames appendString:@" "];
}
NSLog(@"%@", methodNames);
free(methods);
}
复制代码
打印内容以下:
setAge:
class
dealloc
_isKVOA
,下图是NSKVONotifying_Person的内存结构以及方法调用顺序。
这里NSKVONotifying_Person重写class方法是为了隐藏NSKVONotifying_Person。咱们在p1添加KVO后,打印p一、p2对象的class能够发现他们都返回Person。
NSLog(@"%@, %@", [p1 class], [p2 class]);
// 打印结果: Person, Person
复制代码
猜想NSKVONotifying_Person内重写的class内部实现大体为:
- (Class) class {
// 获得类对象,在找到类对象父类
return class_getSuperclass(object_getClass(self));
}
复制代码
- (void)willChangeValueForKey:(NSString *)key {
NSLog(@"willChangeValueForKey: - begin");
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey: - end");
}
- (void)didChangeValueForKey:(NSString *)key {
NSLog(@"didChangeValueForKey: - begin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey: - end");
}
复制代码
打印结果:
didChangeValueForKey
方法内部调用了
observeValueForKeyPath:ofObject:change:context:
方法。