iOS 中的 KVO 底層實現

KVO是Key-Value-Observer的縮寫,使用的是觀察者模式。底層實現機制都是isa-swizzing,就是在底層調用object_setClass函數,將對象的isa指向的Class偷偷換掉。php

而觀察者模式就是 目標對象(被觀察的對象)管理全部依賴於它的觀察者對象,並在它自身的狀態改變時主動通知觀察者對象。而主動通知觀察者對象這個實現通常都是調用觀察者對象提供的接口。這樣就能夠將目標對象和觀察者對象鬆散偶合。安全

iOS 中的實現就更簡單了,利用respondsToSelector來判斷觀察者是否實現了指定的方法,就能夠通知觀察者對象了。bash

KVO的實現依賴於runtime,它須要動態獲取到class,也須要動態的修改class,還須要動態判斷是否實現了某些方法等。框架

原理:當第一次觀察某個類的實例對象時,會動態建立一個該類的子類,而後將該對象的isa修改成這個新的子類的Class,重寫被觀察的屬性的 set方法,而後在修改屬性先後,調用觀察者的接口來通知觀察者。ide

1.GNUstep中的KVO實現

GNUstep是Objective-C中大部分實現的前身,雖然OC在GNUstep的基礎上作了許多更新和優化,可是不少基本邏輯思路是一致的。而KVO的源碼又沒有開源,因此咱們就只能先從GNUstep的實現中來參考一二了。函數

[GNUstep Core Base](wwwmain.gnustep.org/resources/d… 中有Foundation框架的實現。雖然可能與OC的實現不太同樣,可是整體思路是同樣的。優化

咱們在下載的開源工程中的【base/Headers/Foundation/NSKeyValueObserving.h】中能夠看到KVO相關的頭文件。ui

這個NSKeyValueObserving.h中暴露的API與Objective-C中Foudation中NSKeyValueObserving.h中的API基本上是一致的。atom

都是爲NSObjet增長了幾個Category,分別放了KVO要實現的鍵值觀察方法和添加觀察者、移除觀察者等API方法。spa

咱們能夠在【base/Source/Foundation/KVO】目錄下找到NSKeyValueObserving.m

1.1 - addObserver: forKeyPath: options: context: 的實現

先來看一下源碼,因爲是GNUstep的開源框架,因此部分類型仍是GS前綴,爲了便於理解,我已添加一些註釋。

- (void) addObserver: (NSObject*)anObserver
          forKeyPath: (NSString*)aPath
             options: (NSKeyValueObservingOptions)options
             context: (void*)aContext
{
    GSKVOInfo             *info;
    GSKVOReplacement      *r;
    NSKeyValueObservationForwarder *forwarder;
    NSRange               dot;
    
    // 1.初始化一些全局變量
    setup();
    // 2.使用遞歸鎖保證線程安全
    [kvoLock lock];
    // 3.從全局NSMapTable中獲取某個類的KVO子類Class
    r = replacementForClass([self class]);
    
    // 4.從全局NSMapTable中獲取某個類的觀察者信息對象
    info = (GSKVOInfo*)[self observationInfo];
    
    // 5.若是不存在就建立一個觀察者信息對象實例。
    if (info == nil)
    {
        info = [[GSKVOInfo alloc] initWithInstance: self];
        // 5.1 保存到全局NSMapTable中。
        [self setObservationInfo: info];
        // 5.2 將被觀察的對象的isa修改成新的KVO子類Class
        object_setClass(self, [r replacement]);
    }
    
    // 6.調用info實例方法處理觀察
    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];
    }
    
    // 7.遞歸鎖解鎖
    [kvoLock unlock];
}
複製代碼

setup()

setup()函數中主要是對一些全局變量的初始化,固然了內部也加了遞歸鎖,以及全局變量是否爲空的判斷。

// 全局遞歸鎖
static NSRecursiveLock	*kvoLock = nil;
static NSMapTable	 *classTable = 0;
static NSMapTable	 *infoTable = 0;
static NSMapTable       *dependentKeyTable;
static Class		baseClass;
static id               null;

// 這個是在GSLock中定義的
NSRecursiveLock *gnustep_global_lock = nil;

