深入了解 KVO

2021-02-24  本文已影响0人  _一叶孤帆

含义

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

官方文档

基本使用

注册键值观察

示例:

  1. 新建一个 person
    image.png
  1. 使用 KVO 来监听 person 类中 age 的变化
image.png

输出结果

2021-02-24 21:44:37.344268+0800 KVO[43609:799884] 监听到 <Person: 0x6000000cc320> 的 age 属性值改了--- {
    kind = 1;
    new = 20;
    old = 10;
}--- 我是context

本质分析

  1. 构建一个 p2 对象
  2. 点击屏幕改变其 age 值
  3. 断点两个对象并打印其 isa

结果验证

(lldb) po self.p.isa
NSKVONotifying_Person

  Fix-it applied, fixed expression was: 
    self.p->isa
(lldb) po self.p1.isa
Person

  Fix-it applied, fixed expression was: 
    self.p1->isa
(lldb) 

可以发现两个对象的 isa 并不相同,而实例对象的 isa 指向其类对象,所以我们可以发送两个对象的类对象并不相同,p 对象被指向了一个名为 NSKVONotifying_Person 的类。

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
    frame #0: 0x000000010f0edf4f KVO`-[Person setAge:](self=0x0000600000c446d0, _cmd="setAge:", age=20) at Person.m:14:12
  * frame #1: 0x00007fff207c1749 Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:] + 646
    frame #2: 0x00007fff207c200b Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 68
    frame #3: 0x00007fff207bb3c1 Foundation`_NSSetIntValueAndNotify + 266
    frame #4: 0x000000010f0eda6e KVO`-[ViewController touchesBegan:withEvent:](self=0x00007fe8d7507540, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x0000600003b4c240) at ViewController.m:37:12
    frame #5: 0x00007fff246c4823 UIKitCore`forwardTouchMethod + 321
    frame #6: 0x00007fff246c46d1 UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49
    frame #7: 0x00007fff246d36c9 UIKitCore`-[UIWindow _sendTouchesForEvent:] + 622
    frame #8: 0x00007fff246d57db UIKitCore`-[UIWindow sendEvent:] + 4774
    frame #9: 0x00007fff246af57a UIKitCore`-[UIApplication sendEvent:] + 633
    frame #10: 0x00007fff24740000 UIKitCore`__processEventQueue + 13895
    frame #11: 0x00007fff24736973 UIKitCore`__eventFetcherSourceCallback + 104
    frame #12: 0x00007fff2038f38a CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #13: 0x00007fff2038f282 CoreFoundation`__CFRunLoopDoSource0 + 180
    frame #14: 0x00007fff2038e764 CoreFoundation`__CFRunLoopDoSources0 + 248
    frame #15: 0x00007fff20388f2f CoreFoundation`__CFRunLoopRun + 878
    frame #16: 0x00007fff203886d6 CoreFoundation`CFRunLoopRunSpecific + 567
    frame #17: 0x00007fff2bededb3 GraphicsServices`GSEventRunModal + 139
    frame #18: 0x00007fff24690e0b UIKitCore`-[UIApplication _run] + 912
    frame #19: 0x00007fff24695cbc UIKitCore`UIApplicationMain + 101
    frame #20: 0x000000010f0edff2 KVO`main(argc=1, argv=0x00007ffee0b11ce0) at main.m:17:12
    frame #21: 0x00007fff202593e9 libdyld.dylib`start + 1
    
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x000000010f0edb22 KVO`-[ViewController observeValueForKeyPath:ofObject:change:context:](self=0x00007fe8d7507540, _cmd="observeValueForKeyPath:ofObject:change:context:", keyPath=@"age", object=0x0000600000c446d0, change=0x0000600001b01dc0, context=@"我是context") at ViewController.m:45:63
    frame #1: 0x00007fff207be6f4 Foundation`NSKeyValueNotifyObserver + 329
    frame #2: 0x00007fff207c1e28 Foundation`NSKeyValueDidChange + 439
    frame #3: 0x00007fff207c17a8 Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:] + 741
    frame #4: 0x00007fff207c200b Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 68
    frame #5: 0x00007fff207bb3c1 Foundation`_NSSetIntValueAndNotify + 266
    frame #6: 0x000000010f0eda6e KVO`-[ViewController touchesBegan:withEvent:](self=0x00007fe8d7507540, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x0000600003b4c240) at ViewController.m:37:12
    frame #7: 0x00007fff246c4823 UIKitCore`forwardTouchMethod + 321
    frame #8: 0x00007fff246c46d1 UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49
    frame #9: 0x00007fff246d36c9 UIKitCore`-[UIWindow _sendTouchesForEvent:] + 622
    frame #10: 0x00007fff246d57db UIKitCore`-[UIWindow sendEvent:] + 4774
    frame #11: 0x00007fff246af57a UIKitCore`-[UIApplication sendEvent:] + 633
    frame #12: 0x00007fff24740000 UIKitCore`__processEventQueue + 13895
    frame #13: 0x00007fff24736973 UIKitCore`__eventFetcherSourceCallback + 104
    frame #14: 0x00007fff2038f38a CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #15: 0x00007fff2038f282 CoreFoundation`__CFRunLoopDoSource0 + 180
    frame #16: 0x00007fff2038e764 CoreFoundation`__CFRunLoopDoSources0 + 248
    frame #17: 0x00007fff20388f2f CoreFoundation`__CFRunLoopRun + 878
    frame #18: 0x00007fff203886d6 CoreFoundation`CFRunLoopRunSpecific + 567
    frame #19: 0x00007fff2bededb3 GraphicsServices`GSEventRunModal + 139
    frame #20: 0x00007fff24690e0b UIKitCore`-[UIApplication _run] + 912
    frame #21: 0x00007fff24695cbc UIKitCore`UIApplicationMain + 101
    frame #22: 0x000000010f0edff2 KVO`main(argc=1, argv=0x00007ffee0b11ce0) at main.m:17:12
    frame #23: 0x00007fff202593e9 libdyld.dylib`start + 1

可以看出会首先调用 Fountion 中的 _NSSetIntValueAndNotify,然后在内部又调用了父类的 setAge

动态类分析

分析其中的方法

NSKVONotifying_Person setAge:,class,dealloc,_isKVOA,

修改成员变量

不会触发 kvo, 因为不会调用 set 方法。

结论

所以本质就是为我们动态生成了一个类,切重写了 set 方法 并将 set 方法内部绑定调用了 Fountion 中的函数

上一篇 下一篇

猜你喜欢

热点阅读