Skip to content

KVO 实现原理

jiaxw32 edited this page Jun 6, 2020 · 6 revisions

测试 Demo

KVOExplorer

添加观察者前

  • 实例化一个 Person 对象,在添加观察者前,打印变量

    (lldb) po [personA _ivarDescription]
    <Person: 0x60000377d9e0>:
    in Person:
        _name (NSString*): nil
        _age (long): 0
    in NSObject:
        isa (Class): Person (isa, 0x10e8b36f0)
  • 查看方法列表

    (lldb) pmethods personA -a
    Class Methods:
    No methods were found
    
    Instance Methods:
    - (void).cxx_destruct 0x10e8aee10
    - (id)name 0x10e8aed60
    - (void)setName:(id)arg0  0x10e8aed90
    - (long long)age 0x10e8aedd0
    - (void)setAge:(long long)arg0  0x10e8aedf0

添加观察者后

  • 打印实例变量,添加观察者后 isa 指针指向了新了对象 NSKVONotifying_Person

    (lldb) po [personA _ivarDescription]
    <Person: 0x60000377d9e0>:
    in Person:
        _name (NSString*): nil
        _age (long): 0
    in NSObject:
        isa (Class): NSKVONotifying_Person (isa, 0x600000548630)
  • 查看 NSKVONotifying_Person 父类

    (lldb) po class_getSuperclass((id)0x600000548630)
    Perso
  • 打印方法列表

    (lldb) pmethods personA -a
    Class Methods:
    No methods were found
    
    Instance Methods:
    - (void)setName:(id)arg0  0x7fff258dba8b
    - (Class)class 0x7fff258da515
    - (void)dealloc 0x7fff258da27a
    - (bool)_isKVOA 0x7fff258da272
  • 打印 IMP

    (lldb) print (IMP)0x7fff258dba8b
    (IMP) $78 = 0x00007fff258dba8b (Foundation`_NSSetObjectValueAndNotify)
    (lldb) print (IMP)0x7fff258da515
    (IMP) $79 = 0x00007fff258da515 (Foundation`NSKVOClass)
    (lldb) print (IMP)0x7fff258da27a
    (IMP) $80 = 0x00007fff258da27a (Foundation`NSKVODeallocate)
    (lldb) print (IMP)0x7fff258da272
    (IMP) $81 = 0x00007fff258da272 (Foundation`NSKVOIsAutonotifying)

为变量赋值

  • 直接访问属性赋值,会触发 KVO

    personAB.name = @"John";
  • 使用 KVC 为属性赋值,也会触发 KVO

    [personAB setValue:@"Jack" forKey:@"name"];
  • 直接访问私有变量赋值,不会触发 KVO

    //Person.h
    @interface Person : NSObject{
        NSString *_nickname;
    }
    
    //Person.m
    - (void)setNickName:(NSString *)nickname{
        _nickname = nickname;
    }
    
    //ViewController.m
    [personAB setNickName:@"Air Jonh"];
  • 使用 KVC 为私有变量赋值,会触发 KVO

    [personAB setValue:@"Big Boss" forKey:@"_nickname"];
  • 调用堆栈

    * frame #0: 0x000000010e8ae9c2 KVOExplorer`-[ViewController observeValueForKeyPath:ofObject:change:context:](self=0x00007f913a409d70, _cmd="observeValueForKeyPath:ofObject:change:context:", keyPath=@"name", object=0x000060000377da20, change=0x000060000227f980, context=0x0000000000000000) at ViewController.m:90:18
        frame #1: 0x00007fff258de357 Foundation`NSKeyValueNotifyObserver + 329
        frame #2: 0x00007fff258e18f3 Foundation`NSKeyValueDidChange + 502
        frame #3: 0x00007fff258e11f5 Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:] + 741
        frame #4: 0x00007fff258e1af2 Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 68
        frame #5: 0x00007fff258dbb98 Foundation`_NSSetObjectValueAndNotify + 269
        frame #6: 0x000000010e8ae693 KVOExplorer`-[ViewController viewDidLoad](self=0x00007f913a409d70, _cmd="viewDidLoad") at ViewController.m:79:5

移除观察者后

  • 打印实例变量

    (lldb) po [personA _ivarDescription]
    <Person: 0x60000377d9e0>:
    in Person:
        _name (NSString*): nil
        _age (long): 0
    in NSObject:
        isa (Class): Person (isa, 0x10e8b36f0)
  • 打印方法列表

    (lldb) pmethods personA -a
    Class Methods:
    No methods were found
    
    Instance Methods:
    - (void).cxx_destruct 0x10e8aee10
    - (id)name 0x10e8aed60
    - (void)setName:(id)arg0  0x10e8aed90
    - (long long)age 0x10e8aedd0
    - (void)setAge:(long long)arg0  0x10e8aedf0

NSKVONotifying_<xxx>

当定义一个 NSKVONotifying_Person 类时,系统无法动态创建 NSKVONotifying_Persson 类,导致 KVO 失效,错误如下:

 [general] KVO failed to allocate class pair for name NSKVONotifying_Person, automatic key-value observing will not work for this class

参考资料

Clone this wiki locally