級(jí)別: ★★☆☆☆
標(biāo)簽:「iOS 」「避免常見(jiàn)崩潰」「FBKVOController」「KVO」
作者: WYW
審校: QiShare團(tuán)隊(duì)
前言 項(xiàng)目中可能會(huì)用到KVO。關(guān)于KVO的基礎(chǔ)使用可以查看大成哥的iOS KVC與KVO簡(jiǎn)介。系統(tǒng)提供的KVO的方式寫(xiě)起來(lái)代碼較分散,有時(shí)候會(huì)出問(wèn)題。
facebook有個(gè)開(kāi)源項(xiàng)目KVOController下文用FBKVOController代指。FBKVOController用起來(lái)比較簡(jiǎn)單,可以在一定程度避免KVO的常見(jiàn)問(wèn)題,本文筆者將通過(guò)分析FBKVOController,看看FBKVOController是如何避免KVO常見(jiàn)問(wèn)題的。
使用KVO的常見(jiàn)問(wèn)題有
- 添加觀察者的時(shí)候,寫(xiě)錯(cuò)待觀察的對(duì)象屬性名;
- 多次添加對(duì)某對(duì)象的屬性的觀察;
- 忘記移除觀察者,多次移除某觀察者;
- 移除觀察者的時(shí)候,觀察者已釋放;
FBKVOController封裝了系統(tǒng)的KVO,解決上邊提到的相應(yīng)問(wèn)題。下邊筆者簡(jiǎn)單分析了FBKVOController是如何避免系統(tǒng)KVO相關(guān)問(wèn)題的。筆者將會(huì)從如下幾個(gè)方面來(lái)展開(kāi)分析FBKVOController。
- 系統(tǒng)KVO的簡(jiǎn)單使用;
- FBKVOController的簡(jiǎn)單使用;
- FBKVOController的類(lèi)圖,思維導(dǎo)圖,使用流程圖;
- FBKVOController避免寫(xiě)錯(cuò)待觀察屬性;
- FBKVOController初始化過(guò)程;
- FBKVOController 觀察某對(duì)象的某屬性;
- FBKVOController 觀察對(duì)象屬性變化;
- FBKVOController不需要開(kāi)發(fā)者removeObserver;
- FBKVOController的線(xiàn)程安全實(shí)現(xiàn)方式之互斥鎖;
- _FBKVOInfo重寫(xiě)isEqual:、 hash;
- NSMapTable之keyOptions;
- NSHashTable之weakObjectsHashTable;
系統(tǒng)KVO的簡(jiǎn)單使用
KVO的基礎(chǔ)使用可查看大成哥的iOS KVC與KVO簡(jiǎn)介。
筆者下邊貼出的是一個(gè)系統(tǒng)方式觀察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的簡(jiǎn)單使用
FBKVOController是一個(gè)適用于iOS和OS X的簡(jiǎn)單的線(xiàn)程安全的KVO三方庫(kù)。
線(xiàn)程安全是通過(guò)互斥鎖方式保證的。
FBKVOController的簡(jiǎn)單體現(xiàn)在有時(shí)我們只需寫(xiě)一行代碼即可。
其實(shí)FBKVOController也是對(duì)系統(tǒng)KVO的封裝。
討論FBKVOController簡(jiǎn)單使用的過(guò)程中,筆者將以觀察Person類(lèi)的name屬性為例。
相關(guān)代碼如下:
#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的類(lèi)圖,思維導(dǎo)圖,使用流程圖
為了便于更容易理解FBKVOController,筆者繪制了FBKVOController的類(lèi)圖,思維導(dǎo)圖,使用流程圖依次如下:
(思維導(dǎo)圖比較模糊,如有需要請(qǐng)到QiSafeType中下載)
FBKVOController避免寫(xiě)錯(cuò)待觀察屬性;
FBKVOController為了使用過(guò)程中,避免寫(xiě)錯(cuò)待觀察屬性,設(shè)置了2個(gè)宏。
#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類(lèi)的name屬性為例,查看這2個(gè)宏的使用方式;
這里的宏使用了逗號(hào)表達(dá)式。簡(jiǎn)單說(shuō)逗號(hào)表達(dá)式的結(jié)果就是最右邊的表達(dá)式的結(jié)果。如(3+5,6+8)的結(jié)果為14。
c語(yǔ)言提供一種特殊的運(yùn)算符,逗號(hào)運(yùn)算符,優(yōu)先級(jí)別最低,它將兩個(gè)及其以上的式子聯(lián)接起來(lái),從左往右逐個(gè)計(jì)算表達(dá)式,整個(gè)表達(dá)式的值為最后一個(gè)表達(dá)式的值。如:(3+5,6+8)稱(chēng)為逗號(hào)表達(dá)式,其求解過(guò)程先表達(dá)式1,后表達(dá)式2,整個(gè)表達(dá)式值是表達(dá)式2的值,如:(3+5,6+8)的值是14,a=(a=35,a4)的值是60,而(a=35,a4)的值是60, a的值在逗號(hào)表達(dá)式里一直是15,最后被逗號(hào)表達(dá)式賦值為60,a的值最終為60。 摘自360百科
FBKVOKeyPath使用了斷言檢測(cè)待觀察對(duì)象的屬性:
斷言:當(dāng)需要在一個(gè)值為FALSE時(shí),中斷當(dāng)前操作的話(huà),可以使用斷言。斷言
#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.
當(dāng)condition為false的時(shí)候會(huì)終端當(dāng)前操作,并且終端操作的原因會(huì)展示為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;
編譯通過(guò)了之后const char *fbkvokeypath = strchr(#KEYPATH, '.');
#keypath宏返回的是字符串"_person.name";
fbkvokeypath是'.'在#KEYPATH即"_person.name"中的指針。
fbkvokeypath + 1返回的即"name"。
最后結(jié)合@,即為待觀察的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
編譯通過(guò)后,#KEYPATH返回的是"name",結(jié)合@。
即最后的待觀察對(duì)象@"name"
FBKVOController初始化過(guò)程
初始化FBKVOController的過(guò)程。
- (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修飾的,是為了避免出現(xiàn)Retain Cycle。
- 如果是strong修飾observer會(huì)出現(xiàn) 控制器 持有 FBKVOController,F(xiàn)BKVOController 持有 observer(控制器),出現(xiàn)Retain Cycle。
-
初始化了_objectInfosMap
- _objectInfosMap(
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
,用于存儲(chǔ)待觀察對(duì)象,待觀察對(duì)象為key
,及待觀察對(duì)象的屬性,及回調(diào)相關(guān)信息,待觀察對(duì)象的屬性及其他信息為value
)。 - NSMapTable的keyOptions為NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality,可以做到,當(dāng)key(此處為object)釋放的時(shí)候,_objectInfosMap中的相應(yīng)的key value會(huì)自動(dòng)移除。
- 關(guān)于NSMapTable的keyOptions,下文會(huì)提到。
- _objectInfosMap(
這里FBKVOController實(shí)例,可以直接使用NSObject+FBKVOController.h
添加的屬性kvoController(即self.kvoController);
也可以使用如下全能初始化方法創(chuàng)建。
- (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
- 如果在當(dāng)前類(lèi)中,觀察當(dāng)前類(lèi)的屬性,傳入的retainObserved參數(shù)需要傳入NO。
retainObserved參數(shù)用于控制,創(chuàng)建FBKVOController實(shí)例的時(shí)候,_objectInfosMap對(duì)key持有弱引用還是強(qiáng)引用。
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
,key用于存儲(chǔ)待觀察者object,value用于存儲(chǔ)待觀察對(duì)象object的待觀察信息。
在當(dāng)前類(lèi)中觀察當(dāng)前類(lèi)的屬性的示例:
[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。將會(huì)出現(xiàn)
self(QiSafeKVOController) 持有 KVOController(FBKVOController)
KVOController(FBKVOController) 持有 _objectInfosMap
_objectInfosMap 持有 self(QiSafeKVOController)
的循環(huán)引用的問(wèn)題。
FBKVOController 觀察某對(duì)象的某屬性;
FBKVOController觀察的對(duì)象,及觀察的對(duì)象的keyPath,option,block等信息都存儲(chǔ)在了_FBKVOInfo實(shí)例中。
筆者以
- (void)observe:(nullable id)object
keyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
block:(FBKVONotificationBlock)block;
分析觀察某對(duì)象某屬性的過(guò)程。
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block {
// 對(duì)keyPath block 及 待觀察對(duì)象object的簡(jiǎn)單校驗(yàn)
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;
}
// 創(chuàng)建存儲(chǔ)觀察者信息的info(_FBKVOInfo實(shí)例) 存儲(chǔ)觀察者self keyPath options 及值改變的block
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
// 使用info觀察object
[self _observe:object info:info];
}
- 在觀察對(duì)象屬性的時(shí)候,F(xiàn)BKVOController用到了_FBKVOInfo的實(shí)例,
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
用于存儲(chǔ)待觀察對(duì)象的屬性及回調(diào)信息。
- (void)_observe:(id)object info:(_FBKVOInfo *)info {
// 互斥鎖加鎖
pthread_mutex_lock(&_lock);
// 查看_objectInfosMap中是否已經(jīng)添加過(guò)object對(duì)應(yīng)的信息
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// 查看infos中是否已經(jīng)添加過(guò)info信息 這里的查看方式是按照object的keypath的hash值確定的
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// 查看與待觀察對(duì)象object相應(yīng)的infos中,已經(jīng)添加過(guò)info信息,解鎖返回
pthread_mutex_unlock(&_lock);
return;
}
// _objectInfosMap之前沒(méi)有添加過(guò)對(duì)待觀察對(duì)象object的信息,創(chuàng)建用于存儲(chǔ)object相應(yīng)的infos信息的內(nèi)容
if (nil == infos) {
infos = [NSMutableSet set];
[_objectInfosMap setObject:infos forKey:object];
}
// 同樣會(huì)調(diào)用hash 添加info信息到infos中
[infos addObject:info];
// 解鎖
pthread_mutex_unlock(&_lock);
[[_FBKVOSharedController sharedController] observe:object info:info];
}
- 如下代碼可以避免重復(fù)觀察某對(duì)象的某屬性。
// 查看與待觀察對(duì)象object的屬性信息 是否已經(jīng)添加過(guò)info信息
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// 查看與待觀察對(duì)象object相應(yīng)的infos中,已經(jīng)添加過(guò)info信息,解鎖返回
pthread_mutex_unlock(&_lock);
return;
}
- _FBKVOShareController中的如下方法封裝了系統(tǒng)的KVO,及改變FBKVOInfo的
_FBKVOInfoStateInitial
狀態(tài)為_FBKVOInfoStateObserving
。
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info {
if (nil == info) {
return;
}
// 存儲(chǔ)待觀察對(duì)象的信息 到_infos中
pthread_mutex_lock(&_mutex);
[_infos addObject:info];
pthread_mutex_unlock(&_mutex);
// 系統(tǒng)的方式添加觀察者
[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
if (info->_state == _FBKVOInfoStateInitial) {
// 改變要觀察的對(duì)象的info的觀察狀態(tài)為 _FBKVOInfoStateObserving
info->_state = _FBKVOInfoStateObserving;
} else if (info->_state == _FBKVOInfoStateNotObserving) {
// 這部分內(nèi)容筆者沒(méi)有復(fù)現(xiàn)
// 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.
// 系統(tǒng)方式移除觀察者
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
}
FBKVOController 觀察對(duì)象屬性變化
在FBKVOController中有如下系統(tǒng)KVO的observeValueForKeyPath
方法及block回調(diào)代碼。
- (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];
}
}
}
}
}
以下代碼實(shí)現(xiàn)了簡(jiǎn)單校驗(yàn)info->controller、觀察者、info->block,校驗(yàn)無(wú)誤的情況下,進(jìn)行block回調(diào),實(shí)現(xiàn)回調(diào)到控制器中的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觀察對(duì)象
FBKVOController觀察的對(duì)象,及觀察的對(duì)象的keyPath,option,block等信息都存儲(chǔ)在了FBKVOInfo實(shí)例中。
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block {
// 使用斷言 對(duì)keyPath block 的簡(jiǎn)單校驗(yàn)
NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
// 對(duì)keyPath block 及 待觀察對(duì)象object的簡(jiǎn)單校驗(yàn)
if (nil == object || 0 == keyPath.length || NULL == block) {
return;
}
// 創(chuàng)建存儲(chǔ)觀察者信息的info(_FBKVOInfo實(shí)例) 存儲(chǔ)觀察者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中是否已經(jīng)添加過(guò)object對(duì)應(yīng)的信息
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// 查看與待觀察對(duì)象object相應(yīng)的infos中 是否已經(jīng)添加過(guò)info信息
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// 查看與待觀察對(duì)象object相應(yīng)的infos中,已經(jīng)添加過(guò)info信息,解鎖返回
pthread_mutex_unlock(&_lock);
return;
}
// _objectInfosMap之前沒(méi)有添加過(guò)對(duì)待觀察對(duì)象object的信息,創(chuàng)建用于存儲(chǔ)object相應(yīng)的infos信息的內(nèi)容
if (nil == infos) {
infos = [NSMutableSet set];
[_objectInfosMap setObject:infos forKey:object];
}
// 同樣會(huì)調(diào)用hash 添加info信息到infos中
[infos addObject:info];
// 解鎖
pthread_mutex_unlock(&_lock);
[[_FBKVOSharedController sharedController] observe:object info:info];
}
封裝系統(tǒng)KVO,改變info->_state。
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info {
if (nil == info) {
return;
}
// 存儲(chǔ)待觀察對(duì)象的信息 到_infos中
pthread_mutex_lock(&_mutex);
[_infos addObject:info];
pthread_mutex_unlock(&_mutex);
// **系統(tǒng)的方式添加觀察者**
[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
if (info->_state == _FBKVOInfoStateInitial) {
// 改變要觀察的對(duì)象的info的觀察狀態(tài)為 _FBKVOInfoStateObserving
info->_state = _FBKVOInfoStateObserving;
} else if (info->_state == _FBKVOInfoStateNotObserving) {
// 這部分內(nèi)容筆者沒(méi)有復(fù)現(xiàn)
// 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.
// 系統(tǒng)方式移除觀察者
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
}
存儲(chǔ)待觀察的object及FBKVOInfo信息到_objectInfosMap,為了避免多次觀察某對(duì)象的同一屬性,在存儲(chǔ)操作前有個(gè)簡(jiǎn)單的校驗(yàn).
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// 查看與待觀察對(duì)象object相應(yīng)的infos中,已經(jīng)添加過(guò)info信息,解鎖返回
pthread_mutex_unlock(&_lock);
return;
}
FBKVOController不需要開(kāi)發(fā)者removeObserver
在控制器銷(xiāo)毀的時(shí)候,F(xiàn)BKVOController的實(shí)例也會(huì)銷(xiāo)毀,F(xiàn)BKVOController在實(shí)現(xiàn)文件中重寫(xiě)了dealloc,依次移除之前objectsInfosMap中的需要移除觀察者的object的觀察者。
- (void)dealloc {
[self unobserveAll];
// 銷(xiāo)毀互斥鎖
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) {
// 取消觀察每一個(gè)注冊(cè)了觀察的object及相應(yīng)的觀察的信息
NSSet *infos = [objectInfoMaps objectForKey:object];
[shareController unobserve:object infos:infos];
}
}
- (void)unobserve:(id)object infos:(nullable NSSet<_FBKVOInfo *> *)infos {
// 如果沒(méi)有待移除的object相關(guān)的info信息了, return
if (0 == infos.count) {
return;
}
// _infos移除infos中的info信息
/**
_infos中存放的是觀察的所有object的info信息
infos存儲(chǔ)的是當(dāng)前的object的info信息
info指的的infos中的每個(gè)info(_FBKVOInfo *)信息
*/
pthread_mutex_lock(&_mutex);
for (_FBKVOInfo *info in infos) {
[_infos removeObject:info];
}
pthread_mutex_unlock(&_mutex);
// 移除info指定的keyPath及context信息的觀察者 并且info的狀態(tài)為_(kāi)FBKVOInfoStateNotObserving
for (_FBKVOInfo *info in infos) {
if (info->_state == _FBKVOInfoStateObserving) {
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
info->_state = _FBKVOInfoStateNotObserving;
}
}
FBKVOController的線(xiàn)程安全實(shí)現(xiàn)方式之互斥鎖;
- FBKVOController的線(xiàn)程安全是通過(guò)互斥鎖實(shí)現(xiàn)的
mutext(MUTual EXclusion)是一種互斥設(shè)置。 用于保護(hù)共享數(shù)據(jù)免于并發(fā)訪(fǎng)問(wèn)、設(shè)置出現(xiàn)問(wèn)題。還有一些其他挑剔的情況。
在FBKVOController中,在操作_objectInfosMap,_infos的時(shí)候使用了互斥鎖。
就FBKVOController的實(shí)例變量_objectInfosMap而言。
在初始化FBKVOController的時(shí)候,初始化了互斥鎖;在讀寫(xiě)objectInfosMap之前鎖定了互斥鎖;在讀寫(xiě)完objectInfosMap之后,解鎖了互斥鎖;在FBKVOController銷(xiāo)毀的時(shí)候銷(xiāo)毀了互斥鎖。
// 使用互斥鎖需要導(dǎo)入pthread.h
#import <pthread.h>
pthread_mutex_t _lock;
// 初始化互斥鎖
pthread_mutex_init(&_lock, NULL);
// 鎖定互斥鎖
pthread_mutex_lock(&_lock);
// 解鎖互斥鎖
pthread_mutex_unlock(&_lock);
// 銷(xiāo)毀互斥鎖
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 *);
// 銷(xiāo)毀互斥鎖
__API_AVAILABLE(macos(10.4), ios(2.0))
int pthread_mutex_destroy(pthread_mutex_t *);
互斥鎖的使用可以查看:使用互斥鎖
關(guān)于鎖的更多內(nèi)容可以查看大成哥的:iOS 多線(xiàn)程之線(xiàn)程安全
_FBKVOInfo重寫(xiě)isEqual:、 hash
_FBKVOInfo重寫(xiě)了isEqual: 和hash方法。
_FBKVOInfo重寫(xiě)isEqual:和hash方法的原因是,F(xiàn)BKVOController想要自己去控制2個(gè)_FBKVOInfo的實(shí)例是否相等。這里_FBKVOInfo是根據(jù)的 _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.
如果兩個(gè)對(duì)象是相等的,他們一定有相同的hash值。尤其重要的是,如果我們打算把子類(lèi)實(shí)例放到一個(gè)集合對(duì)象中,并且在子類(lèi)中重寫(xiě)了isEqual方法的時(shí)候,請(qǐng)確保也在子類(lèi)中重寫(xiě)了hash方法。
NSMapTable之keyOptions;
FBKVOController使用了NSMapTable存儲(chǔ)要監(jiān)聽(tīng)的對(duì)象。
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
_objectInfosMap的key為要觀察的對(duì)象
_objectInfosMap的value存儲(chǔ)了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 是一中類(lèi)似于NSDictionary的集合,不過(guò)NSMapTable有更廣范的內(nèi)存語(yǔ)義。
NSMapTable在NSDictionary的基礎(chǔ)上做了部分修整,NSMapTable相比較NSDictionary有如下不同的地方:
NSMapTable的keys或者values是可選地持有weak類(lèi)型對(duì)象的。當(dāng)NSMapTable中的weak類(lèi)性的key或者value釋放的時(shí)候,相應(yīng)的鍵值對(duì)會(huì)自動(dòng)從NSMapTable中移除。
NSMapTable可以在添加鍵值對(duì)的時(shí)候進(jìn)行拷貝操作,可以通過(guò)指針進(jìn)行相等性和散列檢查。
NSMapTable可以包含任意指針(她的內(nèi)容不限于對(duì)象)。
NSMapTable關(guān)于keyOptions相關(guān)代碼:
// weakKeyStrongObjectsMapTable
NSMapTable *weakKeyStrongObjectsMapTable = [NSMapTable weakToStrongObjectsMapTable];
/** // 相當(dāng)于
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];
// 相當(dāng)于
// 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創(chuàng)建的NSMapTable的實(shí)例weakKeyStrongObjectsMapTable,當(dāng)添加的key key1銷(xiāo)毀時(shí),相應(yīng)的key1 obj1的鍵值對(duì)會(huì)自動(dòng)移除。
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是類(lèi)似于NSSet的集合,不過(guò)NSHashTable有更加廣泛的內(nèi)存語(yǔ)義。NSHashTable是在NSSet的基礎(chǔ)上做的調(diào)整,相比較NSSet,NSHashTable有如下不同之處:
NSHashTable可以持有成員的弱引用。
NSHashTable可以在加入成員時(shí)執(zhí)行copy操作,可以通過(guò)isEqual:和hash檢測(cè)成員的散列值和相等性。
NSHashTable可以存放任意的指針(NSHashTable的成員不限于對(duì)象)。
相關(guān)代碼:
// NSHashTable
NSHashTable *hashTable = [NSHashTable weakObjectsHashTable];
// 相當(dāng)于
[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 {
}
*/
對(duì)于weakObjectsHashTable創(chuàng)建的NSHashTable實(shí)例hashTable,當(dāng)hashTable中添加的obj,銷(xiāo)毀后,hashTable中的之前添加的obj,會(huì)自動(dòng)移除。
Demo
- 更多相關(guān)內(nèi)容,可查看Demo QiSafeType。
參考學(xué)習(xí)網(wǎng)址
- facebook/KVOController
- 逗號(hào)表達(dá)式
- 斷言
- ProcessOn
- Equality
- NSHash?Table & NSMap?Table
- 使用互斥鎖
- iOS 多線(xiàn)程之線(xiàn)程安全
- iOS KVC與KVO簡(jiǎn)介
推薦文章:
iOS 避免常見(jiàn)崩潰(一)
算法小專(zhuān)欄:選擇排序
iOS Runloop(一)
iOS 常用調(diào)試方法:LLDB命令
iOS 常用調(diào)試方法:斷點(diǎn)
iOS 常用調(diào)試方法:靜態(tài)分析
iOS消息轉(zhuǎn)發(fā)