系统底层源码分析(1)——KVO

在日常开发中经常会用到KVO,而RxSwift框架也有KVO,在了解RxSwift框架的KVO之前,我们先来了解一下系统KVO的底层原理。

(在这之前说个题外话,以前面试的时候曾经被问到:“KVO的底层原理是什么?”,当时我说:“不知道,我猜可能在setter方法的底层源码里加了个回调。”,当时觉得自己好菜♂️,虽然说了总比不说好,可是说了个好傻的答案。)

  • 但是大家都知道,苹果的源码看不了,那怎么办呢?还有一个靠谱的方法就是看GNUstep,因为它的源码和苹果的很像。

早在 1985 年, Steve Jobs 离开苹果电脑 (Apple) 后成立了 NeXT 公司, 并于 1988 年推出了 NeXT 电脑, 使用 NeXTStep 为作业系统. 在当时, NeXTStep 是相当先进的系统. 以 Unix (BSD) 为基础, 使用 PostScript 提供高品质的使用者图形介面, 并以 Objective-C 语言提供完整的物件导向环境.

尽管 NeXT 在软体上的优异, 其硬体销售成绩不佳, 不久之后, NeXT 便转型为软体公司. 1994 年, NeXT 与升阳 (Sun Microsystem) 合作推出 OpenStep 界面, 目标为跨平台的物件导向程式开发环境. NeXT 接着推出实作 OpenStep 介面的 OPENSTEP 系统, 可在 Mach, Microsoft Windows NT, Sun Solaris 及 HP/UX 上执行. 1996 年, 苹果电脑买下 NeXT, 做为苹果电脑下一代作业系统的基础, OPENSTEP 系统便演进成为 MacOS X 的 Cocoa 环境.

在 1995 年, 自由软体基金会 (Free Software Fundation) 开始了 GNUstep 计划, 目的在实作 OpenStep 介面, 以提供 Linux/BSD 系统一个完整的程式发展环境. 但由于 OpenStep 界面过于庞大, 开发人力不足, 及许多技术在当时尚未成熟 (如 Display PostScript), 所以直到目前为止, GNUstep 才算是一个完整的程式开发环境.

我们从基本的开始,写OC的代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSLog(@"KVO OC");
}

- (void)dealloc {
    [self.person removeObserver:self forKeyPath:@"name"];
}
  • (一)添加观察者
  1. 我们直接从添加观察者开始,在GNUstep源码全局搜索并找到相应方法:
@implementation NSObject (NSKeyValueObserverRegistration)

- (void) addObserver: (NSObject*)anObserver
      forKeyPath: (NSString*)aPath
         options: (NSKeyValueObservingOptions)options
         context: (void*)aContext
{
  GSKVOInfo             *info;
  GSKVOReplacement      *r;
  NSKeyValueObservationForwarder *forwarder;
  NSRange               dot;

  setup();//获取 baseClass: GSKVOBase
  [kvoLock lock];

  r = replacementForClass([self class]);//创建子类并注册

  info = (GSKVOInfo*)[self observationInfo];
  if (info == nil)//判断是否交换过
    {
      info = [[GSKVOInfo alloc] initWithInstance: self];//创建GSKVOInfo
      [self setObservationInfo: info];//设置GSKVOInfo
      object_setClass(self, [r replacement]);//切换isa
    }

  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];//替换原来的setter方法
      [info addObserver: anObserver
             forKeyPath: aPath
                options: options
                context: aContext];
    }

  [kvoLock unlock];
}
  1. 首先调用setup();,这里面会获取到一个GSKVOBase类:
static inline void
setup()
{
  if (nil == kvoLock)
    {
      [gnustep_global_lock lock];
      if (nil == kvoLock)
    {
      ...
      baseClass = NSClassFromString(@"GSKVOBase");
    }
      [gnustep_global_lock unlock];
    }
}
  1. 然后通过replacementForClass([self class]);创建子类并注册:
static GSKVOReplacement *
replacementForClass(Class c)
{
   ...
  r = (GSKVOReplacement*)NSMapGet(classTable, (void*)c);//从列表获取
  if (r == nil)//没有就创建
    {
      r = [[GSKVOReplacement alloc] initWithClass: c];
      NSMapInsert(classTable, (void*)c, (void*)r);
    }
  [kvoLock unlock];
  return r;
}
@implementation GSKVOReplacement

- (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);//添加GSKVObase的重写方法

  keys = [NSMutableSet new];

  return self;
}
  1. 这里先是调用GSObjCMakeClass(name, superName, nil);创建子类:
