美文网首页
手动实现KVO+Runtime

手动实现KVO+Runtime

作者: Queen_BJ | 来源:发表于2020-08-31 18:18 被阅读0次

前言

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方法,如果 没有就抛出异常;是否已经存在该派生类对象,如果没有创建如果有就返回等等。另外,同一个对象的属性可以有多个观察者,所以内部必须要有一个集合去记录,当发生变化时,需要各个通知一一回调。

以上参考代码

参考简书以及代码

相关文章

网友评论

      本文标题:手动实现KVO+Runtime

      本文链接:https://www.haomeiwen.com/subject/vzcpsktx.html