美文网首页
深入理解KVO

深入理解KVO

作者: 03a336dff0ec | 来源:发表于2018-09-15 15:09 被阅读56次

    iOS | KVO | Objective-C

    KVO的本质是什么,如何手动触发KVO?


    1.什么是KVO

    KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变

    添加监听:

    typedef enum NSKeyValueObservingOptions : NSUInteger {
        // 新值(包含于回调方法change字典中)
        NSKeyValueObservingOptionNew = 0x01,
        // 旧值(包含于回调方法change字典中)
        NSKeyValueObservingOptionOld = 0x02,
        // 观察最初的值(在注册观察服务时会调用一次触发方法)
        NSKeyValueObservingOptionInitial = 0x04,
        // 分别在值修改前后触发方法(即一次修改有两次触发)
        NSKeyValueObservingOptionPrior = 0x08
    } NSKeyValueObservingOptions;
    
    /**
    监听属性方法,方法调用者为被观察对象
    
    @param observer 观察者/订阅者
    @param keyPath  要观察的属性 
    @param options  监听变化条件
    @param context  上下文,将会传递到监听回调函数中
    */
    - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
    

    监听回调:

    - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;
    

    移除监听:

    - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
    

    KVO使用大家都比较熟悉,Demo应该就没有写的必要了,下面我们直接来探索下本质

    2.KVO的本质

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    
        self.person1 = [[Person alloc] init];
        self.person1.age = 10;
    
        self.person2 = [[Person alloc] init];
        self.person2.age = 15;
    
        // 输出 >> 监听之前:Person, Person
        NSLog(@"监听之前:%@, %@", object_getClass(self.person1), object_getClass(self.person2));
        // 输出 >> 监听之前 setter 方法:0x10d2fb550, 0x10d2fb550
        NSLog(@"监听之前 setter 方法:%p, %p",
        [self.person1 methodForSelector:@selector(setAge:)],
        [self.person2 methodForSelector:@selector(setAge:)]);
    
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
        // 输出 >> 监听之后:NSKVONotifying_Person, Person
        NSLog(@"监听之前:%@, %@", object_getClass(self.person1), object_getClass(self.person2));
        // 输出 >> 监听之后 setter 方法:0x10d643bf4, 0x10d2fb550
        NSLog(@"监听之后 setter 方法:%p, %p",
        [self.person1 methodForSelector:@selector(setAge:)],
        [self.person2 methodForSelector:@selector(setAge:)]);
    
        // (lldb)po ([NSKVONotifying_Person class]).superclass Person
        // (lldb) p (IMP)0x10d2fb550 (IMP) $0 = 0x000000010d2fb550 (KVO与KVC`-[Person setAge:] at Person.h:13)
        // (lldb) p (IMP)0x10d643bf4 (IMP) $1 = 0x000000010d643bf4 (Foundation`_NSSetLongLongValueAndNotify)
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        self.person1.age = 20;
        self.person2.age = 25;
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        NSLog(@"监听到:%@ 的:%@ 属性值改变了,change:%@ - context:%@", object, keyPath, change, context);
    }
    

    通过断点调试,我们发现 person2 的类对象没有发生变化,person1 的类对象变成了 NSKVONotifying_Person,而且是Person的子类。

    使用 Runtime 打印 NSKVONotifying_Person 方法列表:

        unsigned int methCount = 0;
        NSMutableArray *methodArr = [NSMutableArray array];
        Method *meths = class_copyMethodList([NSClassFromString(@"NSKVONotifying_Person") class], &methCount);
        for(int i = 0; i < methCount; i++) {
            Method meth = meths[i];
            SEL sel = method_getName(meth);
            const char *name = sel_getName(sel);
            [methodArr addObject:[NSString stringWithUTF8String:name]];
        }
        if (methodArr.count) NSLog(@"%@", methodArr);
        free(meths);
    
    • NSKVONotifying_Person 实例方法列表:
      • setAge:
      • class
      • dealloc
      • _isKVOA

    总结:
    1> 添加监听时,Runtime动态生成 NSKVONotifying_Person类,并且使 person1 的 isa 指针指向新的类
    2> 重写 setAge: ,person1 调用 setter 方法时会从 NSKVONotifying_Person 开始查找,在自己的类对象中能够找到,所以会调用自己的 setAge:方法( 会调用Foundation的_NSSetValueAndNotify函数)
    3> _NSSet
    ValueAndNotify 调用流程:willChangeValueForKey -> [super setAge:] (Person 的 setter 方法) -> didChangeValueForKey(同时触发 observeValueForKeyPath 监听回调方法,订阅者接收)
    4> class 方法:重写 class 方法的目的是什么呢?(lldb) po self.person1.class 输出为:Person,原来,苹果粑粑是想要隐藏NSKVONotifying_Person,让开发者无感,使用时与未添加监听时无异
    5> dealloc方法:释放 KVO 新产生的资源
    6> _isKVOA方法:标记这个新类 KVO 机制新建的

    willChangeValueForKey/didChangeValueForKey还有疑惑的同学可以在 Person.m 中对这两个方法进行重写,再进行调试以变理解。

    相关文章

      网友评论

          本文标题:深入理解KVO

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