KVO 底层原理详解

基本原理-> 给一个对象的属性添加监听 当属性值发生变化时 会触发监听器的监听的方法

#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic, strong) Person *p;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.p=[[Person alloc]init];
    self.p.age=5;
    [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:@"额外信息"];
    // Do any additional setup after loading the view.
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.p.age=10;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"\n%@\n%@\n%@\n%@",keyPath,object,change,context);
    /*
     age
     <Person: 0x600001390970>
     {
         kind = 1;
         new = 10;
         old = 5;
     }
     额外信息
     */
}
-(void)dealloc{
    [self.p removeObserver:self forKeyPath:@"age"];
}

2、在给person 添加监听之后、其实苹果底层通过运行时动态给person添加了一个子类 NSKVONotifying_Person

   NSLog(@"%s",object_getClassName(self.p)); // Person
    [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:@"额外信息"];
    NSLog(@"%s",object_getClassName(self.p)); //NSKVONotifying_Person

3、在新生产的类会新生成4个方法

- (void)viewDidLoad {
    [super viewDidLoad];
    self.p=[[Person alloc]init];
    self.p.age=5;
    [self printMethodNamesOfClass:object_getClass(self.p)]; // Person age, setAge:
    [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:@"额外信息"];
    [self printMethodNamesOfClass:object_getClass(self.p)];//NSKVONotifying_Person setAge:, class, dealloc, _isKVOA,
}
- (void)printMethodNamesOfClass:(Class)cls
{
    unsigned int count;
    // 获得方法数组
    Method *methodList = class_copyMethodList(cls, &count);
    
    // 存储方法名
    NSMutableString *methodNames = [NSMutableString string];
    
    // 遍历所有的方法
    for (int i = 0; i < count; i++) {
        // 获得方法
        Method method = methodList[i];
        // 获得方法名
        NSString *methodName = NSStringFromSelector(method_getName(method));
        // 拼接方法名
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    
    // 释放
    free(methodList);
    
    // 打印方法名
    NSLog(@"%@ %@", cls, methodNames);
}

4个方法介绍

- (void)setAge:(int)age
{
    _NSSetIntValueAndNotify();
}

// 屏幕内部实现,隐藏了NSKVONotifying_Person类的存在
- (Class)class
{
    return [MJPerson class];
}

- (void)dealloc
{
    // 收尾工作
}

- (BOOL)_isKVOA
{
    return YES;
}

4、在新生成的这个类中 修改对象的属性时-> 其实会类似重写Set方法- 会调用Foundation 框架中的  

_NSSetXXXValueAndNotify 函数:

  • willChangeValueForKey:
  • 父类原来的setter
  • didChangeValueForKey:
  • 内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)

5、如要要手动触发一个KVO  监听器的监听方法

  • 调用改对象的  willChangeValueForKey
  • 调用改对象的  didChangeValueForKey
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//    self.p.age=10;
    [self.p willChangeValueForKey:@"age"];
    [self.p didChangeValueForKey:@"age"];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"\n%@\n%@\n%@\n%@",keyPath,object,change,context);
    /*
    age
     <Person: 0x6000019947f0>
     {
         kind = 1;
         new = 5;
         old = 5;
     }
     额外信息
     */
}

 6、修改对象的成员变量是不会触发KVO的、KVC赋值是能够触发KVO

posted @ 2019-12-25 00:36  ZhangShengjie  阅读(363)  评论(0编辑  收藏  举报