iOS KVO实现原理和FBKVOController的使用

iOS KVO实现原理和FBKVOController的使用

我们通常需要监听一个对象的某个属性值的变化,来动态的修改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实现原理,可参考文章链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值