回顧
在前面的幾篇部落格中,已經介紹了
KVO
的基本使用,如何自定義
KVO
,那麼本篇部落格将分析一下
FBKVOController
這個優秀的
KVO
三方庫。

iOS底層探索之KVO(一)—KVO簡介
iOS底層探索之KVO(二)—KVO原理分析
iOS底層探索之KVO(三)—自定義KVO
iOS底層探索之KVO(四)—自定義KVO
FBKVOController
是一個函數式程式設計實作,不用移除觀察者者。
1. FBKVOController簡單介紹
FBKVOController
是
Facebook
開源的一個基于系統
KVO
實作的架構。支援
Objective-C
和
Swift
語言。
GitHub位址
鍵值觀察是一種特别有用的技術,用于在模型-視圖-控制器應用程式中的層之間進行通信。建立在
KVOController
久經考驗的鍵值觀察實作之上。它提供了一個簡單、現代的
Cocoa
,這也是
API
的。
線程安全
優點如下:
KVOController
1.1 KVOController優點
- 使用blocks、自定義操作或
回調通知。NSKeyValueObserving
- 不需要額外的移除觀察者
- 在控制器 dealloc 的時候隐式的把觀察者移除。
- 具有防止觀察者複活的特殊線程安全的保護機制
- 有關 KVO 的更多資訊,請參閱 Apple 的鍵值觀察簡介。
1.2 FBKVOController 使用
FBKVOController
的使用起來非常的簡單,代碼很少,
FBKVOController
簡單使用如下代碼所示:
-
使用FBKVOController
self.person = [[JPPerson alloc] init];
self.person.name = @"RENO";
self.person.age = 18;
self.person.mArray = [NSMutableArray arrayWithObject:@"1"];
[self.kvoCtrl observe:self.person keyPath:@"age" options:0 action:@selector(jp_observerAge)];
[self.kvoCtrl observe:self.person keyPath:@"name" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(@"****%@****",change);
}];
[self.kvoCtrl observe:self.person keyPath:@"mArray" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(@"****%@****",change);
}];
代碼非常的簡潔,不用向我們之前使用系統的
KVO
那樣在
dealloc
裡面移除觀察者,這一波使用就很爽啊!
- 懶加載初始化
#pragma mark - lazy
- (FBKVOController *)kvoCtrl{
if (!_kvoCtrl) {
_kvoCtrl = [FBKVOController controllerWithObserver:self];
}
return _kvoCtrl;
}
2. KVOController 實作分析
2.1 中介者模式
我們平時買房、租房都會找中介,通過中介可以更快更高效的找到合适的房子,也就很多事情中介幫我們去做了,不用我們自己去找房源。主要是使用了中介者模式,官方
KVOController
使用麻煩的點在于使用需要三部曲。
kvo
核心就是将三部曲進行了底層封裝,上層隻需要關心業務邏輯。
KVOController
會進行注冊、移除以及回調的處理(回調包括
FBKVOController
、
block
以及相容系統的
action
回調)。是對外暴露的互動類。使用
observe
分為兩步:
FBKVOController
- 使用
初始化controllerWithObserver
執行個體。FBKVOController
- 使用
進行注冊。observe:
2.2 FBKVOController 初始化
- (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;
}
_observer
是觀察者,是
FBKVOController
的屬性,用 weak來修飾
因為本身被觀察者持有了,是以是
FBKVOController
類型的修飾。
weak
_objectInfosMap
根據
retainObserved
進行
NSMapTable
記憶體管理/初始化配置,
FBKVOController
的成員變量。其中儲存的是一個被觀察者對應多個
_FBKVOInfo
(也就是被觀察對象對應多個
keyPath
):
_FBKVOInfo
是放在
NSMutableSet
中的,說明是去重的。
2.3 FBKVOController 注冊
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
{
NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
if (nil == object || 0 == keyPath.length || NULL == block) {
return;
}
// create info
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
// observe object with info
[self _observe:object info:info];
}
- 首先第一步就是做一些判斷,容錯判斷。
- 構造
,儲存_FBKVOInfo
、keyPath、FBKVOController
以及options
。block
- 調用
_observe:(id)object info:(_FBKVOInfo *)info
- _FBKVOInfo
@implementation _FBKVOInfo
{
@public
__weak FBKVOController *_controller;
NSString *_keyPath;
NSKeyValueObservingOptions _options;
SEL _action;
void *_context;
FBKVONotificationBlock _block;
_FBKVOInfoState _state;
}
在
_FBKVOInfo
中儲存了一些相關的資料資訊
- 重寫
與isEqual
方法hash
- (NSUInteger)hash
{
return [_keyPath hash];
}
- (BOOL)isEqual:(id)object
{
if (nil == object) {
return NO;
}
if (self == object) {
return YES;
}
if (![object isKindOfClass:[self class]]) {
return NO;
}
return [_keyPath isEqualToString:((_FBKVOInfo *)object)->_keyPath];
}
隻要 _keyPath
相同就認為是同一對象
- _observe: info:
- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
// lock
pthread_mutex_lock(&_lock);
//從TableMap中擷取 object(被觀察者) 對應的 set
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// check for info existence
//判斷對應的keypath info 是否存在
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
//存在直接傳回,這裡就相當于對于同一個觀察者排除了相同的keypath
// observation info already exists; do not observe it again
// unlock and return
pthread_mutex_unlock(&_lock);
return;
}
// lazilly create set of infos
//TableMap資料為空進行建立設定
if (nil == infos) {
infos = [NSMutableSet set];
//<被觀察者 - keypaths info>
[_objectInfosMap setObject:infos forKey:object];
}
// add info and oberve
//keypaths info添加 keypath info
[infos addObject:info];
// unlock prior to callout
pthread_mutex_unlock(&_lock);
//注冊
[[_FBKVOSharedController sharedController] observe:object info:info];
}
- 首先判斷
是否已經被注冊了,注冊了直接傳回,這裡也就是進行了去重的處理,這一波操作就非常細節。kayPath
- 将構造的
資訊添加進_FBKVOInfo
中。_objectInfosMap
- 調用
進行真正的注冊。_FBKVOSharedController
-
說明member:
會調用到member
以及_FBKVOInfo中的hash
進行判斷對象是否存在,也就是判斷isEqual
對應的對象是否存在。keyPath
這裡注冊是使用了單例
[[_FBKVOSharedController sharedController] observe:object info:info]
為什麼這裡使用呢?而不是在外面的調用初始化的時候使用單例呢?
單例
這方法裡面使用單例,下次再次使用就不會重複建立了,就是相當于保活了,我們在中使用的是
VC
的執行個體對象,會随着
FBKVOController
的銷毀而銷毀,這個單例觀察者會在内部移除,移除不是銷毀的意思,隻是告訴這個單例,移除對某個對象的觀察,例如觀察了
VC
的屬性,最後的
self.person
是移除對
dealloc
的觀察的意思。這一波操作,又是非常的細節,厲害了!
self.person
這裡的
object
參數傳入的是什麼呢?是
self
嗎?
不是
self
是
self.person
,why ?小朋友,你現在是否有很多問号?
在我們的印象中,使用添加觀察者傳入的都是
KVO
啊!但是靓仔,我們這裡不是哦!
self
我們的
VC
需要的是
block
的回調,添加觀察者是觀察
self.person
的屬性變化,是以傳入
self.person
就好了。你内部怎麼操作,我
VC
不管,你隻要把改變之後結果告訴我就好了,丢個
block
的回調通知我
VC
就
OK
了!
如圖這裡的
self
是指前面的那個
單例
,就是為了複用,就是:隻要添加屬性的觀察都是使用這個單例,這裡通過
keyPath
來區分,觀察的是不同的屬性。
2. 4 KVOController銷毀
KVOController
的銷毀,其實是内部幫我們實作了,是以不用我們手動去銷毀。
- dealloc
iOS底層探索之KVO(五)—FBKVOController分析 - unobserveAll
- (void)unobserveAll
{
[self _unobserveAll];
}
- _unobserveAll
iOS底層探索之KVO(五)—FBKVOController分析 - _unobserve:(id)object info:
- (void)_unobserve:(id)object info:(_FBKVOInfo *)info
{
// lock
pthread_mutex_lock(&_lock);
// get observation infos
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// lookup registered info instance
_FBKVOInfo *registeredInfo = [infos member:info];
if (nil != registeredInfo) {
[infos removeObject:registeredInfo];
// remove no longer used infos
if (0 == infos.count) {
[_objectInfosMap removeObjectForKey:object];
}
}
// unlock
pthread_mutex_unlock(&_lock);
// unobserve
[[_FBKVOSharedController sharedController] unobserve:object info:registeredInfo];
}
單例去調用内部的移除觀察者方法
圖中紅色框起來的代碼,其實就是調用的系統的移除觀察者的方法
removeObserver: forKeyPath :
由于的執行個體是VC持有的,是以我們的 VC被
FBKVOController
銷毀的時候
dealloc
執行個體也就
FBKVOController
了。在這裡調用就相當于在 VC 中
dealloc
中調用了移除是一樣的。
dealloc
單例的生命周期是随着程式的生命周期的,不會銷毀,隻是傳入的觀察的對象在内部做了銷毀,也就是移除操作,就是說 老鐵 我不需要觀察了,後續别給我發送消息了。
3. 通過gnustep探索KVO
kvo
與
kvc
是屬于
Foundation
架構裡面的,由于
Foundation
相關的代碼蘋果并沒有開源,對于它們的探索可以通過gnustep檢視原理,
gnustep
中有一些蘋果早期底層的實作。
那麼
FBKVOController
分析就介紹到這了。
通過【
gnustep
】具體的探索,這裡就不過多的描述了,感興趣的老鐵,可以自行去下載下傳
Foundation
的源碼,看看裡面的實作,思路都是差不多的!
4. 總結
-
使用了FBKVOController
模式,通過中介者
的思想,把對屬性的變化的觀察,使用函數式程式設計
通知回調block
-
注冊,内部使用了單例,進行複用,通過FBKVOController
來區分,觀察的是不同的屬性。keyPath
- 在控制器
的時候隐式的把觀察者移除,其實内部還是調用了系統的移除方法。dealloc
更多内容持續更新
🌹 喜歡就點個贊吧👍🌹
🌹 覺得有收獲的,可以來一波,收藏+關注,評論 + 轉發,以免你下次找不到我😁🌹
🌹歡迎大家留言交流,批評指正,互相學習😁,提升自我🌹