手动实现KVO+Runtime
前言
KVO:观察者观察被观察者的属性的变化
实现原理:KVC+runtime
自动实现
大概分为三步:假设有类person,有一个属性值age,当person类在第一次观察时,系统会在运行期建立person派生类。可以根据控制台打断点调试
监听谁就给谁添加观察者
[p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
[_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
[self addObserver:self forKeyPath:@"response" options:NSKeyValueObservingOptionNew context:nil];
上述代码执行前:

上述代码执行后:

1、通过以上可知在系统运行时期动态创建person的派生类NSKVONOtifying_Person
2、在派生类NSKVONOtifying_Person中重写person类set方法,NSKVONOtifying_Person类在被重写的set方法中实现通知机制。此时类NSKVONOtifying_Person重写class方法,并且系统将原本指向person对象的isa指针指向类NSKVONOtifying_Person对象。
3、最后person类调用对象set方法时,实质是NSKVONOtifying_Person调用了重新写的set方法,在set方法中用super调用其父类的set方法,而后调用监听改变的方法-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 作出通知响应。
项目中应用如下
属性
@property(nonatomic, strong) SelectProjectResponse *response;
注册
[self addObserver:self forKeyPath:@"response" options:NSKeyValueObservingOptionNew context:nil];
监听变化
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == self && [keyPath isEqualToString:@"response"]) {
SelectProjectResponse *response = [change objectForKey:NSKeyValueChangeNewKey];
if ([response isEmpty]) {
[self showEmptyInView:self.tableView withText:@"" withImage:nil block:^(UIView *emptyView) {
[emptyView removeAllSubviews];
UILabel *tipLabel = [UILabel newAutoLayoutView];
[emptyView addSubview:tipLabel];
tipLabel.text = @"没有搜索到结果,换个关键词试试";
tipLabel.textColor = HEXCOLOR(0x4d4d4d);
tipLabel.font = FONT_SMALL;
[tipLabel autoAlignAxisToSuperviewAxis:ALAxisVertical];
[tipLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:45;
}];
}
else {
[self removeEmptyInView:self.tableView];
}
}
手动实现
了解以上原理后,我们可以自己尝试实现
- 创建NSObject的分类,自定义方法,在方法中创建中间派生类
- 重写set方法
- set方法中通知调用
- (void)zz_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
self.zz_observer = observer;
self.zz_keyPath = keyPath;
NSString * className = [NSString stringWithFormat:@"ZZKVONotifying_%@",NSStringFromClass(self.class)];
const char *cla = className.UTF8String;
Class subP = objc_allocateClassPair([self class], cla, 0);
class_addMethod(subP, @selector(setAge:), (IMP)setAge, "v@:@");
objc_registerClassPair(subP);
object_setClass(self, subP);
}
void setAge(id self , SEL _cmd,NSUInteger age){
NSString *keyPath = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(observeKeyPathKey));
NSObject *obj = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(observeKey));
[self willChangeValueForKey:keyPath];
/*这里还应该调用父亲的[super setAge:age]方法*/
[self didChangeValueForKey:keyPath];
[obj observeValueForKeyPath:keyPath ofObject:self change:@{@"new":[NSNumber numberWithInteger:age]} context:nil];
}
以上,就实现了简单的KVO监听属性变化响应的功能。
当然系统调用原比这要复杂的多。当添加观察者后,方法内部需要做很多的安全判断,如该对象是否实现了属性的set、get方法,如果 没有就抛出异常;是否已经存在该派生类对象,如果没有创建如果有就返回等等。另外,同一个对象的属性可以有多个观察者,所以内部必须要有一个集合去记录,当发生变化时,需要各个通知一一回调。