static inline void
setup()
{
  if (nil == kvoLock)
    {
      [gnustep_global_lock lock];
      if (nil == kvoLock)
	{
	  kvoLock = [NSRecursiveLock new];
	  null = [[NSNull null] retain];
	  classTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
	    NSNonOwnedPointerMapValueCallBacks, 128);
	  infoTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
	    NSNonOwnedPointerMapValueCallBacks, 1024);
	  dependentKeyTable = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
	      NSOwnedPointerMapValueCallBacks, 128);
	  baseClass = NSClassFromString(@"GSKVOBase");
	}
      [gnustep_global_lock unlock];
    }
}
複製代碼

以上源代碼基本上都在NSKeyValueObserving.m的頂部。

NSMapTable 是iOS 是iOS 6 新增的容器類,功能相似於NSDictionary。通常的字典,會持有key 和value,致使對象的引用計數增長。可是NSMapTable能夠分別設置key 和value的持有狀況,若是對key 和 value是弱引用,當key 和 value被釋放銷燬後,NSMapTable中對應的數據也會被清除。

replacementForClass()

這是一個全局靜態函數,做用是從全局classTable中獲取已經建立的某個類的KVO子類。

static GSKVOReplacement *
replacementForClass(Class c)
{
  GSKVOReplacement *r;
  // 0.方式全局變量沒有初始化
  setup();
  // 1.使用遞歸鎖鎖住
  [kvoLock lock];
  // 2.從全局classTable中獲取GSKVOReplacement實例
  r = (GSKVOReplacement*)NSMapGet(classTable, (void*)c);
  // 3.若是不存在,就建立一個保存到全局classTable中
  if (r == nil)
    {
      r = [[GSKVOReplacement alloc] initWithClass: c];
      NSMapInsert(classTable, (void*)c, (void*)r);
    }
    // 4.釋放遞歸鎖
  [kvoLock unlock];
  return r;
}
複製代碼

GSKVOReplacement中其實主要存儲的是原始的Class以及對象被更新後的Class和被觀察的keys。

@interface	GSKVOReplacement : NSObject
{
  Class         original;       /* The original class */
  Class         replacement;    /* The replacement class */
  NSMutableSet  *keys;          /* The observed setter keys */
}
- (id) initWithClass: (Class)aClass;
- (void) overrideSetterFor: (NSString*)aKey;
- (Class) replacement;
@end
複製代碼

GSKVOInfo

全局infoTable中存儲的就是該類型的實例對象。

@interface	GSKVOInfo : NSObject
{
  NSObject	        *instance;	// Not retained.
  NSRecursiveLock	        *iLock;
  NSMapTable	        *paths;
}
- (GSKVOPathInfo *) lockReturningPathInfoForKey: (NSString *)key;
- (void*) contextForObserver: (NSObject*)anObserver ofKeyPath: (NSString*)aPath;
- (id) initWithInstance: (NSObject*)i;
- (NSObject*) instance;
- (BOOL) isUnobserved;
- (void) unlock;

@end
複製代碼

-observationInfo和 -setObservationInfo:

這兩個函數主要是從全局infoTable中存取對象而已,比較簡單就不作贅述了。

- (void*) observationInfo
{
  void	*info;

  setup();
  [kvoLock lock];
  info = NSMapGet(infoTable, (void*)self);
  IF_NO_GC(AUTORELEASE(RETAIN((id)info));)
  [kvoLock unlock];
  return info;
}

- (void) setObservationInfo: (void*)observationInfo
{
  setup();
  [kvoLock lock];
  if (observationInfo == 0)
    {
      NSMapRemove(infoTable, (void*)self);
    }
  else
    {
      NSMapInsert(infoTable, (void*)self, observationInfo);
    }
  [kvoLock unlock];
}
複製代碼

object_setClass(self, [r replacement])

這裏的[r replacement]其實僅僅是獲取到GSKVOReplacement內的replacement成員變量的值而已。而生成replacement的過程在init函數中。

- (id) initWithClass: (Class)aClass
{
    original = aClass;
    /*
     * Create subclass of the original, and override some methods
     * with implementations from our abstract base class.
     */
    NSString *superName = NSStringFromClass(original);
    NSString *name = [@"GSKVO" stringByAppendingString: superName];
    // 這裏利用runtime動態建立一個集成自original的GSKVOxxx類
    NSValue *template = GSObjCMakeClass(name, superName, nil);
    // 將新的GSKVOXXX類,註冊到系統中
    GSObjCAddClasses([NSArray arrayWithObject: template]);
    replacement = NSClassFromString(name);
    // 將baseClass(GSKVOBase)中的API添加到replacement上。
    GSObjCAddClassBehavior(replacement, baseClass);
    keys = [NSMutableSet new];
    return self;
}
複製代碼

