源码地址: GNUstep: Download
我使用的版本: gnustep-base-1.26.0.tar
需要具备 runtime 的基础知识: 二. runtime 怎么实现封装 | runtime 的基础数据结构
KVC/KVO 源码苹果没开源, 这里用同源的 GNUStep 作为分析, 虽然实现上有不同, 但两边接口一直尽量保持一致.
探究源码便于了解实现思路
主要代码都描述在: source/NSKeyValueObserving.h
NSKeyValueObserving.m
NSKeyValueObserving.m 文件是 NSObject 的一个扩展
@implementation NSObject (NSKeyValueObserverRegistration)
从源码可以推测的是, 我们平时使用 KVO 时调用的addObserver等相关函数, 实际实现都会跑到这儿
NSObject 的 addObserver: forKeyPath: options: context:
这段函数中主要分为四部分
- 初始化流程: 声明变量, 并调用
setup();初始化及加锁 - 生成替换类:
r = replacementForClass([self class]);其内部:- 通过父类和拼接出来的名字构造子类,调用
GSObjCMakeClass,复制变量 - 调用
GSObjCAddClassBehavior, 复制函数表
- 通过父类和拼接出来的名字构造子类,调用
- 获取 info 并替换 isa:
info = (GSKVOInfo*)[self observationInfo];
info 是 KVO 生成过后会缓存起来的相关信息- 这一步内部就是 kvo 生效的核心: 替换 isa , 通过调用
object_setClass实现info = (GSKVOInfo*)[self observationInfo]; if (info == nil){ info = [[GSKVOInfo alloc] initWithInstance: self]; [self setObservationInfo: info]; // 替换 isa 的关键实现 object_setClass(self, [r replacement]); }
- 这一步内部就是 kvo 生效的核心: 替换 isa , 通过调用
- 监听对应的 Key:这一步就是判断监听的内容是否有属性嵌套, 或者说是否监听的是成员变量的属性(
xx.xx)- 如果是成员变量的属性,
- 生成一个forwarder
forwarder(runtime 中可知这个名字与消息转发相关) , 最终会有子对象处理 - 然后调用 info 的
addObserver:forKeyPath:options:context:
* 如果是自己的属性, - 调用
overrideSetterFor: 覆写修改对应属性 setter - 然后调用由 info 调用
addObserver:forKeyPath:options:context:
(ps. 看起来是函数内又重复调用了自己, 实际上这次的 addObserver 由 info 调用,他内部覆写了 addObserver 实现)
整体函数源码放最后面(或者直接对照文件看), 先逐个看子函数内部实现
1 - 初始化流程中的 setup()
这里面主要是对三个 map 做初始化, classTable, infoTable, dependentKeyTable
其中涉及的 kvoLock 是个全局静态变量, 可以知初始化只会执行一次
static inline void setup(){
if (nil == kvoLock) {
[gnustep_global_lock lock];
if (nil == kvoLock) {
kvoLock = [NSRecursiveLock new];
classTable = // ...
infoTable = // ...
dependentKeyTable = // ...
baseClass = NSClassFromString(@"GSKVOBase");
}
[gnustep_global_lock unlock];
}
}
2 - 生成替换类: replacementForClass
KVO 监听的方式其实是生成一个中间类, 替换对象的 isa 实现的, 具体在正文中描述 [[正文: KVO 的实现原理]]
这个函数内部在做的是从全局取个已实现的中间类, 如果取不到就创建一个.
其内部的initWithClass 中实现了变量表和函数表的 copy
static GSKVOReplacement * replacementForClass(Class c){
// 省略部分代码
// 尝试从全局变量 classTable 找个适合当前 class 的中间类
r = (GSKVOReplacement*)NSMapGet(classTable, (void*)c);
// 找不到, 重新创建一个
if (r == nil) {
r = [[GSKVOReplacement alloc] initWithClass: c];
NSMapInsert(classTable, (void*)c, (void*)r);
}
return r;
}
2 - 生成替换类: initWithClass
注释解释的比较清楚: 创建一个子类并覆写部分方法
- 子类的类名是从父类拼接出来的(这是中间类名字的来源)
GSObjCMakeClass: 创建子类的方式是复制父类的变量表, 源码中能搜到比较简单, 不展开了GSObjCAddClassBehavior: 做的是复制出函数表(调用的class_copyMethodList), 然后给新建的类加上GSObjCAddMethods
- (id) initWithClass: (Class)aClass {
// 省略部分代码
original = aClass;
// 拿到类名
superName = NSStringFromClass(original);
// 准备新建一个子类, 子类的类名由父类拼接而来
name = [@"GSKVO" stringByAppendingString: superName];
template = GSObjCMakeClass(name, superName, nil);
GSObjCAddClasses([NSArray arrayWithObject: template]);
replacement = NSClassFromString(name);
GSObjCAddClassBehavior(replacement, baseClass);
return self;
}
3 - 替换 isa: object_setClass
这个函数实现在 runtime 中, 函数内容如下:
lookUpImpOrNil: 是 runtime 中查找函数的一个流程, 具体看我这篇:
三. runtime 的消息机制 & 围绕消息机制设计的数据结构
changeIsa 就不在这里展开了, 直接放源码
objc-class.mm
Class object_setClass(id obj, Class cls){
if (!obj) return nil;
if (!cls->isFuture() && !cls->isInitialized()) {
lookUpImpOrNil(nil, @selector(initialize), cls, LOOKUP_INITIALIZE);
}
return obj->changeIsa(cls);
}
4 - 监听对应的 Key: overrideSetterFor
这个函数就是监听实现的其中核心之一, 修改属性的 Setter
整个函数挺长, 实际做的事情是 3 步, 其内部主要是:
- 判断要修改的属性,是否已经在被修改过的表中: 如果在, 直接退出, 如果不在, 准备修改
if ([keys member: aKey] == nil) - for 循环, 通过字符拼接, 修改对应属性两种前缀的函数
set及_set, 内部实现对 setter 的替换// 取从 1~最后的子字符 suffix = [aKey substringFromIndex: 1]; // 这个没具体看明白拿第一个字符干嘛, 暂不细究 u = uni_toupper([aKey characterAtIndex: 0]); tmp = [[NSString alloc] initWithCharacters: &u length: 1]; a[0] = [NSString stringWithFormat: @"set%@%@:", tmp, suffix]; a[1] = [NSString stringWithFormat: @"_set%@%@:", tmp, suffix]; for (i = 0; i < 2; i++) - 循环中, 判断能不能拿到对应名字
set&_set的sel,sel = NSSelectorFromString(a[i]);
如果拿不到, 则数组下一个(既_set) - 判断能否拿到 sig,
[original instanceMethodSignatureForSelector: sel];runtime 中的概念
这一步可以拿到函数的整个签名信息, 里面会带参数等,便于后面生成对应函数 - switch-case, 根据不同的属性类型, 拿到已定义好的对应函数
setterXXX
注意: 这个已实现好的函数,就是实现监听的关键, 这个函数内部如下[self willChangeValueForKey: key]; (*imp)(self, _cmd, val); // post setting code here [self didChangeValueForKey: key]; - 拿这个 imp, 加到类中(会替换掉原先的 setter), 类中的函数表是 key-value 结构, 这里 sel 对应的 imp 已被修改
调用的是 runtime 中的class_addMethodif (class_addMethod(replacement, sel, imp, [sig methodType])) - 最后, 如果 for 循环中有找到, 并修改过就直接结束. 如果没找到, 会从全局的 key 中中再找一次
❓这是个我没找到的点, 全局 key 叫dependentKeyTable, 但我找不到哪往里面加(有一个函数加了,但是没东西调)
GSKVOInfo 的 addObserver: forKeyPath: options: context:
上面提到, NSObject 的 addObserver 最终会变成 info 调用 addObserver
info 覆写了 NSObject 的 addObserver 实现
这里面和监听函数直接关系就不大了, 主要的实现都是在维护全局变量
维护 path
pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
NSMapInsert(paths, (void*)aPath, (void*)pathInfo);
维护 context: 通过遍历所有监听者的方式
count = [pathInfo->observations count];
while (count-- > 0){
if (o->observer == anObserver){
o->context = aContext;
o->options = options;
}
}
最后,处理了监听时指定的 NSKeyValueObservingOptionInitial, 要立刻收到消息, 就会走到下面
if (options & NSKeyValueObservingOptionInitial) {
}
附录 1: NSObject 的 addObserver 实现
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext{
GSKVOInfo *info;
GSKVOReplacement *r;
NSKeyValueObservationForwarder *forwarder;
NSRange dot;
setup();
[kvoLock lock];
r = replacementForClass([self class]);
info = (GSKVOInfo*)[self observationInfo];
if (info == nil)
{
info = [[GSKVOInfo alloc] initWithInstance: self];
[self setObservationInfo: info];
object_setClass(self, [r replacement]);
}
/*
* Now add the observer.
*/
dot = [aPath rangeOfString:@"."];
if (dot.location != NSNotFound)
{
forwarder = [[NSKeyValueObservationForwarder alloc]
initWithKeyPath: aPath
ofObject: self
withTarget: anObserver
context: aContext];
[info addObserver: anObserver
forKeyPath: aPath
options: options
context: forwarder];
}
else
{
[r overrideSetterFor: aPath];
[info addObserver: anObserver
forKeyPath: aPath
options: options
context: aContext];
}
[kvoLock unlock];
}
附录 2: NSObject 的 addObserver 实现
(void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext
{
GSKVOPathInfo *pathInfo;
GSKVOObservation *observation;
unsigned count;
if ([anObserver respondsToSelector:
@selector(observeValueForKeyPath:ofObject:change:context:)] == NO)
{
return;
}
[iLock lock];
pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
if (pathInfo == nil)
{
pathInfo = [GSKVOPathInfo new];
// use immutable object for map key
aPath = [aPath copy];
NSMapInsert(paths, (void*)aPath, (void*)pathInfo);
[pathInfo release];
[aPath release];
}
observation = nil;
pathInfo->allOptions = 0;
count = [pathInfo->observations count];
while (count-- > 0)
{
GSKVOObservation *o;
o = [pathInfo->observations objectAtIndex: count];
if (o->observer == anObserver)
{
o->context = aContext;
o->options = options;
observation = o;
}
pathInfo->allOptions |= o->options;
}
if (observation == nil)
{
observation = [GSKVOObservation new];
GSAssignZeroingWeakPointer((void**)&observation->observer,
(void*)anObserver);
observation->context = aContext;
observation->options = options;
[pathInfo->observations addObject: observation];
[observation release];
pathInfo->allOptions |= options;
}
if (options & NSKeyValueObservingOptionInitial)
{
/* If the NSKeyValueObservingOptionInitial option is set,
* we must send an immediate notification containing the
* existing value in the NSKeyValueChangeNewKey
*/
[pathInfo->change setObject: [NSNumber numberWithInt: 1]
forKey: NSKeyValueChangeKindKey];
if (options & NSKeyValueObservingOptionNew)
{
id value;
value = [instance valueForKeyPath: aPath];
if (value == nil)
{
value = null;
}
[pathInfo->change setObject: value
forKey: NSKeyValueChangeNewKey];
}
[anObserver observeValueForKeyPath: aPath
ofObject: instance
change: pathInfo->change
context: aContext];
}
[iLock unlock];
}
附录 3: 精简后的overrideSetterFor
- (void) overrideSetterFor: (NSString*)aKey
{
if ([keys member: aKey] == nil) {
// 一堆声明变量操作
suffix = [aKey substringFromIndex: 1];
u = uni_toupper([aKey characterAtIndex: 0]);
tmp = [[NSString alloc] initWithCharacters: &u length: 1];
// 开始拼接函数名,
a[0] = [NSString stringWithFormat: @"set%@%@:", tmp, suffix];
a[1] = [NSString stringWithFormat: @"_set%@%@:", tmp, suffix];
[tmp release];
for (i = 0; i < 2; i++){
sel = NSSelectorFromString(a[i]);
//找 sel
if (sel == 0){continue;}
sig = [original instanceMethodSignatureForSelector: sel];
//找函数描述
if (sig == 0){ continue;}
if ([sig numberOfArguments] != 3) {continue; // Not a valid setter method.}
type = [sig getArgumentTypeAtIndex: 2];
// 判断不同的函数类型, 用来生成不同的setter 函数, 最后会拿到一个 imp
switch (*type){}
if (imp != 0){
// 这里会把新的 setter 加到函数表中
if (class_addMethod(replacement, sel, imp, [sig methodType]))
found = YES;
else{
NSLog(@"Failed to add setter method for %s to %s",
sel_getName(sel), class_getName(original));
}
}
}
// 记录缓存, 准备结束了
if (found == YES){ [keys addObject: aKey]; }
else {
// 前面没找到, 没替换成功, 会从 dependentKeyTable 再来一次
NSMapTable *depKeys = NSMapGet(dependentKeyTable, original);
}
// 还是找不到, 没辙, dalog
if (!found)
{
NSDebugLLog(@"KVC", @"class %@ not KVC compliant for %@",
original, aKey);
}
}
}
}