iOS 避免常見崩潰(二)

級別: ★★☆☆☆
標簽:「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。

    1. 系統KVO的簡單使用;
    1. FBKVOController的簡單使用;
    1. FBKVOController的類圖,思維導圖,使用流程圖;
    1. FBKVOController避免寫錯待觀察屬性;
    1. FBKVOController初始化過程;
    1. FBKVOController 觀察某對象的某屬性;
    1. FBKVOController 觀察對象屬性變化;
    1. FBKVOController不需要開發者removeObserver;
    1. FBKVOController的線程安全實現方式之互斥鎖;
    1. _FBKVOInfo重寫isEqual:、 hash;
    1. NSMapTable之keyOptions;
    1. NSHashTable之weakObjectsHashTable;

系統KVO的簡單使用

KVO的基礎使用可查看大成哥的iOS KVC與KVO簡介。

筆者下邊貼出的是一個系統方式觀察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的簡單使用

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中下載)

FBKVOClassDiagram.png
FBKVOMind.png
FBKVOFlowChart.png

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,下文會提到。

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

參考學習網址


推薦文章:
iOS 避免常見崩潰(一)
算法小專欄:選擇排序
iOS Runloop(一)
iOS 常用調試方法:LLDB命令
iOS 常用調試方法:斷點
iOS 常用調試方法:靜態分析
iOS消息轉發

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。