含义
KVO 全程是 Key-Value Observing
,又称 键值监听, 可以用于监听某个对象属性值的改变。
基本使用
注册键值观察
-
使用方法将观察者注册到观察对象 addObserver:forKeyPath:options:context:。
-
observeValueForKeyPath:ofObject:change:context: 在观察者内部实现接收更改通知消息。
-
当观察者 removeObserver:forKeyPath: 不再应接收消息时,使用该方法注销观察者。至少在从内存释放观察者之前调用此方法。
示例:
- 新建一个
person
类
image.png
- 使用 KVO 来监听 person 类中 age 的变化
输出结果
2021-02-24 21:44:37.344268+0800 KVO[43609:799884] 监听到 <Person: 0x6000000cc320> 的 age 属性值改了--- {
kind = 1;
new = 20;
old = 10;
}--- 我是context
本质分析
- 构建一个 p2 对象
- 点击屏幕改变其 age 值
- 断点两个对象并打印其 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 中的函数
网友评论