NSValue *
GSObjCMakeClass(NSString *name, NSString *superName, NSDictionary *iVars)
{
  ...
  classSuperClass = NSClassFromString(superName);
  ...
  classNameCString = [name UTF8String];
  //创建新类
  newClass = objc_allocateClassPair(classSuperClass, classNameCString, 0);
  if ([iVars count] > 0)
    {
      ...
      //添加变量
      if (NO
        == class_addIvar(newClass, iVarName, iVarSize, iVarAlign, iVarType))
        { ... }
    }
    }

  return [NSValue valueWithPointer: newClass];
}
  1. 然后通过GSObjCAddClasses([NSArray arrayWithObject: template]);进行注册:
void
GSObjCAddClasses(NSArray *classes)
{
  NSUInteger    numClasses = [classes count];
  NSUInteger    i;

  for (i = 0; i < numClasses; i++)
    {
      objc_registerClassPair((Class)[[classes objectAtIndex: i] pointerValue]);//注册
    }
}
  1. 接下来回到第3点,下一步就是通过GSObjCAddClassBehavior(replacement, baseClass);添加GSKVObase的方法:
void
GSObjCAddClassBehavior(Class receiver, Class behavior)
{
  ...
  methods = class_copyMethodList(behavior, &count);//GSKVOBase方法列表
  
  if (methods == NULL) 
  { ... }
  else
    {
      GSObjCAddMethods (receiver, methods, NO);//添加方法
      free(methods);
    }
    ...
}
void
GSObjCAddMethods(Class cls, Method *list, BOOL replace)
{
  ...
  while ((m = list[index++]) != NULL)
    {
      SEL       n = method_getName(m);
      IMP       i = method_getImplementation(m);
      const char    *t = method_getTypeEncoding(m);
      //添加方法
      if (YES == class_addMethod(cls, n, i, t))
    { ... }
      else if (YES == replace)
    { ... } 
      else
    { ... }
    }
}

这里可以先看一下GSKVObase的方法:

@implementation GSKVOBase

- (void) dealloc { ... }
- (Class) class { ... }
- (void) setValue: (id)anObject forKey: (NSString*)aKey { ... }
- (void) takeStoredValue: (id)anObject forKey: (NSString*)aKey { ... }
- (void) takeValue: (id)anObject forKey: (NSString*)aKey { ... }
- (void) takeValue: (id)anObject forKeyPath: (NSString*)aKey { ... }
- (Class) superclass { ... }

@end
    1. 子类的准备完成后回到第1点,通过object_setClass(self, [r replacement]);切换isa指向子类。
    1. 接着会创建GSKVOInfoGSKVOInfo会保存很多信息,所以后面调用了[info addObserver: anObserver forKeyPath: aPath options: options context: forwarder];,由原观察者进行迁移到GSKVOInfo观察者迁移),然后在里面做各种处理(context,options等)。
  1. 在后面还有一步,通过[r overrideSetterFor: aPath];替换原来的settter方法(会根据对应类型替换成GSKVOSettersetter):
@implementation GSKVOReplacement

