美文网首页
iOS KVO实现原理和FBKVOController的使用

iOS KVO实现原理和FBKVOController的使用

作者: YYFast | 来源:发表于2020-10-02 12:29 被阅读0次

    我们通常需要监听一个对象的某个属性值的变化,来动态的修改UI或者展示;

    这时候KVO就排上了用场,KVO是苹果专门提供的用于监听某个对象的属性变化的方法;

    例如:
    要监听一个person对象的属性age值的变化,实现步骤如下;

    1.系统KVO的使用

    1、 给对象添加一个observer:

    - (void)viewDidLoad {
        [super viewDidLoad];
        self.person1 = [[YYPerson alloc]init];
        [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
    }
    

    2、 实现observer回调方法:

    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        if ([keyPath isEqualToString:@"age"]) {
            NSLog(@"age = %d",[[change objectForKey:@"new"] intValue]);
        }
    }
    

    3、 在dealloc方法中移除观察者:

    - (void)dealloc{
        [self.person1 removeObserver:self forKeyPath:@"age"];
    }
    

    2.KVO实现原理

    要弄清楚KVO的实现原理,我们就得知道,在添加观察者和没添加观察者之间有什么区别呢?

    如下图,一个添加了observer的person1和没有添加observer的person2,打印其isa指针,区别如下:

    添加监听后isa指针变化

    person1和person2的区别:

    • person1的 isa指针 指向: NSKVONotifying_YYPerson

    • person2的 isa指针 指向: YYPerson

    2.1 未使用KVO监听的对象:

    • 直接调用父类的setAge方法,改变成员变量_age的值;
    未添加KVO对象

    2.2 添加了KVO监听的对像:

    会通过runtime动态的生成一个 NSKVONotifying_YYPerson 的中间类;

    • 然后实例对象person1的 isa指针 指向这个新生成的类;

    • NSKVONotifying_YYPerson 是YYPerson的子类,所以它的superclass指针指向YYPerson;

    添加KVO对象

    伪代码 模拟 NSKVONotifying_YYPerson 内部实现:

    // .h
    
    #import "YYPerson.h"
    
    @interface NSKVONotifying_YYPerson : YYPerson
    
    @end
    
    //.m
    
    @implementation NSKVONotifying_YYPerson
    
    - (void)setAge:(int)age{
        _NSSetIntValueAndNotify();
    }
    
    //_NSSetIntValueAndNotify() 方法为Foundation框架中的方法,此处为伪代码,模拟实现
    void _NSSetIntValueAndNotify(){
    
        [self willChangeValueForKey:@"age"];
        
        //真正的去改变父类中的age值
        [super setAge:age];
        
        [self didChangeValueForKey:@"age"];
    }
    
    - (void)didChangeValueForKey:(NSString *)key{
        //通知监听器, XXX属性值改变
        [observer observeValueForKeyPath:@"age" ofObject:self change:value context:nil];
    }
    
    @end
    
    
    • person1.age调用流程:

      • 通过person1的 isa指针 找到 NSKVONotifying_YYPerson中的setAge方法并调用

      • NSKVONotifying_YYPerson 中的setAge方法,会去调用Foundation框架中的方法_NSSetIntValueAndNotify();

      • _NSSetIntValueAndNotify()方法会依次调用如下方法:

        • 调用willChangeValueForKey();

        • [super setAge:age]; 调用YYPerson中的setAge方法,真正改变age属性的值;

        • didChangeValueForKey() 通知监听器,age的值变化了,回调观察者中实现的回调方法;

    重写YYPerson中的方法willChangeValueForKey和didChangeValueForKey验证调用流程:

    重写YYPerson中的方法

    2.3 NSKVONotifying_YYPerson中其它被重写的方法

    1、 通过[self.person1 class]和runtime获取类方法object__getClass(self.person1)对比,可以发现中间类NSKVONotifying_YYPerson 重写了class方法,因为苹果不希望开发者知道这个类的存在,所以重写这个方法

    打印class

    在中间类中,NSKVONotifying_YYPerson还重写了其他哪些方法,可以使用以下打印method列表方法打印一下:

    - (void)printMethodList:(Class )cls{
        
        unsigned int count;
        //获得类的方法数组
        Method *methodList =  class_copyMethodList(cls, &count);
        
        //遍历所有的方法
        NSMutableString *methodString = [NSMutableString string];
        for (int i = 0; i < count; i ++) {
            Method method = methodList[i];
            NSString *mstring = NSStringFromSelector(method_getName(method));
            //拼接方法名
            [methodString appendString:mstring];
            [methodString appendString:@","];
        }
        free(methodList); //methodList是通过C语言copy得到的对象,需要释放
        NSLog(@"方法列表:%@",methodString);
    }
    

    分别传入person1和person2的类对象,打印结果:

    打印方法列表

    这3个方法简单实现如下,其中class方法直接返回的是[YYPerson class],这样可以不让外界知道这个类的存在:

    - (Class)class{
        return [YYPerson class];
    }
    
    - (void)dealloc{
        //在移除观察者的时候做收尾工作
    }
    
    - (BOOL)isKVOA{
        return YES; //是不是KVO
    }
    
    

    2.4 验证

    2、 验证 NSKVONotifying_YYPerson 的存在

    验证中间类的存在

    3、 验证 NSKVONotifying_YYPerson 中的setAge调用了Foundation中的_NSSetIntValueAndNotify()方法

    _NSSetIntValueAndNotify()方法

    4、 _NSSetIntValueAndNotify()方法和属性的类型是int,double,char是对应的:

    image.png

    3.FBKVOController 的使用

    1、 实例化一个controller对象,并添加监听:

        //初始化后有一个是否强引用观察者参数retainObserved:NO可以避免循环引用问题
        self.fbVC = [[FBKVOController alloc]initWithObserver:self retainObserved:NO];
    
        [self.fbVC observe:self.person1 keyPath:@"age" options:NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
            NSLog(@"age = %d",[[change objectForKey:@"new"] intValue]);
        }];
    
    

    2、取消监听 (可以不取消)

    注意: FBKVOController的一个特点就是:不移除observer,不会崩溃 所以这一步可以省略

    - (void)dealloc{
        [sekf.fbVC unobserve:self.person1];
        //[self.fbVC unobserveAll];�  //或者可以取消全部的监听
        NSLog(@"%s",__func__);
    }
    

    3.1 系统KVO和FBKVOController优缺点

    1.系统KVO的问题

    • 当观察者被销毁之前,需要手动移除观察者,否则会出现程序异常(向已经销毁的对象发送消息);
    • 可能会对同一个被监听的属性多次添加监听,这样我们会接收到多次监听的回调结果;
    • 当观察者对多个对象的不同属性进行监听,处理监听结果时,需要在监听回调的方法中,作出大量的判断;
    • 当对同一个被监听的属性进行两次removeObserver时,会导致程序crash。这种情况通常出现在父类中有一个KVO,在父类的dealloc中remove一次,而在子类中再次remove。

    2、FBKVOController的优点

    • 可以同时对一个对象的多个属性进行监听,写法简洁;
    • 通知不会向已释放的观察者发送消息;
    • 增加了block和自定义操作对NSKeyValueObserving回调的处理支持;
    • 不需要在dealloc 方法中手动移除观察者,而且移除观察者不会抛出异常,当FBKVOController对象被释放时, 观察者被隐式移除;

    例如:如下面代码,添加一个监听属性的方法有单独的block实现,不需要和系统的KVO那样在同一个方法里面去判断,解耦,看起来也简洁

        [self.fbVC observe:self.person1 keyPath:@"age" options:NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
            NSLog(@"age = %d",[[change objectForKey:@"new"] intValue]);
        }];
        
        [self.fbVC observe:self.person2 keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
            NSLog(@"age = %@",[[change objectForKey:@"new"] intValue]);
        }];
    

    关于FBKVOController实现原理,可参考文章链接

    相关文章

      网友评论

          本文标题:iOS KVO实现原理和FBKVOController的使用

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