欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS KVO实现原理和FBKVOController的使用

程序员文章站 2024-03-24 14:04:10
...

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指针,区别如下:

iOS KVO实现原理和FBKVOController的使用

person1和person2的区别:

  • person1的 isa指针 指向: NSKVONotifying_YYPerson

  • person2的 isa指针 指向: YYPerson

2.1 未使用KVO监听的对象:

  • 直接调用父类的setAge方法,改变成员变量_age的值;

iOS KVO实现原理和FBKVOController的使用

2.2 添加了KVO监听的对像:

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

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

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

iOS KVO实现原理和FBKVOController的使用

伪代码 模拟 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验证调用流程:

iOS KVO实现原理和FBKVOController的使用

2.3 NSKVONotifying_YYPerson中其它被重写的方法

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

iOS KVO实现原理和FBKVOController的使用

在中间类中,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的类对象,打印结果:

iOS KVO实现原理和FBKVOController的使用

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

- (Class)class{
    return [YYPerson class];
}

- (void)dealloc{
    //在移除观察者的时候做收尾工作
}

- (BOOL)isKVOA{
    return YES; //是不是KVO
}

2.4 验证

2、 验证 NSKVONotifying_YYPerson 的存在

iOS KVO实现原理和FBKVOController的使用

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

iOS KVO实现原理和FBKVOController的使用

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

iOS KVO实现原理和FBKVOController的使用

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