級別: ★★☆☆☆
標簽:「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
- NSHash?Table & NSMap?Table
- 使用互斥鎖
- iOS 多線程之線程安全
- iOS KVC與KVO簡介
推薦文章:
iOS 避免常見崩潰(一)
算法小專欄:選擇排序
iOS Runloop(一)
iOS 常用調試方法:LLDB命令
iOS 常用調試方法:斷點
iOS 常用調試方法:靜態分析
iOS消息轉發