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

就問此時此刻還有誰?45度仰望天空,該死!我這無處安放的魅力!git


RxSwift 目錄直通車 --- 和諧學習,不急不躁!github


KVO在咱們實際開發之中運用很是之多,不少開發者都知道原理!可是這些原理是如何來的,通常都是淺嘗輒止。這個篇章我會從 Swift 入手分析,探索KVO底層源碼.但願讓讀者真正掌握這一塊底層,知其然而知其因此然!面試

KVO簡介

首先咱們從KVO的三部曲開始swift

// 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")
複製代碼

其實咱們也知道,就是平時在開發的時候,咱們也能夠經過計算型屬性也能夠直接觀察app

var name: String = ""{
    willSet{
        print(newValue)
    }
    didSet{
        print(oldValue)
    }
}
複製代碼

問題來了:這二者有什麼關係?ide

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)
複製代碼

如今咱們開始探索底層源碼:post

- (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類型....我相信聰明的你同樣能夠解析讀懂!

RxSwift-KVO底層探索(下)

就問此時此刻還有誰?45度仰望天空,該死!我這無處安放的魅力!

相關文章
相關標籤/搜索