RxSwift(8)—— KVO底层探索(上)

就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!


RxSwift目录直通车--- 和谐学习,不急不躁!


KVO在我们实际开发之中运用非常之多,很多开发者都知道原理!但是这些原理是如何来的,一般都是浅尝辄止。这个篇章我会从 Swift 入手分析,探索KVO底层源码.希望让读者真正掌握这一块底层,知其然而知其所以然!

KVO简介

首先我们从KVO的三部曲开始

// 1: 添加观察
person.addObserver(self, forKeyPath: "name", options: .new, context: nil)
// 2: 观察响应回调
override func observeValue(forKeyPath keyPath:, of object:, change: , context:){}
// 3: 移除观察
person.removeObserver(self, forKeyPath: "name")

其实我们也知道,就是平时在开发的时候,我们也可以通过计算型属性也可以直接观察

var name: String = ""{
    willSet{
        print(newValue)
    }
    didSet{
        print(oldValue)
    }
}

问题来了:这两者有什么关系?

KVO与计算型属性的关系

下面我们开始分析,首先感谢苹果开源精神,在Github可以直接下载,我们通过 Swift 源码展开分析

public func willChangeValue<Value>(for keyPath: __owned KeyPath<Self, Value>) {
    (self as! NSObject).willChangeValue(forKey: _bridgeKeyPathToString(keyPath))
}

public func willChange<Value>(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: __owned KeyPath<Self, Value>) {
    (self as! NSObject).willChange(changeKind, valuesAt: indexes, forKey: _bridgeKeyPathToString(keyPath))
}

public func willChangeValue<Value>(for keyPath: __owned KeyPath<Self, Value>, withSetMutation mutation: NSKeyValueSetMutationKind, using set: Set<Value>) -> Void {
    (self as! NSObject).willChangeValue(forKey: _bridgeKeyPathToString(keyPath), withSetMutation: mutation, using: set)
}

public func didChangeValue<Value>(for keyPath: __owned KeyPath<Self, Value>) {
    (self as! NSObject).didChangeValue(forKey: _bridgeKeyPathToString(keyPath))
}

public func didChange<Value>(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: __owned KeyPath<Self, Value>) {
    (self as! NSObject).didChange(changeKind, valuesAt: indexes, forKey: _bridgeKeyPathToString(keyPath))
}

public func didChangeValue<Value>(for keyPath: __owned KeyPath<Self, Value>, withSetMutation mutation: NSKeyValueSetMutationKind, using set: Set<Value>) -> Void {
    (self as! NSObject).didChangeValue(forKey: _bridgeKeyPathToString(keyPath), withSetMutation: mutation, using: set)
}
  • willChangeValuedidChangeValue 作为数据改变的两个重要的方法
  • 我们通过这两个方法继续展开分析
class Target : NSObject, NSKeyValueObservingCustomization {
    // This dynamic property is observed by KVO
    @objc dynamic var objcValue: String
    @objc dynamic var objcValue2: String {
        willSet {
            willChangeValue(for: \.objcValue2)
        }
        didSet {
            didChangeValue(for: \.objcValue2)
        }
    }
}
  • 很明显的继承关系来自NSObject
  • 实现了 NSKeyValueObservingCustomization 的协议
public protocol NSKeyValueObservingCustomization : NSObjectProtocol {
    static func keyPathsAffectingValue(for key: AnyKeyPath) -> Set<AnyKeyPath>
    static func automaticallyNotifiesObservers(for key: AnyKeyPath) -> Bool
}
  • 这也是我们两个非常重要的方法,平时在开发也是很有利的方法,keyPathsAffectingValue能够建立 keyPath 的依赖,例如两个属性的变化同时影响一个重要属性的改变:进度 = 下载量 / 总量
  • automaticallyNotifiesObservers 自动开关
  • 很明显我们的计算型属性在willSet里面就调用willChangeValuedidSet调用didChangeValue,的确我们计算型属性是和我们KVO相关方法是有所关联,这里也直接证明!
  • OK,我们探索完这个问题,我们摸着这条线继续探索KVO底层

KVO底层

这里说明一下,本篇章的贴出的源码没有给大家省略,目的是想让大家认真阅读,自己对照学习。当然可能中间我也忽略过一些细节,源码直接贴出来方便自己理解

添加观察

person.addObserver(self, forKeyPath: "name", options: .new, context: nil)

现在我们开始探索底层源码:

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

  setup();
  [kvoLock lock];

  // Use the original class
  r = replacementForClass([self class]);

  /*
   * Get the existing observation information, creating it (and changing
   * the receiver to start key-value-observing by switching its class)
   * if necessary.
   */
  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];
}
  • 中间replacementForClass做了一些处理:

    • 创建了一个动态子类名字:"NSKVONotifing_原类的名字"
    • 添加了class、set、dealloc方法
    • 原类的isa与动态isa切换
  • 由原来的观察者进行迁移到 GSKVOInfo

