05-iOS底层学习 | KVO(1)

82 阅读1分钟
KVO

Key-Value Observing,键值监听,可以用于监听对象属性值的改变

@interface Person : NSObject

@property(nonatomic,assign)NSInteger age;

@end

#import "Person.h"

@implementation Person

- (void)setAge:(NSInteger)age
{
    _age = age;
    NSLog(@"%s",__FUNCTION__);
}

- (void)willChangeValueForKey:(NSString *)key
{
    [super willChangeValueForKey:key];
    NSLog(@"%s",__FUNCTION__);
}

- (void)didChangeValueForKey:(NSString *)key
{
    [super didChangeValueForKey:key];
    NSLog(@"%s",__FUNCTION__);
}

@end
self.ei = [[Person alloc] init];
self.ei.age = 18;
    
NSLog(@"%@",object_getClass(self.ei)); // Person
    
NSKeyValueObservingOptions options = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
[self.ei addObserver:self forKeyPath:@"age" options:options context:@"ei.age"];
    
NSLog(@"%@",object_getClass(self.ei)); // NSKVONotifying_Person
NSLog(@"%@",[object_getClass(self.ei) superclass]); // Person
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"监听到%@的%@值改变 %@ %@",object,keyPath,change,context);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.ei.age = 20;
}

- (void)dealloc
{
    [self.ei removeObserver:self forKeyPath:@"age" context:@"ei.age"];
}
总结
  • 添加监听后,runtime动态生成一个NSKVONotifying_Person子类,让实例对象isa指向这个子类
  • 在全新的子类里重写被监听的属性setter方法,当执行setter方法时会调用_NSSetXXXValueAndNotify函数
  • 函数先调用willChangeValueForKey
  • 在调用setter
  • 最后调用didChangeValueForKey
  • 内部触发observeValueForKeyPath: ofObject: change: context:
应用场景
  • 监听UIScrollView的偏移量,改变导航栏背景颜色
  • 给TextView增加pleaceHolder,通过KVO监听文本是否输入隐藏展示pleaceHolder
  • ......