美文网首页
KVO底层探索

KVO底层探索

作者: Maji1 | 来源:发表于2019-11-19 09:46 被阅读0次

    问题

    1.iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)

    答. 当一个对象使用了KVO监听,iOS系统会修改这个对象的isa指针,改为指向一个全新的通过Runtime动态创建的NSKVONotifying_Person子类,子类拥有自己的set方法实现,set方法实现内部会顺序调用willChangeValueForKey方法、原来的setter方法实现、didChangeValueForKey方法,而didChangeValueForKey方法内部又会调用监听者的observeValueForKeyPath:ofObject:change:context:监听方法。

    2.如何手动触发KVO?

    答. 被监听的属性的值被修改时,就会自动触发KVO。如果想要手动触发KVO,则需要我们自己调用willChangeValueForKeydidChangeValueForKey方法即可在不改变属性值的情况下手动触发KVO,并且这两个方法缺一不可。

    • KVO的全称 Key-Value Observing(俗称“键值监听”),可以用于监听某个对象属性值的改变。基于Runtime

    首先贴出Swift源码下载地址,通过Swift源码分析。

    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)
        }
    }
    

    问题来了,这两种使用方式有什么关系?带着这个问题我们来进行探索。

    关系

    拿到了Swift源码,但是如何找到跟KVO相关的demo,一脸懵逼。。。
    想一下,对任意的对象都可以使用KVO,所以大胆猜测一下,相关的方法应该会写在NSObject的类里面。然后通过搜索addObserver:确实发现NSObject.swift文件,打开文件查看:

    public struct NSKeyValueObservedChange<Value> {
        public typealias Kind = NSKeyValueChange
        public let kind: Kind
        ///newValue and oldValue will only be non-nil if .new/.old is passed to `observe()`. In general, get the most up to date value by accessing it directly on the observed object instead.
        public let newValue: Value?
        public let oldValue: Value?
        ///indexes will be nil unless the observed KeyPath refers to an ordered to-many property
        public let indexes: IndexSet?
        ///'isPrior' will be true if this change observation is being sent before the change happens, due to .prior being passed to `observe()`
        public let isPrior:Bool
    }
    

    这段代码明显就是KVO监听值变化的相关状态

    继续往下找发现了NSKeyValueObservingCustomization协议:

    public protocol NSKeyValueObservingCustomization : NSObjectProtocol {
        static func keyPathsAffectingValue(for key: AnyKeyPath) -> Set<AnyKeyPath>
        static func automaticallyNotifiesObservers(for key: AnyKeyPath) -> Bool
    }
    

    1、keyPathsAffectingValue(for key: AnyKeyPath) -> Set<AnyKeyPath>
    通过一个key观察多个属性值的改变
    2、automaticallyNotifiesObservers (for key: AnyKeyPath) -> Bool
    想要手动调用或自己实现KVO需要重写该方法该方法返回YES表示可以调用,返回NO则表示不可以调用。

    继续往下走。。。此处省略很多代码,走到最后发现了如下代码:

        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这两个方法。

    既然这两个方法比较重要,那么我们再来搜素一下willChangeValuedidChangeValue。又发现了一个文件KVOKeyPaths.swift,打开查看:

    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)
            }
        }
        @objc dynamic var objcValue3: String
        
        // This Swift-typed property causes vtable usage on this class.
        var swiftValue: Guts
        
        override init() {
            self.swiftValue = Guts()
            self.objcValue = ""
            self.objcValue2 = ""
            self.objcValue3 = ""
            super.init()
        }
        
        static func keyPathsAffectingValue(for key: AnyKeyPath) -> Set<AnyKeyPath> {
            if (key == \Target.objcValue) {
                return [\Target.objcValue2, \Target.objcValue3]
            } else {
                return []
            }
        }
        
        static func automaticallyNotifiesObservers(for key: AnyKeyPath) -> Bool {
            if key == \Target.objcValue2 || key == \Target.objcValue3 {
                return false
            }
            return true
        }
        
        func print() {
            Swift.print("swiftValue \(self.swiftValue.value), objcValue \(objcValue)")
        }
    }
    

    1、发现这个Target遵守了NSKeyValueObservingCustomization协议并且实现了协议的两个方法。
    2、计算型属性在willSet里面就调用willChangeValue(for: \.objcValue2),didSet调用didChangeValue(for: \.objcValue2),这里说明计算型属性是和KVO相关方法有所关联的!

    底层

    我们都知道苹果底层并不开源,所以真正的底层源码是无法探究到的。但是我们可以从GNUstep源码来了解一下,这里贴出GNUStep源码地址。
    拿到GNUstep源码

    • 第一步:添加观察

    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
      // 使用初始类Persion
      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([self class])这句代码内部创建了一个子类
    • info = (GSKVOInfo*)[self observationInfo] 获取观察信息
    • [info addObserver: anObserver forKeyPath: aPath options: options context: aContext]添加监听者
    1、如何创建子类

    查看GSKVOReplacement类的初始化方法:

    - (id) initWithClass: (Class)aClass
    {
      NSValue       *template;
      NSString      *superName;
      NSString      *name;
      ...
      ginal = aClass;
    
      /*
       * Create subclass of the original, and override some methods
       * with implementations from our abstract base class.
       * 创建子类,并且在基类里重写了一些方法
       */
      superName = NSStringFromClass(original);
      name = [@"GSKVO" stringByAppendingString: superName];
      template = GSObjCMakeClass(name, superName, nil);
      GSObjCAddClasses([NSArray arrayWithObject: template]);
      replacement = NSClassFromString(name);
      GSObjCAddClassBehavior(replacement, baseClass);
    
      /* Create the set of setter methods overridden.
       * 创建 setter方法重写 集合
       */
      keys = [NSMutableSet new];
    
      return self;
    }
    
    • 1、 GSObjCMakeClass:创建了名字为GSKVOPerson的子类
    • 2、GSObjCAddClasses:注册类
    • 3、GSObjCAddClassBehavior(replacement, baseClass):添加了基类GSKVOBase的相关方法

    原类的isa与动态isa切换

    我们来看一下GSObjCAddClassBehavior(replacement, baseClass)函数内的实现:

    void
    GSObjCAddClassBehavior(Class receiver, Class behavior)
    {
      ...
      ...
      ...
      /* Add instance methods */
      methods = class_copyMethodList(behavior, &count);               
      GSObjCAddMethods (receiver, methods, NO);
      free(methods);
      /* Add class methods */
      methods = class_copyMethodList(object_getClass(behavior), &count);
      GSObjCAddMethods (object_getClass(receiver), methods, NO);
      free(methods);
    
      /* Add behavior's superclass, if not already there. */
      if (!GSObjCIsKindOf(receiver, behavior_super_class))
        {
          GSObjCAddClassBehavior (receiver, behavior_super_class);
        }
      GSFlushMethodCacheForClass (receiver);
    }
    

    我们发现会调用GSObjCAddMethods(Class cls, Method *list, BOOL replace)函数

    void
    GSObjCAddMethods(Class cls, Method *list, BOOL replace)
    {
      unsigned int  index = 0;
      char      c;
      Method    m;
      c = class_isMetaClass(cls) ? '+' : '-';
    
      while ((m = list[index++]) != NULL)
        {
          SEL       n = method_getName(m);
          IMP       i = method_getImplementation(m);
          const char    *t = method_getTypeEncoding(m);
    
          /* This will override a superclass method but will not replace a
           * method which already exists in the class itsself.
           * 覆盖父类方法,但是如果当前类已经实现了该方法就不再替换
           */
          if (YES == class_addMethod(cls, n, i, t)){
              BDBGPrintf("    added %c%s\n", c, sel_getName(n));
          }  else if (YES == replace) {
          /* If we want to replace an existing implemetation ...
               * 替换方法的实现
           */
          method_setImplementation(class_getInstanceMethod(cls, n), i);
              BDBGPrintf("    replaced %c%s\n", c, sel_getName(n));
        }   else {
              BDBGPrintf("    skipped %c%s\n", c, sel_getName(n));
        }
        }
    }
    

    到这里我们应该明白了,是如何创建子类的。

    2、添加监听者

    首先来看一下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;  }
      pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
    
      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;
      }
      // 如果添加监听者时设置包含 NSKeyValueObservingOptionInitial就会立即执行
      if (options & NSKeyValueObservingOptionInitial)
        {
          [pathInfo->change setObject: [NSNumber numberWithInt: 1]
                               forKey:  NSKeyValueChangeKindKey];
          [anObserver observeValueForKeyPath: aPath
                                    ofObject: instance
                                      change: pathInfo->change
                                     context: aContext];
        }
    }
    
    • 1、先判断监听者有没有实现监听方法

    • 2、GSKVOPathInfo类型的pathInfo存储path信息

    • 3、while循环取出当前观察者

    • 4、如果添加监听者时设置包含 NSKeyValueObservingOptionInitial就会立即执行监听方法

    • 第二步:观察属性值的改变

    我们通过搜索willChangeValueForKey :找到如下代码:

    - (void) willChangeValueForKey: (NSString*)aKey
    {
      GSKVOPathInfo *pathInfo;
      GSKVOInfo     *info;
      // 获取监听信息
      info = (GSKVOInfo *)[self observationInfo];
      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的值设置为NSKeyValueChangeOldKey
    • [pathInfo notifyForKey: aKey ofInstance: [info instance] prior: YES] 发通知

    查看GSKVOPathInfo类内notifyForKey: ofInstance:prior:方法的代码实现:

    - (void) notifyForKey: (NSString *)aKey ofInstance: (id)instance prior: (BOOL)f
    {
      unsigned      count;
      id            oldValue;
      id            newValue;
          
    if (f == YES)  {
          if ((allOptions & NSKeyValueObservingOptionPrior) == 0) {  return;  }
          [change setObject: [NSNumber numberWithBool: YES]
                     forKey: NSKeyValueChangeNotificationIsPriorKey];
     } else  {
          [change removeObjectForKey: NSKeyValueChangeNotificationIsPriorKey];
    }
    
      oldValue = [[change objectForKey: NSKeyValueChangeOldKey] retain];
      newValue = [[change objectForKey: NSKeyValueChangeNewKey] retain];
    
      /* 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 ((o->options & NSKeyValueObservingOptionPrior) == 0)  {  continue; }
    
          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集合 设置/获取 一些数据
    • [o->observer observeValueForKeyPath: aKey ofObject: instance change: change context: o->context];
      监听者响应监听方法。

    同样在didChangeValueForKey:方法内也调用了[pathInfo notifyForKey: aKey ofInstance: [info instance] prior: NO];方法

    到这里三部曲已经走完两部了,还剩下最后一步移除监听者:

    第三步,移除监听者

    - (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.
           * 关闭KVO
           */
          object_setClass(self, [self class]);
          IF_NO_GC(AUTORELEASE(info);)
          [self setObservationInfo: nil];
        }
      if ([aPath rangeOfString:@"."].location != NSNotFound)
        [forwarder finalize];
    }
    
    • [info removeObserver: anObserver forKeyPath: aPath]; info移除监听者
    • object_setClass(self, [self class]);动态子类的isa和原类的isa切换回来
    `GSKVOInfo `类内的移除方法:
    - (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
    {
      GSKVOPathInfo *pathInfo;
    
      [iLock lock];
      pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
      if (pathInfo != nil)
        {
          unsigned  count = [pathInfo->observations count];
    
          pathInfo->allOptions = 0;
          while (count-- > 0)
            {
              GSKVOObservation      *o;
    
              o = [pathInfo->observations objectAtIndex: count];
              if (o->observer == anObserver || o->observer == nil)
                {
                  [pathInfo->observations removeObjectAtIndex: count];
                  if ([pathInfo->observations count] == 0)
                    {
                      NSMapRemove(paths, (void*)aPath);
                    }
                }
              else
                {
                  pathInfo->allOptions |= o->options;
                }
        }
        }
      [iLock unlock];
    }
    
    • [pathInfo->observations removeObjectAtIndex: count];移除监听者
    • NSMapRemove(paths, (void*)aPath);移除keyPath

    相关文章

      网友评论

          本文标题:KVO底层探索

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