iOS 避免常見(jiàn)崩潰(二)

級(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。

    1. 系統(tǒng)KVO的簡(jiǎn)單使用;
    1. FBKVOController的簡(jiǎn)單使用;
    1. FBKVOController的類(lèi)圖,思維導(dǎo)圖,使用流程圖;
    1. FBKVOController避免寫(xiě)錯(cuò)待觀察屬性;
    1. FBKVOController初始化過(guò)程;
    1. FBKVOController 觀察某對(duì)象的某屬性;
    1. FBKVOController 觀察對(duì)象屬性變化;
    1. FBKVOController不需要開(kāi)發(fā)者removeObserver;
    1. FBKVOController的線(xiàn)程安全實(shí)現(xiàn)方式之互斥鎖;
    1. _FBKVOInfo重寫(xiě)isEqual:、 hash;
    1. NSMapTable之keyOptions;
    1. NSHashTable之weakObjectsHashTable;

系統(tǒng)KVO的簡(jiǎn)單使用

KVO的基礎(chǔ)使用可查看大成哥的iOS KVC與KVO簡(jiǎn)介

筆者下邊貼出的是一個(gè)系統(tǒng)方式觀察Person的name屬性的代碼。

    1. addObserver
    1. 在observeValueForKeyPath方法中查看person的name屬性的變化情況;
    1. 最后在控制器的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中下載)

FBKVOClassDiagram.png
FBKVOMind.png
FBKVOFlowChart.png

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ì)提到。

這里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)址


推薦文章:
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ā)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,310評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,951評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,796評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,566評(píng)論 6 407
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,055評(píng)論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,303評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,799評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,683評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,899評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,135評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,520評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,757評(píng)論 1 282
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,528評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,844評(píng)論 2 372