- (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];
}
  • 判断我们的观察者是否能够响应:observeValueForKeyPath:ofObject:change:context:方法。常规操作,没有回调,响应就没有什么意义了!
  • 通过获取pathInfo来保存KVO信息
  • 中间对context&options的处理数据
  • NSKeyValueObservingOptionInitial就会主动发起一次KVO响应:observeValueForKeyPath

观察属性变化的时候

- (void) willChangeValueForKey: (NSString*)aKey
{
  GSKVOPathInfo *pathInfo;
  GSKVOInfo     *info;

  info = (GSKVOInfo *)[self observationInfo];
  if (info == nil)
    {
      return;
    }

  pathInfo = [info lockReturningPathInfoForKey: aKey];
  if (pathInfo != nil)
    {
      if (pathInfo->recursion++ == 0)
        {
          id    old = [pathInfo->change objectForKey: NSKeyValueChangeNewKey];

          if (old != nil)
            {
              /* We have set a value for this key already, so the value
               * we set must now be the old value and we don't need to
               * refetch it.
               */
              [pathInfo->change setObject: old
                                   forKey: NSKeyValueChangeOldKey];
              [pathInfo->change removeObjectForKey: NSKeyValueChangeNewKey];
            }
          else if (pathInfo->allOptions & NSKeyValueObservingOptionOld)
            {
              /* We don't have an old value set, so we must fetch the
               * existing value because at least one observation wants it.
               */
              old = [self valueForKey: aKey];
              if (old == nil)
                {
                  old = null;
                }
              [pathInfo->change setObject: old
                                   forKey: NSKeyValueChangeOldKey];
            }
          [pathInfo->change setValue:
            [NSNumber numberWithInt: NSKeyValueChangeSetting]
            forKey: NSKeyValueChangeKindKey];

          [pathInfo notifyForKey: aKey ofInstance: [info instance] prior: YES];
        }
      [info unlock];
    }
  [self willChangeValueForDependentsOfKey: aKey];
}
  • 通过pathInfo获取回之前的旧值
  • pathInfo->change 里面的数据处理
  • 重点:[pathInfo notifyForKey: aKey ofInstance: [info instance] prior: YES];开始发起响应通知
- (void) notifyForKey: (NSString *)aKey ofInstance: (id)instance prior: (BOOL)f
{
  unsigned      count;
  id            oldValue;
  id            newValue;

  if (f == YES)
    {
      if ((allOptions & NSKeyValueObservingOptionPrior) == 0)
        {
          return;   // Nothing to do.
        }
      [change setObject: [NSNumber numberWithBool: YES]
                 forKey: NSKeyValueChangeNotificationIsPriorKey];
    }
  else
    {
      [change removeObjectForKey: NSKeyValueChangeNotificationIsPriorKey];
    }

  oldValue = [[change objectForKey: NSKeyValueChangeOldKey] retain];
  if (oldValue == nil)
    {
      oldValue = null;
    }
  newValue = [[change objectForKey: NSKeyValueChangeNewKey] retain];
  if (newValue == nil)
    {
      newValue = null;
    }

  /* Retain self so that we won't be deallocated during the
   * notification process.
   */
  [self retain];
  count = [observations count];
  while (count-- > 0)
    {
      GSKVOObservation  *o = [observations objectAtIndex: count];

      if (f == YES)
        {
          if ((o->options & NSKeyValueObservingOptionPrior) == 0)
            {
              continue;
            }
        }
      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];
    }

  [change setObject: oldValue forKey: NSKeyValueChangeOldKey];
  [oldValue release];
  [change setObject: newValue forKey: NSKeyValueChangeNewKey];
  [newValue release];
  [self release];
}
  • change里面值的处理完毕之后
  • 让我们的观察者响应实现的KVO回调方法: [o->observer observeValueForKeyPath: aKey ofObject: instance change: change context: o->context];
  • 完美看到响应回调,舒服

移除观察者

移除观察的流程相对来说,比较简单了,但是优秀的我还是愿意和大家一起探索

- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
  GSKVOInfo *info;
  id            forwarder;

  /*
   * Get the observation information and remove this observation.
   */
  info = (GSKVOInfo*)[self observationInfo];
  forwarder = [info contextForObserver: anObserver ofKeyPath: aPath];
  [info removeObserver: anObserver forKeyPath: aPath];
  if ([info isUnobserved] == YES)
    {
      /*
       * The instance is no longer being observed ... so we can
       * turn off key-value-observing for it.
       */
      object_setClass(self, [self class]);
      IF_NO_GC(AUTORELEASE(info);)
      [self setObservationInfo: nil];
    }
  if ([aPath rangeOfString:@"."].location != NSNotFound)
    [forwarder finalize];
}
  • 拿回我们observationInfo就是我们信息收集者
  • 利用NSMapRemove(paths, (void*)aPath)移除
  • 动态子类的isa和原类的isa切换回来
  • 把当前设置的info置空

OK 完美解析了KVO底层源码!我们在探索完KVO底层实现才能说是真正的掌握了,而不是通过面试宝典背下结论,那是没有什么意义! 在真正的高手对决间一眼就能看出,中间忽略了一些小细节,比如set的多种情况,setNumber类型,setInt类型, setLong类型....我相信聪明的你一样可以解析读懂!

就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容