這裏比較重要的實際上是GSObjCAddClassBehavior(replacement, baseClass),由於GSKVOBase中一共也沒幾個API,主要是實現了以下幾個API:

- (void) dealloc
{
  // Turn off KVO for self ... then call the real dealloc implementation.
  [self setObservationInfo: nil];
  object_setClass(self, [self class]);
  [self dealloc];
  GSNOSUPERDEALLOC;
}

- (Class) class
{
  return class_getSuperclass(object_getClass(self));
}

- (void) setValue: (id)anObject forKey: (NSString*)aKey
{
  Class		c = [self class];
  void		(*imp)(id,SEL,id,id);

  imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];

  if ([[self class] automaticallyNotifiesObserversForKey: aKey])
    {
      [self willChangeValueForKey: aKey];
      imp(self,_cmd,anObject,aKey);
      [self didChangeValueForKey: aKey];
    }
  else
    {
      imp(self,_cmd,anObject,aKey);
    }
}

- (void) takeStoredValue: (id)anObject forKey: (NSString*)aKey
{
  Class		c = [self class];
  void		(*imp)(id,SEL,id,id);

  imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];

  if ([[self class] automaticallyNotifiesObserversForKey: aKey])
    {
      [self willChangeValueForKey: aKey];
      imp(self,_cmd,anObject,aKey);
      [self didChangeValueForKey: aKey];
    }
  else
    {
      imp(self,_cmd,anObject,aKey);
    }
}

- (void) takeValue: (id)anObject forKey: (NSString*)aKey
{
  Class		c = [self class];
  void		(*imp)(id,SEL,id,id);

  imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];

  if ([[self class] automaticallyNotifiesObserversForKey: aKey])
    {
      [self willChangeValueForKey: aKey];
      imp(self,_cmd,anObject,aKey);
      [self didChangeValueForKey: aKey];
    }
  else
    {
      imp(self,_cmd,anObject,aKey);
    }
}

- (void) takeValue: (id)anObject forKeyPath: (NSString*)aKey
{
  Class		c = [self class];
  void		(*imp)(id,SEL,id,id);

  imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];

  if ([[self class] automaticallyNotifiesObserversForKey: aKey])
    {
      [self willChangeValueForKey: aKey];
      imp(self,_cmd,anObject,aKey);
      [self didChangeValueForKey: aKey];
    }
  else
    {
      imp(self,_cmd,anObject,aKey);
    }
}

- (Class) superclass
{
  return class_getSuperclass(class_getSuperclass(object_getClass(self)));
}
複製代碼

這幾個函數的實現都很簡單,主要做用就是爲了讓開發者感知不到GSKVOxxx類的存在,由於當開發者在使用這些函數時,取到的仍是original類的信息。

接下來,分兩種狀況:

  • 1.若是要觀察的就是對象的屬性,則只須要重寫set方法便可。
  • 2.若是要觀察的是成員變量的屬性,則須要構造一個NSKeyValueObservationForwarder對象,再調用GSKVOInfo中的- addObserver: forKeyPath: options: context:函數。

狀況1

GSKVOReplacement中的overrideSetterFor實現,也就是拼接出setXxx:或者_setXxx:,而後獲取到SEL,最後將GSKVOSetter中針對各類類型的setter imp 賦值給sel。

關於各類類型的屬性的set方法的實現,已經集中在GSKVOSetter中實現了。另外,賦值使用的是以下API:class_addMethod(replacement, sel, imp, [sig methodType])

最後,調用GSKVOInfo中的- addObserver: forKeyPath: options: context:函數。調用該API目的有兩個:

  • 1.將keyPath 信息保存到GSKVOInfo中的paths中,方便之後直接從內存中取。
  • 2.若是kvo設置的options中包含initial值,須要將初始化的值返回給觀察者。

狀況2

這種狀況的實現,其實都在以下函數中:

