本文将利用holper disassembler
class dump
窥探KVO背后的原理。
准备工作
- holper disassembler
- class dump
- LLDB 调试指令如:&arg0 symbol breakpoint (SEL)&arg0 frame 等等
- 汇编相关知识。操作符 寄存器等
希望解决的问题
- KVO注册期间发生经过了什么,内部组成是什么
- KVO如何实现弱引用
- KVO的observationInfo如何实现
Foundation.framework
通过class dump 查看 Foundation.framework 的目录结构及对象结构,这里有一篇继承结构说明
通过holper disassembler
反编译 Foundation.framework 的可执行文件,切记不是.tdb结尾的文件。反编译成功后,搜索“addObserver”关键字,可便看见如下方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25void -[NSObject addObserver:forKeyPath:options:context:](void * self, void * _cmd, void * arg2, void * arg3, unsigned long long arg4, void * arg5) {
/*
* 参数说明 self, _cmd 对应OC的方法中target selector,
* arg2:observer对象
* arg3:path
* arg4:options
* arg5:context
*/
pthread_mutex_lock(__NSKeyValueObserverRegistrationLock);
*__NSKeyValueObserverRegistrationLockOwner = pthread_self();
rax = object_getClass(self);
// 获取NSKeyValueUnnestedProperty(继承与NSKeyValueProperty) 对象
rax = _NSKeyValuePropertyForIsaAndKeyPath(rax, arg3, arg2);
//真正的方法
[self _addObserver:arg2 forProperty:rax options:arg4 context:arg5];
*__NSKeyValueObserverRegistrationLockOwner = 0x0;
pthread_mutex_unlock(__NSKeyValueObserverRegistrationLock);
if (0x0 != 0x0) {
objc_exception_rethrow();
}
return;
}
NSKeyValueProperty
NSKeyValueProperty对象结构,NSKeyValueUnnestedProperty对象结构
获取NSKeyValueProperty对象
1 |
|
NSKeyValueContainerClass
NSKeyValueContainerClass类结构
获取NSKeyValueContainerClass
1 | int __NSKeyValueContainerClassForIsa(int arg0, int arg1, int arg2) { |
经过上面两步准备工作之后,会调用下的方法并传入准备好的NSKeyValueProperty
,其中部分枚举定义可参考NSKeyValueObserving.h
_addObserver真正的实现
1 |
|
NSKeyValueObserverInfo
创建NSKeyValueObserverInfo的逻辑
1 | int __NSKeyValueObservationInfoCreateByAdding(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7) { |
我们可以发现上面针对observationInfo
都是以弱引用的方式存贮的,那么真正是谁来管理observationInfo
的生命周期呢
生命周期
我们可从这几个方面找到答案
ObservationInfo
的setter getter方法- 设置breakpoint 在dealloc 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void -[NSObject setObservationInfo:](void * self, void * _cmd, void * arg2) {
r14 = arg2;
rbx = self;
//主角登场,全局管理ObservationInfo的CFDictionary
rdi = *_NSKeyValueObservationInfoPerObject;
if (rdi == 0x0) {
rax = CFDictionaryCreateMutable(0x0, 0x0, 0x0, 0x0);
rdi = rax;
*_NSKeyValueObservationInfoPerObject = rax;
}
rsi = !rbx;
if (r14 != 0x0) {
rdx = r14;
CFDictionarySetValue(rdi, rsi, rdx);
}
else {
CFDictionaryRemoveValue(rdi, rsi);
}
return;
}
__NSKeyValueRemoveObservationInfoForObject 在这个方法里面我们也能看到 _NSKeyValueObservationInfoPerObject
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64void _NSKVODeallocate(int arg0, int arg1) {
r12 = arg1;
r15 = arg0;
if (*_NSKVODeallocate.onceToken != 0xffffffffffffffff) {
dispatch_once(_NSKVODeallocate.onceToken, ^ { /* block implemented at ___NSKVODeallocate_block_invoke */ });
}
rax = object_getClass(r15);
rbx = rax;
if (class_getMethodImplementation(rax, @selector(observationInfo)) != *_NSKVODeallocate.NSObjectObservationInfoImp) {
r14 = 0x0;
}
else {
r14 = class_getMethodImplementation(rbx, @selector(setObservationInfo:)) == *_NSKVODeallocate.NSObjectSetObservationInfoImp ? 0x1 : 0x0;
}
// 获取object对应的observationInfo
r13 = __NSKeyValueRetainedObservationInfoForObject(r15, 0x0);
// 获取notifyInfo
rax = object_getIndexedIvars(rbx);
var_38 = rax;
// 调用object原来的dealloc实现
rbx = class_getInstanceMethod(*rax, r12);
if (r14 != 0x0) {
// observationInfo不存在才对, 如果还存在, 说明没有正确地移除observer
method_invoke(r15, 0x0);
if (r13 != 0x0) {
__NSKeyValueRemoveObservationInfoForObject(r15);
[r13 release];
}
if (0x0 != 0x0) {
objc_exception_rethrow();
}
}
else {
*var_50 = r15;
*(var_50 + 0x8) = r13;
*(var_50 + 0x10) = 0x0;
// 移除watcher
__NSKeyValueAddObservationInfoWatcher(var_50);
method_invoke(r15, rbx);
if (var_48 != 0x0) {
r14 = dyld_get_program_sdk_version();
*(int8_t *)var_29 = 0x0;
rbx = (var_29 == 0x0 ? 0x1 : 0x0) | (CFPreferencesGetAppBooleanValue(@"NSKVODeallocateCleansUpBeforeThrowing", *_kCFPreferencesCurrentApplication, var_29) == 0x0 ? 0x1 : 0x0);
if ((r14 <= 0x7ffff) && (rbx != 0x0)) {
_NSLog(@"An instance %p of class %@ was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debu…", r15, *var_38);
_NSKVODeallocateBreak(r15);
}
else {
r14 = [var_48 description];
if (rbx == 0x0) {
__NSKeyValueRemoveObservationInfoForObject(var_50);
}
rdx = *_NSInternalInconsistencyException;
[NSException raise:rdx format:@"An instance %p of class %@ was deallocated while key value observers were still registered with it. Current observation info: %@"];
}
}
__NSKeyValueRemoveObservationInfoWatcher(var_50);
[var_48 release];
if (0x0 != 0x0) {
objc_exception_rethrow();
}
}
return;
}
总结
KVO源码中添加观察者时整体的大致流程是什么?
- 将keyPath、class等信息封装成NSKeyValueProperty,分别解析一般属性(@”aa”)、可计算属性(@”@aa”)、属性链(@”aa.bb.@cc.dd“),进行子类化,缓存在CFMutableSet中方便下次快速取出。
- 将NSKeyValueProperty、context、options、observer等信息封装成NSKeyValueObservance,缓存在NSHashTable中。
- 倘若设置了NSKeyValueObservingOptionInitial选项,会在注册观察服务时调用一次触发方法。
- 动态创建名为NSKVONotifying_+原来类名的新类,重写其dealloc、_isKVOA方法,再重写class方法,利用object_setClass()函数将其isa指针指向原先的类。
- 重写willChangeValueForKey:和didChangeValueForKey:方法,重写被观察属性的setter方法,在setter中先调用willChangeValueForKey:方法,然后调用父类的 setter 方法对成员变量赋值,之后再调用 didChangeValueForKey: 方法。
- didChangeValueForKey: 方法中会调用observeValueForKeyPath:ofObject:change:context:方法。
KVO中所封装组件的关系是怎样的?
- 将keyPath、class等信息封装成NSKeyValueProperty,使用CFMutableSet缓存NSKeyValueProperty。
- 将observer、property、options、context 、originalObservable等信息封装成NSKeyValueObservance,使用NSHashTable(NSKeyValueShareableObservationInfos)缓存。
- NSKeyValueObservationInfo与NSKeyValueObservance的关系是: NSKeyValueObservationInfo中有一个observances数组,数组里面是NSKeyValueObservance对象。
- 每一个object都有一个observationInfo属性(void *类型),它与NSKeyValueObservationInfo会相互转化。并交由_NSKeyValueObservationInfoPerObject全局管理
附上动态库的本地路径
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/