RxSwift-KVO底层探索(上)

作者: Cooci_和谐学习_不急不躁 | 来源:发表于2019-08-06 16:50 被阅读297次

    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度仰望天空,该死!我这无处安放的魅力!

    相关文章

      网友评论

        本文标题:RxSwift-KVO底层探索(上)

        本文链接:https://www.haomeiwen.com/subject/ysfwdctx.html