- (id) initWithKeyPath: (NSString *)keyPath
              ofObject: (id)object
            withTarget: (id)aTarget
               context: (void *)context
{
  NSString * remainingKeyPath;
  NSRange dot;

  target = aTarget;
  keyPathToForward = [keyPath copy];
  contextToForward = context;

  dot = [keyPath rangeOfString: @"."];
  if (dot.location == NSNotFound)
    {
      [NSException raise: NSInvalidArgumentException
        format: @"NSKeyValueObservationForwarder was not given a key path"];
    }
  keyForUpdate = [[keyPath substringToIndex: dot.location] copy];
  remainingKeyPath = [keyPath substringFromIndex: dot.location + 1];
  observedObjectForUpdate = object;
  [object addObserver: self
           forKeyPath: keyForUpdate
              options: NSKeyValueObservingOptionNew
                     | NSKeyValueObservingOptionOld
              context: target];
  dot = [remainingKeyPath rangeOfString: @"."];
  if (dot.location != NSNotFound)
    {
      child = [[NSKeyValueObservationForwarder alloc]
        initWithKeyPath: remainingKeyPath
	       ofObject: [object valueForKey: keyForUpdate]
	     withTarget: self
		context: NULL];
      observedObjectForForwarding = nil;
    }
  else
    {
      keyForForwarding = [remainingKeyPath copy];
      observedObjectForForwarding = [object valueForKey: keyForUpdate];
      [observedObjectForForwarding addObserver: self
                                    forKeyPath: keyForForwarding
                                       options: NSKeyValueObservingOptionNew
                                              | NSKeyValueObservingOptionOld
                                       context: target];
      child = nil;
    }

  return self;
}
複製代碼

舉個例子,若是咱們要觀察的Parent中的成員變量child的height屬性,keyPath實際上是child.height。 那上面該方法作的事情,實際上是先建立一個KVO監聽的是其屬性child的變動,而後再執行child的KVO,監聽child對象的成員變量height的變動。

這裏child的觀察者是NSKeyValueObservationForwarder對象,而後在內部的- observeValueForKeyPath:ofObject:change:context:中調用上一級的對象的- observeValueForKeyPath:ofObject:change:context:,這樣就能夠將要監聽的屬性的變動事件一級一級的傳出去。

2.蘋果中的KVO實現

我這裏建立了一個HLPerson類:

@interface HLPerson : NSObject

@property (nonatomic, assign) int height;

@end
複製代碼

而後在viewController的viewDidLoad中初始化該person:

self.person = [[HLPerson alloc] init];
    NSLog(@"Class:%@", object_getClass(self.person));
    NSLog(@"person.class:%@", self.person.class);
    [self.person addObserver:self forKeyPath:@"height" options:NSKeyValueObservingOptionNew context:nil];
    NSLog(@"Class:%@", object_getClass(self.person));
    NSLog(@"person.class:%@", self.person.class);
    
// 輸出結果爲:
Class:HLPerson
person.class:HLPerson
Class:NSKVONotifying_HLPerson
person.class:HLPerson
複製代碼

因而可知,蘋果中的實現是構造出了一個NSKVONotifying_HLPerson,雖然跟GNUstep中的前綴不太同樣,可是實現邏輯應該是差很少的。

3.總結

雖然結論是猜想的,可是可信度應該是很是高的。KVO的實現原理,也就是對象執行- addObserver: forKeyPath: options: context:時,內部實現以下:

  • 1.若是KVO須要的全局變量未初始化,先初始化這些全局變量。
  • 2.從全局classTable中獲取已轉換過的GSKVOReplacement對象,若是不存在,則建立一個保存到classTable中。
  • 3.從全局infoTable中獲取觀察者信息GSKVOInfo對象,若是不存在,則建立一個,並保存到全局infoTable中,並將GSKVOReplacement中動態建立的class,賦值給對象。動態建立的Class會被重寫setValue:forKey等函數。在真正執行賦值操做先後插入willChange 和 didChange 方法。
  • 4.重寫對象的set方法,也是在執行賦值操做先後插入willChange 和 didChange 方法。
  • 5.調用GSKVOInfo中的- observeValueForKeyPath:ofObject:change:context:
  • 6.當對象的屬性真的被修改時,就能夠在willChange 和 didChange中調用 - observeValueForKeyPath:ofObject:change:context: 告知觀察對象了。
相關文章
相關標籤/搜索