級別: ★★☆☆☆
標籤:「iOS 」「避免常見崩潰」「FBKVOController」「KVO」
作者: WYW
審校: QiShare團隊
前言 專案中可能會用到KVO。關於KVO的基礎使用可以檢視大成哥的iOS KVC與KVO簡介。系統提供的KVO的方式寫起來程式碼較分散,有時候會出問題。
facebook有個開源專案KVOController下文用FBKVOController代指。FBKVOController用起來比較簡單,可以在一定程度避免KVO的常見問題,本文筆者將通過分析FBKVOController,看看FBKVOController是如何避免KVO常見問題的。
使用KVO的常見問題有
- 新增觀察者的時候,寫錯待觀察的物件屬性名;
- 多次新增對某物件的屬性的觀察;
- 忘記移除觀察者,多次移除某觀察者;
- 移除觀察者的時候,觀察者已釋放;
FBKVOController封裝了系統的KVO,解決上邊提到的相應問題。下邊筆者簡單分析了FBKVOController是如何避免系統KVO相關問題的。筆者將會從如下幾個方面來展開分析FBKVOController。
- 系統KVO的簡單使用;
- FBKVOController的簡單使用;
- FBKVOController的類圖,思維導圖,使用流程圖;
- FBKVOController避免寫錯待觀察屬性;
- FBKVOController初始化過程;
- FBKVOController 觀察某物件的某屬性;
- FBKVOController 觀察物件屬性變化;
- FBKVOController不需要開發者removeObserver;
- FBKVOController的執行緒安全實現方式之互斥鎖;
- _FBKVOInfo重寫isEqual:、 hash;
- NSMapTable之keyOptions;
- NSHashTable之weakObjectsHashTable;
系統KVO的簡單使用
KVO的基礎使用可檢視大成哥的iOS KVC與KVO簡介。
筆者下邊貼出的是一個系統方式觀察Person的name屬性的程式碼。
- addObserver
- 在observeValueForKeyPath方法中檢視person的name屬性的變化情況;
- 最後在控制器的dealloc中需要記得removeObserver。
_person = [Person new];
[_person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
_person.name = [NSString stringWithFormat:@"personName:QiShare_%u", arc4random() % 1000];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"SystemKVOChange:%@", change);
}
- (void)dealloc {
[_person removeObserver:self forKeyPath:@"name"];
}
複製程式碼
FBKVOController的簡單使用
FBKVOController是一個適用於iOS和OS X的簡單的執行緒安全的KVO三方庫。
執行緒安全是通過互斥鎖方式保證的。
FBKVOController的簡單體現在有時我們只需寫一行程式碼即可。
其實FBKVOController也是對系統KVO的封裝。
討論FBKVOController簡單使用的過程中,筆者將以觀察Person類的name屬性為例。 相關程式碼如下:
#import "NSObject+FBKVOController.h"
[self.KVOController observe:_person keyPath:FBKVOKeyPath(_person.name) options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
NSLog(@"FBKVOChange:%@", change);
}];
複製程式碼
FBKVOController的類圖,思維導圖,使用流程圖
為了便於更容易理解FBKVOController,筆者繪製了FBKVOController的類圖,思維導圖,使用流程圖依次如下:
(思維導圖比較模糊,如有需要請到QiSafeType中下載)
FBKVOController避免寫錯待觀察屬性;
FBKVOController為了使用過程中,避免寫錯待觀察屬性,設定了2個巨集。
#define FBKVOKeyPath(KEYPATH) \
@(((void)(NO && ((void)KEYPATH, NO)), \
({ const char *fbkvokeypath = strchr(#KEYPATH, '.'); NSCAssert(fbkvokeypath, @"Provided key path is invalid."); fbkvokeypath + 1; })))
#define FBKVOClassKeyPath(CLASS, KEYPATH) \
@(((void)(NO && ((void)((CLASS *)(nil)).KEYPATH, NO)), #KEYPATH))
複製程式碼
筆者仍以觀察Person類的name屬性為例,檢視這2個巨集的使用方式;
這裡的巨集使用了逗號表示式。簡單說逗號表示式的結果就是最右邊的表示式的結果。如(3+5,6+8)的結果為14。
c語言提供一種特殊的運算子,逗號運算子,優先順序別最低,它將兩個及其以上的式子聯接起來,從左往右逐個計算表示式,整個表示式的值為最後一個表示式的值。如:(3+5,6+8)稱為逗號表示式,其求解過程先表示式1,後表示式2,整個表示式值是表示式2的值,如:(3+5,6+8)的值是14,a=(a=35,a4)的值是60,而(a=35,a4)的值是60, a的值在逗號表示式裡一直是15,最後被逗號表示式賦值為60,a的值最終為60。 摘自360百科
FBKVOKeyPath使用了斷言檢測待觀察物件的屬性:
斷言:當需要在一個值為FALSE時,中斷當前操作的話,可以使用斷言。斷言
#define NSCAssert(condition, desc, ...)
Assertions evaluate a condition and, if the condition evaluates to false, call the assertion handler for the current thread, passing it a format string and a variable number of arguments.
當condition為false的時候會終端當前操作,並且終端操作的原因會展示為desc。
複製程式碼
以_person.name為例,使用FBKVOKeyPath(_person.name),分析FBKVOKeyPath(KEYPATH)
@(((void)(NO && ((void)KEYPATH, NO)), \
({ const char *fbkvokeypath = strchr(#KEYPATH, '.'); NSCAssert(fbkvokeypath, @"Provided key path is invalid."); fbkvokeypath + 1; })))
((void)KEYPATH, NO) 是為了編譯_person.name;
編譯通過了之後const char *fbkvokeypath = strchr(#KEYPATH, '.');
#keypath巨集返回的是字串"_person.name";
fbkvokeypath是'.'在#KEYPATH即"_person.name"中的指標。
fbkvokeypath + 1返回的即"name"。
最後結合@,即為待觀察的Person的屬性@"name"
複製程式碼
以_person.name為例,使用FBKVOClassKeyPath(Person, name),分析FBKVOClassKeyPath(CLASS, KEYPATH);
#define FBKVOClassKeyPath(CLASS, KEYPATH) \
@(((void)(NO && ((void)((CLASS *)(nil)).KEYPATH, NO)), #KEYPATH))
((CLASS *)(nil)).KEYPATH 部分是為了編譯_person.name
編譯通過後,#KEYPATH返回的是"name",結合@。
即最後的待觀察物件@"name"
複製程式碼
FBKVOController初始化過程
初始化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 = observer,這裡的observer是weak修飾的,是為了避免出現Retain Cycle。
- 如果是strong修飾observer會出現 控制器 持有 FBKVOController,FBKVOController 持有 observer(控制器),出現Retain Cycle。
-
初始化了_objectInfosMap
- _objectInfosMap(
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
,用於儲存待觀察物件,待觀察物件為key
,及待觀察物件的屬性,及回撥相關資訊,待觀察物件的屬性及其他資訊為value
)。 - NSMapTable的keyOptions為NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality,可以做到,當key(此處為object)釋放的時候,_objectInfosMap中的相應的key value會自動移除。
- 關於NSMapTable的keyOptions,下文會提到。
- _objectInfosMap(
這裡FBKVOController例項,可以直接使用NSObject+FBKVOController.h
新增的屬性kvoController(即self.kvoController);
也可以使用如下全能初始化方法建立。
- (instancetype)initWithObserver:(nullable id)observer
retainObserved:(BOOL)retainObserved NS_DESIGNATED_INITIALIZER;
複製程式碼
- (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;
}
複製程式碼
注意:防止Retain Cycle
- 如果在當前類中,觀察當前類的屬性,傳入的retainObserved引數需要傳入NO。
retainObserved引數用於控制,建立FBKVOController例項的時候,_objectInfosMap對key持有弱引用還是強引用。
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
,key用於儲存待觀察者object,value用於儲存待觀察物件object的待觀察資訊。
在當前類中觀察當前類的屬性的示例:
[self.KVOControllerNonRetaining observe:self keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
NSLog(@"FBKVOChange:%@", change);
}];
複製程式碼
上例如果使用self.KVOController。將會出現
self(QiSafeKVOController) 持有 KVOController(FBKVOController)
KVOController(FBKVOController) 持有 _objectInfosMap
_objectInfosMap 持有 self(QiSafeKVOController)
的迴圈引用的問題。
FBKVOController 觀察某物件的某屬性;
FBKVOController觀察的物件,及觀察的物件的keyPath,option,block等資訊都儲存在了_FBKVOInfo例項中。 筆者以
- (void)observe:(nullable id)object
keyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
block:(FBKVONotificationBlock)block;
複製程式碼
分析觀察某物件某屬性的過程。
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block {
// 對keyPath block 及 待觀察物件object的簡單校驗
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;
}
// 建立儲存觀察者資訊的info(_FBKVOInfo例項) 儲存觀察者self keyPath options 及值改變的block
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
// 使用info觀察object
[self _observe:object info:info];
}
複製程式碼
- 在觀察物件屬性的時候,FBKVOController用到了_FBKVOInfo的例項,
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
用於儲存待觀察物件的屬性及回撥資訊。
- (void)_observe:(id)object info:(_FBKVOInfo *)info {
// 互斥鎖加鎖
pthread_mutex_lock(&_lock);
// 檢視_objectInfosMap中是否已經新增過object對應的資訊
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// 檢視infos中是否已經新增過info資訊 這裡的檢視方式是按照object的keypath的hash值確定的
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// 檢視與待觀察物件object相應的infos中,已經新增過info資訊,解鎖返回
pthread_mutex_unlock(&_lock);
return;
}
// _objectInfosMap之前沒有新增過對待觀察物件object的資訊,建立用於儲存object相應的infos資訊的內容
if (nil == infos) {
infos = [NSMutableSet set];
[_objectInfosMap setObject:infos forKey:object];
}
// 同樣會呼叫hash 新增info資訊到infos中
[infos addObject:info];
// 解鎖
pthread_mutex_unlock(&_lock);
[[_FBKVOSharedController sharedController] observe:object info:info];
}
複製程式碼
- 如下程式碼可以避免重複觀察某物件的某屬性。
// 檢視與待觀察物件object的屬性資訊 是否已經新增過info資訊
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// 檢視與待觀察物件object相應的infos中,已經新增過info資訊,解鎖返回
pthread_mutex_unlock(&_lock);
return;
}
複製程式碼
- _FBKVOShareController中的如下方法封裝了系統的KVO,及改變FBKVOInfo的
_FBKVOInfoStateInitial
狀態為_FBKVOInfoStateObserving
。
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info {
if (nil == info) {
return;
}
// 儲存待觀察物件的資訊 到_infos中
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的觀察狀態為 _FBKVOInfoStateObserving
info->_state = _FBKVOInfoStateObserving;
} else if (info->_state == _FBKVOInfoStateNotObserving) {
// 這部分內容筆者沒有復現
// this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions,
// and the observer is unregistered within the callback block.
// at this time the object has been registered as an observer (in Foundation KVO),
// so we can safely unobserve it.
// 系統方式移除觀察者
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
}
複製程式碼
FBKVOController 觀察物件屬性變化
在FBKVOController中有如下系統KVO的observeValueForKeyPath
方法及block回撥程式碼。
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
context:(nullable void *)context
{
NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);
_FBKVOInfo *info;
{
// lookup context in registered infos, taking out a strong reference only if it exists
pthread_mutex_lock(&_mutex);
info = [_infos member:(__bridge id)context];
pthread_mutex_unlock(&_mutex);
}
if (nil != info) {
// take strong reference to controller
FBKVOController *controller = info->_controller;
if (nil != controller) {
// take strong reference to observer
id observer = controller.observer;
if (nil != observer) {
// dispatch custom block or action, fall back to default action
if (info->_block) {
NSDictionary<NSKeyValueChangeKey, id> *changeWithKeyPath = change;
// add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed
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) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[observer performSelector:info->_action withObject:change withObject:object];
#pragma clang diagnostic pop
} else {
[observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
}
}
}
}
}
複製程式碼
以下程式碼實現了簡單校驗info->controller、觀察者、info->block,校驗無誤的情況下,進行block回撥,實現回撥到控制器中的block的部分。
if (nil != info) {
// take strong reference to controller
FBKVOController *controller = info->_controller;
if (nil != controller) {
// take strong reference to observer
id observer = controller.observer;
if (nil != observer) {
// dispatch custom block or action, fall back to default action
if (info->_block) {
NSDictionary<NSKeyValueChangeKey, id> *changeWithKeyPath = change;
// add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed
if (keyPath) {
NSMutableDictionary<NSString *, id> *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
[mChange addEntriesFromDictionary:change];
changeWithKeyPath = [mChange copy];
}
info->_block(observer, object, changeWithKeyPath);
}
複製程式碼
FBKVOController觀察物件
FBKVOController觀察的物件,及觀察的物件的keyPath,option,block等資訊都儲存在了FBKVOInfo例項中。
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block {
// 使用斷言 對keyPath block 的簡單校驗
NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
// 對keyPath block 及 待觀察物件object的簡單校驗
if (nil == object || 0 == keyPath.length || NULL == block) {
return;
}
// 建立儲存觀察者資訊的info(_FBKVOInfo例項) 儲存觀察者self keyPath options 及值改變的block
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
// 使用info觀察object
[self _observe:object info:info];
}
複製程式碼
- (void)_observe:(id)object info:(_FBKVOInfo *)info {
// 互斥鎖加鎖
pthread_mutex_lock(&_lock);
// 檢視_objectInfosMap中是否已經新增過object對應的資訊
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// 檢視與待觀察物件object相應的infos中 是否已經新增過info資訊
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// 檢視與待觀察物件object相應的infos中,已經新增過info資訊,解鎖返回
pthread_mutex_unlock(&_lock);
return;
}
// _objectInfosMap之前沒有新增過對待觀察物件object的資訊,建立用於儲存object相應的infos資訊的內容
if (nil == infos) {
infos = [NSMutableSet set];
[_objectInfosMap setObject:infos forKey:object];
}
// 同樣會呼叫hash 新增info資訊到infos中
[infos addObject:info];
// 解鎖
pthread_mutex_unlock(&_lock);
[[_FBKVOSharedController sharedController] observe:object info:info];
}
複製程式碼
封裝系統KVO,改變info->_state。
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info {
if (nil == info) {
return;
}
// 儲存待觀察物件的資訊 到_infos中
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的觀察狀態為 _FBKVOInfoStateObserving
info->_state = _FBKVOInfoStateObserving;
} else if (info->_state == _FBKVOInfoStateNotObserving) {
// 這部分內容筆者沒有復現
// this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions,
// and the observer is unregistered within the callback block.
// at this time the object has been registered as an observer (in Foundation KVO),
// so we can safely unobserve it.
// 系統方式移除觀察者
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
}
複製程式碼
儲存待觀察的object及FBKVOInfo資訊到_objectInfosMap,為了避免多次觀察某物件的同一屬性,在儲存操作前有個簡單的校驗.
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// 檢視與待觀察物件object相應的infos中,已經新增過info資訊,解鎖返回
pthread_mutex_unlock(&_lock);
return;
}
複製程式碼
FBKVOController不需要開發者removeObserver
在控制器銷燬的時候,FBKVOController的例項也會銷燬,FBKVOController在實現檔案中重寫了dealloc,依次移除之前objectsInfosMap中的需要移除觀察者的object的觀察者。
- (void)dealloc {
[self unobserveAll];
// 銷燬互斥鎖
pthread_mutex_destroy(&_lock);
}
複製程式碼
- (void)unobserveAll {
[self _unobserveAll];
}
複製程式碼
- (void)_unobserveAll {
// 互斥鎖加鎖
pthread_mutex_lock(&_lock);
// copy一份_objectInfosMap
NSMapTable *objectInfoMaps = [_objectInfosMap copy];
// 清空_objectInfosMap中的觀察者object及觀察的infos資訊
[_objectInfosMap removeAllObjects];
// 解鎖
pthread_mutex_unlock(&_lock);
// 獲取單例
_FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];
// 依次取消objectInfoMaps中observer的資訊
for (id object in objectInfoMaps) {
// 取消觀察每一個註冊了觀察的object及相應的觀察的資訊
NSSet *infos = [objectInfoMaps objectForKey:object];
[shareController unobserve:object infos:infos];
}
}
複製程式碼
- (void)unobserve:(id)object infos:(nullable NSSet<_FBKVOInfo *> *)infos {
// 如果沒有待移除的object相關的info資訊了, return
if (0 == infos.count) {
return;
}
// _infos移除infos中的info資訊
/**
_infos中存放的是觀察的所有object的info資訊
infos儲存的是當前的object的info資訊
info指的的infos中的每個info(_FBKVOInfo *)資訊
*/
pthread_mutex_lock(&_mutex);
for (_FBKVOInfo *info in infos) {
[_infos removeObject:info];
}
pthread_mutex_unlock(&_mutex);
// 移除info指定的keyPath及context資訊的觀察者 並且info的狀態為_FBKVOInfoStateNotObserving
for (_FBKVOInfo *info in infos) {
if (info->_state == _FBKVOInfoStateObserving) {
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
info->_state = _FBKVOInfoStateNotObserving;
}
}
複製程式碼
FBKVOController的執行緒安全實現方式之互斥鎖;
- FBKVOController的執行緒安全是通過互斥鎖實現的
mutext(MUTual EXclusion)是一種互斥設定。 用於保護共享資料免於併發訪問、設定出現問題。還有一些其他挑剔的情況。
在FBKVOController中,在操作_objectInfosMap,_infos的時候使用了互斥鎖。
就FBKVOController的例項變數_objectInfosMap而言。
在初始化FBKVOController的時候,初始化了互斥鎖;在讀寫objectInfosMap之前鎖定了互斥鎖;在讀寫完objectInfosMap之後,解鎖了互斥鎖;在FBKVOController銷燬的時候銷燬了互斥鎖。
// 使用互斥鎖需要匯入pthread.h
#import <pthread.h>
pthread_mutex_t _lock;
// 初始化互斥鎖
pthread_mutex_init(&_lock, NULL);
// 鎖定互斥鎖
pthread_mutex_lock(&_lock);
// 解鎖互斥鎖
pthread_mutex_unlock(&_lock);
// 銷燬互斥鎖
pthread_mutex_destroy(&_lock);
複製程式碼
// 初始化互斥鎖
__API_AVAILABLE(macos(10.4), ios(2.0))
int pthread_mutex_init(pthread_mutex_t * __restrict,
const pthread_mutexattr_t * _Nullable __restrict);
// 鎖定互斥鎖
__API_AVAILABLE(macos(10.4), ios(2.0))
int pthread_mutex_lock(pthread_mutex_t *);
// 解除鎖定互斥鎖
__API_AVAILABLE(macos(10.4), ios(2.0))
int pthread_mutex_unlock(pthread_mutex_t *);
// 銷燬互斥鎖
__API_AVAILABLE(macos(10.4), ios(2.0))
int pthread_mutex_destroy(pthread_mutex_t *);
複製程式碼
互斥鎖的使用可以檢視:使用互斥鎖
關於鎖的更多內容可以檢視大成哥的:iOS 多執行緒之執行緒安全
_FBKVOInfo重寫isEqual:、 hash
_FBKVOInfo重寫了isEqual: 和hash方法。
_FBKVOInfo重寫isEqual:和hash方法的原因是,FBKVOController想要自己去控制2個_FBKVOInfo的例項是否相等。這裡_FBKVOInfo是根據的 _keypath的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];
}
複製程式碼
If two objects are equal, they must have the same hash value. This last point is particularly important if you define isEqual: in a subclass and intend to put instances of that subclass into a collection. Make sure you also define hash in your subclass.
如果兩個物件是相等的,他們一定有相同的hash值。尤其重要的是,如果我們打算把子類例項放到一個集合物件中,並且在子類中重寫了isEqual方法的時候,請確保也在子類中重寫了hash方法。
NSMapTable之keyOptions;
FBKVOController使用了NSMapTable儲存要監聽的物件。
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
_objectInfosMap的key為要觀察的物件
_objectInfosMap的value儲存了FBKVOInfo的NSMutableSet資訊.
- _objectInfosMap 初始化。
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
{
self = [super init];
if (nil != self) {
_observer = observer;
// _objectInfosMap 初始化
NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
_objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
pthread_mutex_init(&_lock, NULL);
}
return self;
}
複製程式碼
A collection similar to a dictionary, but with a broader range of available memory semantics.
Declaration @interface NSMapTable<__covariant KeyType, __covariant ObjectType> : NSObject Discussion
The map table is modeled after NSDictionary with the following differences: Keys and/or values are optionally held “weakly” such that entries are removed when one of the objects is reclaimed.
Its keys or values may be copied on input or may use pointer identity for equality and hashing.
It can contain arbitrary pointers (its contents are not constrained to being objects).
NSMapTable 是一中類似於NSDictionary的集合,不過NSMapTable有更廣範的記憶體語義。 NSMapTable在NSDictionary的基礎上做了部分修整,NSMapTable相比較NSDictionary有如下不同的地方:
NSMapTable的keys或者values是可選地持有weak型別物件的。當NSMapTable中的weak類性的key或者value釋放的時候,相應的鍵值對會自動從NSMapTable中移除。
NSMapTable可以在新增鍵值對的時候進行拷貝操作,可以通過指標進行相等性和雜湊檢查。
NSMapTable可以包含任意指標(她的內容不限於物件)。
NSMapTable關於keyOptions相關程式碼:
// weakKeyStrongObjectsMapTable
NSMapTable *weakKeyStrongObjectsMapTable = [NSMapTable weakToStrongObjectsMapTable];
/** // 相當於
NSPointerFunctionsOptions weakOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPointerPersonality;
NSPointerFunctionsOptions strongOptions = NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality;
weakKeyStrongObjectsMapTable = [NSMapTable mapTableWithKeyOptions:weakOptions valueOptions:strongOptions];
*/
NSObject *key0 = [NSObject new];
NSObject *obj0 = [NSObject new];
[weakKeyStrongObjectsMapTable setObject:obj0 forKey:key0];
NSLog(@"weakKeyStrongObjectsMapTable:%@", weakKeyStrongObjectsMapTable);
/*
weakKeyStrongObjectsMapTable:NSMapTable {
[3] <NSObject: 0x600001711180> -> <NSObject: 0x600001711190>
}
*/
key0 = nil;
NSLog(@"key0 =nil, weakKeyStrongObjectsMapTable:%@", weakKeyStrongObjectsMapTable);
/*
key0 =nil, weakKeyStrongObjectsMapTable:NSMapTable {
}
*/
// weakKeyWeakObjsMapTable
NSObject *key1 = [NSObject new];
NSObject *obj1 = [NSObject new];
NSObject *key2 = [NSObject new];
NSObject *obj2 = [NSObject new];
NSMapTable *weakKeyWeakObjsMapTable = [NSMapTable weakToWeakObjectsMapTable];
// 相當於
// weakKeyWeakObjsMapTable = [NSMapTable mapTableWithKeyOptions:weakOptions valueOptions:weakOptions];
[weakKeyWeakObjsMapTable setObject:obj1 forKey:key1];
[weakKeyWeakObjsMapTable setObject:obj2 forKey:key2];
NSLog(@"weakKeyWeakObjsMapTable:%@", weakKeyWeakObjsMapTable);
/*
weakKeyWeakObjsMapTable:NSMapTable {
[3] <NSObject: 0x600001711180> -> <NSObject: 0x600001710fa0>
[10] <NSObject: 0x6000017111a0> -> <NSObject: 0x6000017110b0>
}
*/
key1 = nil;
NSLog(@"key1 = nil, weakKeyWeakObjsMapTable:%@", weakKeyWeakObjsMapTable);
/*
key1 = nil, weakKeyWeakObjsMapTable:NSMapTable {
[10] <NSObject: 0x6000017111a0> -> <NSObject: 0x6000017110b0>
}
*/
obj2 = nil;
[weakKeyWeakObjsMapTable setObject:obj1 forKey:key1];
NSLog(@"obj2 = nil, weakKeyWeakObjsMapTable:%@", weakKeyWeakObjsMapTable);
/*
obj2 = nil, weakKeyWeakObjsMapTable:NSMapTable {
}
*/
複製程式碼
以weakToStrongObjectsMapTable建立的NSMapTable的例項weakKeyStrongObjectsMapTable,當新增的key key1銷燬時,相應的key1 obj1的鍵值對會自動移除。
NSHashTable之weakObjectsHashTable
NSHashTable A collection similar to a set, but with broader range of available memory semantics.
The hash table is modeled after NSSet with the following differences:
It can hold weak references to its members.
Its members may be copied on input or may use pointer identity for equality and hashing.
It can contain arbitrary pointers (its members are not constrained to being objects).
NSHashTable NSHashTable是類似於NSSet的集合,不過NSHashTable有更加廣泛的記憶體語義。
NSHashTable是在NSSet的基礎上做的調整,相比較NSSet,NSHashTable有如下不同之處:
NSHashTable可以持有成員的弱引用。
NSHashTable可以在加入成員時執行copy操作,可以通過isEqual:和hash檢測成員的雜湊值和相等性。
NSHashTable可以存放任意的指標(NSHashTable的成員不限於物件)。
相關程式碼:
// NSHashTable
NSHashTable *hashTable = [NSHashTable weakObjectsHashTable];
// 相當於
[NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPointerPersonality];
NSObject *hashObj = [NSObject new];
[hashTable addObject:hashObj];
NSLog(@"hashTable:%@", hashTable);
/*
hashTable:NSHashTable {
[11] <NSObject: 0x600002528af0>
}
*/
hashObj = nil;
NSLog(@"hashObj = nil, hashTable:%@", hashTable);
/*
hashObj = nil, hashTable:NSHashTable {
}
*/
複製程式碼
對於weakObjectsHashTable建立的NSHashTable例項hashTable,當hashTable中新增的obj,銷燬後,hashTable中的之前新增的obj,會自動移除。
Demo
- 更多相關內容,可檢視Demo QiSafeType。
參考學習網址
- facebook/KVOController
- 逗號表示式
- 斷言
- ProcessOn
- Equality
- NSHashTable & NSMapTable
- 使用互斥鎖
- iOS 多執行緒之執行緒安全
- iOS KVC與KVO簡介
小編微信:可加並拉入《QiShare技術交流群》。
關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)
推薦文章:
iOS 避免常見崩潰(一)
演算法小專欄:選擇排序
iOS Runloop(一)
iOS 常用除錯方法:LLDB命令
iOS 常用除錯方法:斷點
iOS 常用除錯方法:靜態分析
iOS訊息轉發
奇舞週刊