KVO 全稱是Key-Value Observing,即鍵值觀察者。是蘋果官方提供的一種事件通知機制。 鍵值觀察提供了一種機制,該機制容許將其餘對象的特定屬性的更改通知對象。對於應用程序中模型層和控制器層之間的通訊特別有用。控制器對象一般觀察模型對象的屬性,而視圖對象經過控制器觀察模型對象的屬性。可是,此外,模型對象能夠觀察其餘模型對象(一般用於肯定從屬值什麼時候更改),甚至能夠觀察自身(再次肯定從屬值什麼時候更改)。 您能夠觀察屬性,包括簡單屬性,一對一關係和一對多關係。一對多關係的觀察者被告知所作更改的類型,以及更改涉及哪些對象。 KVO最大的優點在於不須要修改其內部代碼便可實現監聽,可是有利有弊,最大的問題也是出自這裏。php
- 本文只說在自動觀察的狀況下的原理,KVO實際上有手動觀察的狀態,可是原理和自動觀察同樣,就再也不多說了。
通常狀況下,咱們使用KVO有如下三種步驟:html
- 經過
-(void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
方法註冊觀察者,觀察者能夠接收keyPath屬性的變化事件,而且使用context加入信息;
- 實現
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
方法,當keypath對應的元素髮生變化時,會發生回調;
- 若是再也不須要監聽,則須要使用
-(void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
方法來釋放掉。
這裏稍微提一下NSKeyValueObservingOptions的種類:git
NSKeyValueObservingOptionNew = 0x01, 提供更改前的值
NSKeyValueObservingOptionOld = 0x02, 提供更改後的值
NSKeyValueObservingOptionInitial = 0x04, 觀察最初的值(在註冊觀察服務時會調用一次觸發方法)
NSKeyValueObservingOptionPrior = 0x08 分別在值修改先後觸發方法(即一次修改有兩次觸發)
複製代碼
好比說,我建立了一個Fish類程序員
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Fish : NSObject
@property (nonatomic,strong)NSString *color;
@property (nonatomic,strong)NSString *price;
@end
NS_ASSUME_NONNULL_END
複製代碼
而後在viewController.m文件中,這樣添加觀察者github
self.saury = [[Fish alloc]init];
[self.saury setValue:@"blue" forKey:@"color"];
[self.saury addObserver:self forKeyPath:@"color" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:(__bridge void * _Nullable)([NSString stringWithFormat:@"yellow"])];
複製代碼
這裏我在context中加入了一個字符串,這也是KVO的一種傳值方式。 接着咱們實現監聽:安全
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if([keyPath isEqualToString:@"color"]) {
NSString *str = (__bridge NSString *)(context);
NSLog(@"___%@",str);
}
}
複製代碼
最後把它移除數據結構
-(void)dealloc {
//移除監聽
[self.saury removeObserver:self forKeyPath:@"price" context:(__bridge void * _Nullable)([NSString stringWithFormat:@"yellow"])];
}
複製代碼
看起來通常都是這麼使用的。多線程
好了,到這裏,就該吐槽一下KVO的不少坑爹的地方了。app
- 每次都必須在可靠準確的時間點手動移除觀察者;
- 傳遞上下文使用context時很是彆扭,由於這個是個void指針,須要神奇的橋接; 好比說我要傳遞一個字符串,添加觀察者的時候使用
(__bridge void * _Nullable)([NSString stringWithFormat:@"yellow"])
,而後在接收的時候,須要使用(__bridge NSString *)
來轉換過來。
- 若是有多個觀察者,在手動移除的時候須要鑑別context來分別移除;
- addObserver和removeObserver須要是成對的,若是remove多了就會發生crash,若是少remove了,就會在再次接收到回調的時候發生crash;
- 一旦被觀察的對象和屬性不少時,就要分門別類的用if方法來分辨,代碼寫的奇醜無比。
- KVO的實現是經過setter方法,使用KVO必須調用setter,直接訪問屬性對象是沒有用的。
固然,這個問題實際上很是廣泛並且持續時間很是久,久到GUN的時代就有了,吐槽的文章也是不少,好比這個。這麼多的缺點,也是各類KVO的封裝,好比說KVOController誕生的主要緣由。ide
在官方文檔中有這樣一句話。
Automatic key-value observing is implemented using a technique called isa-swizzling. The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data. When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance. You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance. 自動鍵值觀察是使用isa-swizzling實現的。 isa指針,顧名思義,指向對象的類,它保持一個調度表。該調度表實質上包含指向該類實現的方法的指針以及其餘數據。 在爲對象的屬性註冊觀察者時,將修改觀察對象的isa指針,指向中間類而不是真實類。結果,isa指針的值不必定反映實例的實際類。 您永遠不要依靠isa指針來肯定類成員。相反,您應該使用該class方法來肯定對象實例的類。
配合demo代碼,闡明瞭KVO的實現原理:
- 當某個類的屬性對象被觀察的時候,系統就會在運行期動態的建立一個派生類NSKVONotifying_xx。在這個派生類中重寫被觀察屬性的setter方法和Class方法,dealloc,_isKVO方法,而後這個isa指針指向了這個新建的類(注意!Class方法指向的仍是原有的類名)。派生類在被重寫的setter方法中實現了真正的通知機制,而和原有的對象隔離開來。
- KVO的實如今上層也依賴於 NSObject 的兩個方法:willChangeValueForKey:、didChangeValueForKey: 。在一個被觀察屬性改變以前,調用 willChangeValueForKey: 記錄舊的值。在屬性值改變以後調用 didChangeValueForKey:,從而 observeValueForKey:ofObject:change:context: 也會被調用。
固然,究竟是不是,看一下源碼不就知道了。
尷尬的是,在runtime的源碼當中,咱們是找不到有關kvo的東西的。那麼該怎麼辦呢? 這裏要先講一點歷史了。
早在1985 年,Steve Jobs 離開蘋果電腦(Apple) 後成立了NeXT 公司,並於1988 年推出了NeXT 電腦,使用NeXTStep 爲操做系統。這也是如今Cocoa裏面不少NS開頭的類名的源頭。在當時,NeXTStep 是至關先進的系統。 以Unix (BSD) 爲基礎,使用PostScript 提供高品質的圖形界面,並以Objective-C 語言提供完整的面向對象環境。 儘管NeXT 在軟件上的優異,其硬體銷售成績不佳,不久以後,NeXT 便轉型爲軟件公司。1994 年,NeXT 與Sun(Sun Microsystem) 合做推出OpenStep 界面,目標爲跨平臺的面向對象程式開發環境。NeXT 接着推出使用OpenStep 界面的OPENSTEP 系統,可在Mach, Microsoft Windows NT, Sun Solaris 及HP/UX 上執行。1996 年,蘋果電腦買下NeXT,作爲蘋果電腦下一代操做系統的基礎。 OPENSTEP 系統便演進成爲MacOS X 的Cocoa 環境。 在1995 年,自由軟體基金會(Free Software Fundation) 開始了GNUstep 計劃,目的在使用OpenStep 界面,以提供Linux/BSD 系統一個完整的程式發展環境,而GNUstep最初是GNU開發人員努力複製技術上雄心勃勃的NeXTSTEP的程序員友好功能。GNUstep是要早於Cocoa的實現的。咱們能夠從GNUstep的實現代碼中,來參考KVO的設計思路。 你能夠點擊這裏來找到GNUstep的源碼,或者也能夠直接查看我下載下來的文件,咱們能夠很驚奇的發現,至少在NSKeyValueObserving.h文件中,不少函數名是同樣的。
- 固然還有不少不一樣,好比說對於context的支持就少不少,remove方法就沒有支持context的函數。
這個方法在**NSObject (NSKeyValueObserverRegistration)**中。
- (void) addObserver: (NSObject*)anObserver
forKeyPath: (NSString*)aPath
options: (NSKeyValueObservingOptions)options
context: (void*)aContext {
GSKVOInfo *info;
GSKVOReplacement *r;
NSKeyValueObservationForwarder *forwarder;
NSRange dot;
//初始化
setup();
//使用遞歸鎖保證線程安全--kvoLock是一個NSRecursiveLock
[kvoLock lock];
// Use the original class
//從全局NSMapTable中獲取某個類的KVO子類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. */
//從全局NSMapTable中獲取某個類的觀察者信息對象,並經過改變它的類來改變接收器以開始觀察關鍵值
info = (GSKVOInfo*)[self observationInfo];
//若是沒有信息(不存在)就建立一個觀察者信息對象實例。
if (info == nil) {
info = [[GSKVOInfo alloc] initWithInstance: self];
//保存到全局NSMapTable中。
[self setObservationInfo: info];
//將被觀察的對象的isa修改成新的KVO子類Class
object_setClass(self, [r replacement]);
}
/* * Now add the observer. * 開始處理觀察者 */
dot = [aPath rangeOfString:@"."];
//string裏有沒有.
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 {
//根據key 找到對應的setter方法,而後根據類型去獲取GSKVOSetter類中相對應數據類型的setter方法
[r overrideSetterFor: aPath];
/* 這個是GSKVOInfo裏的方法 * 將keyPath 信息保存到GSKVOInfo中的paths中,方便之後直接從內存中取。 */
[info addObserver: anObserver
forKeyPath: aPath
options: options
context: aContext];
}
//遞歸鎖解鎖
[kvoLock unlock];
}
複製代碼
咱們接着來分段看。
NSString *const NSKeyValueChangeIndexesKey = @"indexes";
NSString *const NSKeyValueChangeKindKey = @"kind";
NSString *const NSKeyValueChangeNewKey = @"new";
NSString *const NSKeyValueChangeOldKey = @"old";
NSString *const NSKeyValueChangeNotificationIsPriorKey = @"notificationIsPrior";
static NSRecursiveLock *kvoLock = nil;
static NSMapTable *classTable = 0;//NSMapTable若是對key 和 value是弱引用,當key 和 value被釋放銷燬後,NSMapTable中對應的數據也會被清除。
static NSMapTable *infoTable = 0;
static NSMapTable *dependentKeyTable;
static Class baseClass;
static id null;
#pragma mark----- setup
static inline void setup() {
if (nil == kvoLock) {
//這是一個全局的遞歸鎖NSRecursiveLock
[gnustep_global_lock lock];
if (nil == kvoLock) {
kvoLock = [NSRecursiveLock new];
/* * NSCreateMapTable建立的是一個NSMapTable,一個弱引用key-value容器, */
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];
}
}
複製代碼
建立了classTable、infoTable、dependentKeyTable來存儲類名、觀察者的信息、依賴者對應的key。
爲了保證線程安全,這裏使用了遞歸鎖。 遞歸鎖的特色是:能夠容許同一線程屢次加鎖,而不會形成死鎖。**遞歸鎖會跟蹤它被lock的次數。每次成功的lock都必須平衡調用unlock操做。**只有全部達到這種平衡,鎖最後才能被釋放,以供其它線程使用。 這個很符合咱們對於KVO的理解。
static GSKVOReplacement *replacementForClass(Class c) {
GSKVOReplacement *r;
//建立
setup();
//遞歸鎖
[kvoLock lock];
//從全局classTable中獲取GSKVOReplacement實例
r = (GSKVOReplacement*)NSMapGet(classTable, (void*)c);
//若是沒有信息(不存在),就建立一個保存到全局classTable中
if (r == nil) {
r = [[GSKVOReplacement alloc] initWithClass: c];
NSMapInsert(classTable, (void*)c, (void*)r);
}
//遞歸鎖解鎖
[kvoLock unlock];
return r;
}
複製代碼
這裏咱們發現了 r = [[GSKVOReplacement alloc] initWithClass: c]; 方法,它是GSKVOReplacement裏的方法。它有三個成員變量。
{
Class original; /* The original class 原有類*/
Class replacement; /* The replacement class 替換類*/
NSMutableSet *keys; /* The observed setter keys 被觀察者的key*/
}
複製代碼
接着往下看。
- (id) initWithClass: (Class)aClass {
NSValue *template;
NSString *superName;
NSString *name;
...
original = 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);
//這個baseClass是GSKVOBase
GSObjCAddClassBehavior(replacement, baseClass);
/* * Create the set of setter methods overridden. * 建立重寫的setter方法集。 */
keys = [NSMutableSet new];
return self;
}
複製代碼
在 -(id)initWithClass:(Class)aClass 函數中,傳入的原始class便是original,而原有的類名,會在前面拼接一個 "GSKVO" 字符串以後變成替代類的類名。 而經過 GSObjCAddClassBehavior 方法,則會在將GSKVOBase的方法拷貝到replacement中去。 而GSKVOBase中有什麼方法呢?
- (void) dealloc;
- (Class) class;
- (Class) superclass;
- (void) setValue: (id)anObject forKey: (NSString*)aKey;
- (void) takeStoredValue: (id)anObject forKey: (NSString*)aKey;
- (void) takeValue: (id)anObject forKey: (NSString*)aKey;
- (void) takeValue: (id)anObject forKeyPath: (NSString*)aKey;
複製代碼
最關鍵的dealloc、class、superclass、setter方法都被重寫。 class、superclass方法都被加了一層class_getSuperclass,以免干擾,仍是能直接獲取到正確的class名。
這裏結束不談,回 - addObserver: forKeyPath: options: context: 。 接着咱們建立觀察者信息,並插入到infoTable中去。 而後經過object_setClass方法將修改class名稱,將被觀察的對象的isa修改成新的KVO子類Class。
這裏就頗有意思了,咱們須要查看,keyPath裏是否是有.
。 若是有.
,說明多是成員變量,咱們須要遞歸的向下篩選。 舉個🌰, 好比說,咱們要查看Computer
中的成員變量NoteBook
的屬性brand
。 你須要觀察的keyPath其實是NoteBook.brand。 那咱們要先觀察NoteBook的屬性變化,在往下觀察brand的變化。
keyForUpdate = [[keyPath substringToIndex: dot.location] copy];
remainingKeyPath = [keyPath substringFromIndex: dot.location + 1];
複製代碼
而若是沒有.的問題,咱們就能夠根據key,直接找到對應的setter方法,-(void)overrideSetterFor函數。而後根據類型去獲取GSKVOSetter類中相對應數據類型的setter方法。 好比以下代碼:
- (void) setter: (void *)val {
NSString *key;
Class c = [self class];//GSKVOSetter繼承的事NSObject,因此這裏獲取的仍是原有的父類,並未被改寫
void (*imp)(id,SEL,void*);
//獲取真正的函數地址--原始的setter方法
imp = (void (*)(id,SEL,void*))[c instanceMethodForSelector: _cmd];
key = newKey(_cmd);
if ([c automaticallyNotifiesObserversForKey: key] == YES) {
// pre setting code here
[self willChangeValueForKey: key];
(*imp)(self, _cmd, val);
// post setting code here
[self didChangeValueForKey: key];
} else {
(*imp)(self, _cmd, val);
}
RELEASE(key);
}
複製代碼
而後,咱們會發現,誒?怎麼又是一個添加觀察者? 這個其實是一個GSKVOInfo裏的函數。 在這裏建立、存儲KVO的信息,並處理一些細節問題:
在上面,我特意提過NSKeyValueObservingOptions的種類。 裏面有個NSKeyValueObservingOptionInitial屬性,當使用它的時候,須要在註冊觀察服務時會調用一次觸發方法。這個時候就能夠直接在判斷完以後調用 -observeValueForKeyPath:ofObject:change:context 方法。
這是一段很長的代碼
- (void) observeValueForKeyPath: (NSString *)keyPath
ofObject: (id)anObject
change: (NSDictionary *)change
context: (void *)context {
if (anObject == observedObjectForUpdate) {
[self keyPathChanged: nil];
} else {
[target observeValueForKeyPath: keyPathToForward
ofObject: observedObjectForUpdate
change: change
context: contextToForward];
}
}
- (void) keyPathChanged: (id)objectToObserve {
if (objectToObserve != nil) {
[observedObjectForUpdate removeObserver: self forKeyPath: keyForUpdate];
observedObjectForUpdate = objectToObserve;
[objectToObserve addObserver: self
forKeyPath: keyForUpdate
options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context: target];
}
if (child != nil) {
[child keyPathChanged:
[observedObjectForUpdate valueForKey: keyForUpdate]];
} else {
NSMutableDictionary *change;
change = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt: 1]forKey: NSKeyValueChangeKindKey];
if (observedObjectForForwarding != nil) {
id oldValue;
oldValue = [observedObjectForForwarding valueForKey: keyForForwarding];
[observedObjectForForwarding removeObserver: self
forKeyPath:keyForForwarding];
if (oldValue) {
[change setObject: oldValue
forKey: NSKeyValueChangeOldKey];
}
}
observedObjectForForwarding = [observedObjectForUpdate valueForKey:keyForUpdate];
if (observedObjectForForwarding != nil) {
id newValue;
[observedObjectForForwarding addObserver: self
forKeyPath: keyForForwarding
options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context: target];
//prepare change notification
newValue = [observedObjectForForwarding valueForKey: keyForForwarding];
if (newValue) {
[change setObject: newValue forKey: NSKeyValueChangeNewKey];
}
}
[target observeValueForKeyPath: keyPathToForward
ofObject: observedObjectForUpdate
change: change
context: contextToForward];
}
}
@end
複製代碼
咱們發現,無論怎樣都是要調用 - (void) keyPathChanged: ,因此能夠越過observeValueForKeyPath直接來看 - (void) keyPathChanged: 函數。
- (void) keyPathChanged: (id)objectToObserve {
if (objectToObserve != nil) {
[observedObjectForUpdate removeObserver: self forKeyPath: keyForUpdate];
observedObjectForUpdate = objectToObserve;
[objectToObserve addObserver: self
forKeyPath: keyForUpdate
options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context: target];
}
if (child != nil) {
[child keyPathChanged:[observedObjectForUpdate valueForKey: keyForUpdate]];
} else {
NSMutableDictionary *change;
change = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt: 1]
forKey:NSKeyValueChangeKindKey];
if (observedObjectForForwarding != nil) {
id oldValue;
oldValue = [observedObjectForForwarding valueForKey: keyForForwarding];
[observedObjectForForwarding removeObserver: self
forKeyPath:keyForForwarding];
if (oldValue) {
[change setObject: oldValue
forKey: NSKeyValueChangeOldKey];
}
}
observedObjectForForwarding = [observedObjectForUpdate valueForKey:keyForUpdate];
if (observedObjectForForwarding != nil) {
id newValue;
[observedObjectForForwarding addObserver: self
forKeyPath: keyForForwarding
options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context: target];
//prepare change notification
newValue = [observedObjectForForwarding valueForKey: keyForForwarding];
if (newValue) {
[change setObject: newValue
forKey: NSKeyValueChangeNewKey];
}
}
[target observeValueForKeyPath: keyPathToForward
ofObject: observedObjectForUpdate
change: change
context: contextToForward];
}
}
複製代碼
這段是個很長的代碼,做用的將須要的數據不斷的填充進應該的位置: 裏面四個主要的參數,實際上就是方法 **-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> )change context:(void )context 裏的數據。
這個方法則實現的簡單了一些,只有基礎方法,而沒有根據context刪除指定observer的方法,算是一個缺陷。
- (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];
}
複製代碼
這裏實際上就是添加觀察者的反過程,不過多的說明。
另外,由於並無 **- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString )keyPath context:(nullable void )context 方法的實現,我猜想了一下可能的實現。
- 對於infoTable可能設計的更加複雜,可使用context做爲key來添加和刪除相同的被觀察者的實例,即便是同一個被觀察者對象,也能夠經過context來建立不一樣的被觀察實例。
有個老哥本身根據反彙編寫了一個KVC、KVO的實現,代碼地址在這裏,在表現形式上已經和原生的KVO差很少了。不過做者使用的依然是Dictionary而非NSMapTable;鎖使用的是pthread_mutex_t互斥鎖以及OSSpinLockLock自旋鎖,而非NSRecursiveLock遞歸鎖。不過寫到這個已經很不錯了。
KVO在使用上有各類各樣的問題,有一種比較好的解決辦法就是使用Facebook的KVOController。 咱們就能夠寫成這樣。
[self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew action:@selector(updateClockWithDateChange:)];
複製代碼
而且帶來了不少好處:
- 再也不關心釋放的問題,其實是很是有效而且安全。
- 直接使用keypath來對應屬性,就再也不須要屢次的if判斷,即便是多個觀察者;
- 使用 block 來提高使用 KVO 的體驗;
它的實現其實蠻簡單的。刨除頭文件,主要有4個文件。
分別來看,NSObject+FBKVOController裏的 KVOControllerNonRetaining
這個元素並不會持有被觀察的對象,有效的防止循環引用;而KVOController
仍是會形成循環引用。 而它們的區別在於初始化傳入的retianObserved的不一樣。
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
{
self = [super init];
if (nil != self) {
_observer = observer;
NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
_objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
pthread_mutex_init(&_lock, NULL);
}
return self;
}
複製代碼
在這裏,生成持有者信息的時候會有個判斷,持有對象傳入的是 NSPointerFunctionsStrongMemory ,不止有對象的是 NSPointerFunctionsWeakMemory 。
主要的代碼都在FBKVOController.m中。
這裏,咱們能夠發現,這裏有一個NSMapTable類型的_objectInfosMap,和上面的相似的map起到了相似的做用--用來存儲當前對象持有者的相關信息。 而爲了線程安全,這裏使用了pthread_mutex_t
,一個互斥鎖。
- _objectInfosMap
- _lock
仍是從觀察開始看
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block {
......
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
......
[self _observe:object info:info];
}
複製代碼
這裏有個數據結構:_FBKVOInfo在上面也有相似的實現,用於存儲全部有關的信息。這裏就很少說了。 接着看關鍵的一個私有方法。
- (void)_observe:(id)object info:(_FBKVOInfo *)info {
// lock
pthread_mutex_lock(&_lock);
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// check for info existence
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// observation info already exists; do not observe it again
// unlock and return
pthread_mutex_unlock(&_lock);
return;
}
// lazilly create set of infos
if (nil == infos) {
infos = [NSMutableSet set];
[_objectInfosMap setObject:infos forKey:object];
}
// add info and oberve
[infos addObject:info];
// unlock prior to callout
pthread_mutex_unlock(&_lock);
[[_FBKVOSharedController sharedController] observe:object info:info];
}
複製代碼
這裏經過_objectInfosMap來判斷當年的對象信息是否已經註冊過。 而後處理一次InfosMap以後,會接着調用_FBKVOSharedController的單例方法。
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info {
if (nil == info) {
return;
}
pthread_mutex_lock(&_mutex);
[_infos addObject:info];
pthread_mutex_unlock(&_mutex);
[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
if (info->_state == _FBKVOInfoStateInitial) {
info->_state = _FBKVOInfoStateObserving;
} else if (info->_state == _FBKVOInfoStateNotObserving) {
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
}
複製代碼
而在整個流程中,只會有一個_FBKVOSharedController單例。 而這個方法纔會調用原生的KVO方法。
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSString *, id> *)change
context:(nullable void *)context {
_FBKVOInfo *info;
pthread_mutex_lock(&_mutex);
info = [_infos member:(__bridge id)context];
pthread_mutex_unlock(&_mutex);
FBKVOController *controller = info->_controller;
id observer = controller.observer;
if (info->_block) {
NSDictionary<NSString *, id> *changeWithKeyPath = change;
if (keyPath) {
NSMutableDictionary<NSString *, id> *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
[mChange addEntriesFromDictionary:change];
changeWithKeyPath = [mChange copy];
}
info->_block(observer, object, changeWithKeyPath);
} else if (info->_action) {
[observer performSelector:info->_action withObject:change withObject:object];
} else {
[observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
}
}
複製代碼
這裏咱們能夠發現,最後其實是經過_KVOInfo裏的context來判斷不一樣的KVO方法。
移除觀察者的策略比較簡單明瞭。
- (void)unobserve:(id)object infos:(nullable NSSet<_FBKVOInfo *> *)infos {
pthread_mutex_lock(&_mutex);
for (_FBKVOInfo *info in infos) {
[_infos removeObject:info];
}
pthread_mutex_unlock(&_mutex);
for (_FBKVOInfo *info in infos) {
if (info->_state == _FBKVOInfoStateObserving) {
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
info->_state = _FBKVOInfoStateNotObserving;
}
}
複製代碼
遍歷這裏的_FBKVOInfo,從其中取出 keyPath 並將 _KVOSharedController 移除觀察者。
KVOController實際上是用本身的方法,在原生KVO上又包了一層,用於自動處理,並不須要咱們來處理移除觀察者,大大下降了出錯的狀況。
- 能別用KVO就別用了,notification難道很差嗎?一樣是一對多,並且notification並不侷限於屬性的變化,各類各樣狀態的變化也均可以監聽。
- 實在要用直接用KVOController吧。
ps:看完KVO其實比較無趣,由於你會發現KVO其實有很多優秀的替代者,研究得出了不要用的結論確實有點沮喪,也顯得研究並無啥意義。 可是確實有趣啊,哈哈。
Key-Value Observing Programming Guide
Observers and Thread Safety