- (void) overrideSetterFor: (NSString*)aKey
{
  if ([keys member: aKey] == nil)
    {
      ...
      suffix = [aKey substringFromIndex: 1];//从第2个字母开始截取
      u = uni_toupper([aKey characterAtIndex: 0]);//取首字母->大写
      tmp = [[NSString alloc] initWithCharacters: &u length: 1];//char->String
      a[0] = [NSString stringWithFormat: @"set%@%@:", tmp, suffix]//拼接成相应setter方法
      a[1] = [NSString stringWithFormat: @"_set%@%@:", tmp, suffix];
      [tmp release];
      for (i = 0; i < 2; i++)
        {
          /*
           * Replace original setter with our own version which does KVO
           * notifications.
           */
          sel = NSSelectorFromString(a[i]);//方法选择器
         ...
          sig = [original instanceMethodSignatureForSelector: sel];//监听对象的方法
          ...
          type = [sig getArgumentTypeAtIndex: 2];
          switch (*type)
            {
              case _C_CHR:
              case _C_UCHR:
                imp = [[GSKVOSetter class]
                  instanceMethodForSelector: @selector(setterChar:)];//其他类型操作一样
                break;
              case _C_SHT:
              case _C_USHT: ...
              case _C_INT:
              case _C_UINT: ...
              case _C_LNG:
              case _C_ULNG: ...
              case _C_FLT: ...
              case _C_DBL: ...
              case _C_BOOL: ...
              case _C_ID:
              case _C_CLASS:
              case _C_PTR:
                imp = [[GSKVOSetter class]
                  instanceMethodForSelector: @selector(setter:)];
                break;
              case _C_STRUCT_B: ...
              default: ...
            }

          if (imp != 0)
            {
          //添加方法;属性setter方法选择器关联上面的imp指针
          if (class_addMethod(replacement, sel, imp, [sig methodType]))
        { ... }
          else
        { ... }
            }
        }
      ...
}
  • 属性setter方法选择器关联上面的imp指针,执行时对应类型属性调用上面的方法。
  • (二)监听响应
  1. 一切准备好后,当值改变时,将会走setter方法,但是由于之前切换了isa,所以会来到这里:
@implementation GSKVOSetter

- (void) setterChar: (unsigned char)val
{
  NSString  *key;
  Class     c = [self class];
  void      (*imp)(id,SEL,unsigned char);
 //_cmd是SEL
  imp = (void (*)(id,SEL,unsigned char))[c instanceMethodForSelector: _cmd];//获取监听对象属性setter的原imp指针

  key = newKey(_cmd);
  if ([c automaticallyNotifiesObserversForKey: key] == YES)
    {
      // pre setting code here
      [self willChangeValueForKey: key];
      (*imp)(self, _cmd, val);//执行原setter方法
      // post setting code here
      [self didChangeValueForKey: key];
    }
  else
    {
      (*imp)(self, _cmd, val);
    }
  RELEASE(key);
}

- (void) setterDouble: (double)val{ ... }
...//还有其他类型

或者用setValue:forKey:/setValue:forKeyPath:赋值时:

@implementation GSKVOBase

- (void) setValue: (id)anObject forKey: (NSString*)aKey
{
  ...
  if ([[self class] automaticallyNotifiesObserversForKey: aKey])
    {
      [self willChangeValueForKey: aKey];//原来多了这个
      imp(self,_cmd,anObject,aKey);
      [self didChangeValueForKey: aKey];
    }
  else
    {
      imp(self,_cmd,anObject,aKey);
    }
}
  1. 它们都会调用willChangeValueForKey
@implementation NSObject (NSKeyValueObserverNotification)

- (void) willChangeValueForKey: (NSString*)aKey
{
  ...
  if (pathInfo != nil)
    {
      if (pathInfo->recursion++ == 0)
        {
          ...
          if (old != nil)
            { ... }
          else if (pathInfo->allOptions & NSKeyValueObservingOptionOld)
            { ... }
          ...
          //通知响应
          [pathInfo notifyForKey: aKey ofInstance: [info instance] prior: YES];
        }
      [info unlock];
    }

  [self willChangeValueForDependentsOfKey: aKey];
}
@implementation GSKVOPathInfo

- (void) notifyForKey: (NSString *)aKey ofInstance: (id)instance prior: (BOOL)f
{
  ...
while (count-- > 0)
    {
      GSKVOObservation  *o = [observations objectAtIndex: count];

      if (f == YES)
        { ... }
      else
        {
          if (o->options & NSKeyValueObservingOptionNew)
            {
              [change setObject: newValue
                         forKey: NSKeyValueChangeNewKey];//保存新值
            }
        }

      if (o->options & NSKeyValueObservingOptionOld)
        {
          [change setObject: oldValue
                     forKey: NSKeyValueChangeOldKey];//保存旧值
        }

      [o->observer observeValueForKeyPath: aKey
                                 ofObject: instance
                                   change: change
                                  context: o->context];//回调
    }
  ...
}

保存新旧值后,通过[o->observer observeValueForKeyPath: aKey ofObject: instance change: change context: o->context];进行回调出去。

原来KVO就是在setter方法的底层源码里加了回调。

(突然好开心,没想到当年说的好傻的答案起码方向是对的)

  • (三)移除观察者
  1. 移除观察者就是反着来:
@implementation NSObject (NSKeyValueObserverRegistration)

- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
  ...
  [info removeObserver: anObserver forKeyPath: aPath];//
  if ([info isUnobserved] == YES)
    {
      object_setClass(self, [self class]);//把isa换回来
      IF_NO_GC(AUTORELEASE(info);)
      [self setObservationInfo: nil];//
    }
  ...
}
  • 总结

KVO的原理就是,通过在底层创建新的子类,切换监听对象的isa指向子类,交换属性setter时执行的imp指针,属性赋值时,执行新的set系列方法,然后会调用willChangeValueForKey走向监听回调。

  • 补充

其实Swift的源码有一部分开源了,所以我们也可以看看相关的:

在KVOKeyPaths.swift源码里找
在NSObject.swift源码里找到
  • 在Swift项目里使用KVO要加上@objc,因为底层走的还是OC的那套方法。

你可能感兴趣的:(系统底层源码分析(1)——KVO)