附录-OC源码-KVO:KVO源码分析(GNUStep 版)

源码地址: 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:

这段函数中主要分为四部分

  1. 初始化流程: 声明变量, 并调用 setup(); 初始化及加锁
  2. 生成替换类:r = replacementForClass([self class]);其内部:
    • 通过父类和拼接出来的名字构造子类,调用 GSObjCMakeClass,复制变量
    • 调用GSObjCAddClassBehavior, 复制函数表
  3. 获取 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]);  
      }  
      
  4. 监听对应的 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

注释解释的比较清楚: 创建一个子类并覆写部分方法

  1. 子类的类名是从父类拼接出来的(这是中间类名字的来源)
  2. GSObjCMakeClass: 创建子类的方式是复制父类的变量表, 源码中能搜到比较简单, 不展开了
  3. 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 步, 其内部主要是:

  1. 判断要修改的属性,是否已经在被修改过的表中: 如果在, 直接退出, 如果不在, 准备修改
    if ([keys member: aKey] == nil)  
    
  2. 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++)  
    
  3. 循环中, 判断能不能拿到对应名字 set & _setsel, sel = NSSelectorFromString(a[i]);
    如果拿不到, 则数组下一个(既_set)
  4. 判断能否拿到 sig, [original instanceMethodSignatureForSelector: sel]; runtime 中的概念
    这一步可以拿到函数的整个签名信息, 里面会带参数等,便于后面生成对应函数
  5. switch-case, 根据不同的属性类型, 拿到已定义好的对应函数setterXXX
    注意: 这个已实现好的函数,就是实现监听的关键, 这个函数内部如下
      [self willChangeValueForKey: key];  
     (*imp)(self, _cmd, val);  
    // post setting code here  
      [self didChangeValueForKey: key];  
    
  6. 拿这个 imp, 加到类中(会替换掉原先的 setter), 类中的函数表是 key-value 结构, 这里 sel 对应的 imp 已被修改
    调用的是 runtime 中的class_addMethod
    if (class_addMethod(replacement, sel, imp, [sig methodType]))  
    
  7. 最后, 如果 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);  
        }  
    }  
    }  
}