KVO底层探索

问题

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 {
    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
    static func automaticallyNotifiesObservers(for key: AnyKeyPath) -> Bool
}

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

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

    public func willChangeValue(for keyPath: __owned KeyPath) {
        (self as! NSObject).willChangeValue(forKey: _bridgeKeyPathToString(keyPath))
    }
    
    public func willChange(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: __owned KeyPath) {
        (self as! NSObject).willChange(changeKind, valuesAt: indexes, forKey: _bridgeKeyPathToString(keyPath))
    }
    
    public func willChangeValue(for keyPath: __owned KeyPath, withSetMutation mutation: NSKeyValueSetMutationKind, using set: Set) -> Void {
        (self as! NSObject).willChangeValue(forKey: _bridgeKeyPathToString(keyPath), withSetMutation: mutation, using: set)
    }
    
    public func didChangeValue(for keyPath: __owned KeyPath) {
        (self as! NSObject).didChangeValue(forKey: _bridgeKeyPathToString(keyPath))
    }
    
    public func didChange(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: __owned KeyPath) {
        (self as! NSObject).didChange(changeKind, valuesAt: indexes, forKey: _bridgeKeyPathToString(keyPath))
    }
    
    public func didChangeValue(for keyPath: __owned KeyPath, withSetMutation mutation: NSKeyValueSetMutationKind, using set: Set) -> 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 {
        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底层探索)