不常見集合NSHashTable和NSMapTable

NSArray,NSSet,NSDictionary是平時常用的數(shù)據(jù)類型,今天想說的是另外兩個比較高階的集合NSHashTableNSMapTable肮疗。

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是一種類似NSSet一樣的集合渴逻。但是它具有更廣泛的可用內(nèi)存語義桥氏。能夠對持有對象以弱引用的方式存儲效拭。大家都知道平時用的NSArrayNSSet都是對對應的強持有(強引用),結果就是在某些場合達不到理想效果。

那么NSHashTable有哪些使用場景呢浑度?

不知道同學們有沒有遇到過類似場景需求寇窑,某工具類需要持有多個代理對象,方便后續(xù)逐一回調箩张。比如某個訂閱器訂閱了某個通知甩骏,然后通知到來時需要下發(fā)給每一個需要響應的頁面,這些頁面肯定是要實現(xiàn)訂閱器的代理方法的先慷。所以饮笛,遇到這種場景時,我們可能要注意了论熙。不能使用常用數(shù)據(jù)類型來管理多個代理者了(因為代理者不能被強引用福青,會有循環(huán)引用問題),此時我們可以采用NSHashTable的弱引用特性。好了,不多說了无午,直接上代碼解釋吧媒役。
有這么個單例類:

// SharedObject.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface SharedObject : NSObject
@property (nonatomic,strong,readonly)NSArray *delegates;
+ (instancetype)shared;
- (void)addDelegate:(id)delegate;
@end

NS_ASSUME_NONNULL_END

.m文件實現(xiàn)如下:

#import "SharedObject.h"

@implementation SharedObject
{
    NSHashTable *_hashTable;
}

+ (instancetype)shared {
    static SharedObject *object = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        object = [[self alloc] init];
    });
    return object;
};

- (instancetype)init {
    if (self=[super init]) {
        _hashTable = [NSHashTable weakObjectsHashTable];
    }
    return self;;
}

- (void)addDelegate:(id)delegate {
    if (delegate) {
        [_hashTable addObject:delegate];
    }
}

- (NSArray *)delegates {
     return _hashTable.allObjects;
}
@end

看到了沒,這里我們使用的是weakObjectsHashTable來實現(xiàn)宪迟。
然后代理者地方實現(xiàn):

self.sharedObject = [SharedObject shared];
[self.sharedObject addDelegate:self];

大家可以試試酣衷,把weakObjectsHashTable換成NSArray看看什么效果?(結果應該是循環(huán)引用踩验,導致代理者無法被釋放)

NSHashTable使用介紹

大家可以看到鸥诽,NSHashTable有如下幾個初始化方法:

- (instancetype)initWithOptions:(NSPointerFunctionsOptions)options capacity:(NSUInteger)initialCapacity NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithPointerFunctions:(NSPointerFunctions *)functions capacity:(NSUInteger)initialCapacity NS_DESIGNATED_INITIALIZER;
+ (NSHashTable<ObjectType> *)hashTableWithOptions:(NSPointerFunctionsOptions)options;
+ (NSHashTable<ObjectType> *)weakObjectsHashTable API_AVAILABLE(macos(10.8), ios(6.0), watchos(2.0), tvos(9.0));

使用init初始化時我們看到有個NSPointerFunctionsOptions參數(shù)商玫,它有如下集中枚舉值:

typedef NS_OPTIONS(NSUInteger, NSPointerFunctionsOptions) {
    // Memory options are mutually exclusive
    
    // default is strong
    NSPointerFunctionsStrongMemory API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (0UL << 0),       // use strong write-barrier to backing store; use GC memory on copyIn
    NSPointerFunctionsZeroingWeakMemory API_DEPRECATED("GC no longer supported", macos(10.5, 10.8)) API_UNAVAILABLE(ios, watchos, tvos) = (1UL << 0),  // deprecated; uses GC weak read and write barriers, and dangling pointer behavior otherwise
    NSPointerFunctionsOpaqueMemory API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (2UL << 0),
    NSPointerFunctionsMallocMemory API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (3UL << 0),       // free() will be called on removal, calloc on copyIn
    NSPointerFunctionsMachVirtualMemory API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (4UL << 0),
    NSPointerFunctionsWeakMemory API_AVAILABLE(macos(10.8), ios(6.0), watchos(2.0), tvos(9.0)) = (5UL << 0),         // uses weak read and write barriers appropriate for ARC
    
    // Personalities are mutually exclusive
    // default is object.  As a special case, 'strong' memory used for Objects will do retain/release under non-GC
    NSPointerFunctionsObjectPersonality API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (0UL << 8),         // use -hash and -isEqual, object description
    NSPointerFunctionsOpaquePersonality API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (1UL << 8),         // use shifted pointer hash and direct equality
    NSPointerFunctionsObjectPointerPersonality API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (2UL << 8),  // use shifted pointer hash and direct equality, object description
    NSPointerFunctionsCStringPersonality API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (3UL << 8),        // use a string hash and strcmp, description assumes UTF-8 contents; recommended for UTF-8 (or ASCII, which is a subset) only cstrings
    NSPointerFunctionsStructPersonality API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (4UL << 8),         // use a memory hash and memcmp (using size function you must set)
    NSPointerFunctionsIntegerPersonality API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (5UL << 8),        // use unshifted value as hash & equality

    NSPointerFunctionsCopyIn API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (1UL << 16),      // the memory acquire function will be asked to allocate and copy items on input
};

我們可以發(fā)現(xiàn)箕憾,默認是NSPointerFunctionsStrongMemory,即內(nèi)存強引用。效果和NSSet類似拳昌,會造成元素強引用袭异,聰明的你當然能發(fā)現(xiàn),剛好有一個NSPointerFunctionsWeakMemory炬藤。其他方法和常規(guī)NSSet使用類似御铃。

- (void)addObject:(nullable ObjectType)object;
- (void)removeObject:(nullable ObjectType)object;
- (void)removeAllObjects;

NSMapTable介紹

官方介紹是:

A collection similar to a dictionary, but with a broader range of available memory semantics.
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).

大概意思就是:NSMapTableNSDictionary類似,也擁有強大的內(nèi)存管理能力沈矿。分別對keyvalue都可以進行不同內(nèi)存引用管理上真。
還是拿上面那個例子說明:新增一個需求,能夠添加代理者和回調線程羹膳。
此時我們不好用NSHashTable來實現(xiàn)了睡互,因為NSHashTable只能夠添加一個參數(shù)(當然要實現(xiàn)也是可以的,采用中間件思想陵像,用一個新對象來分別持有這兩個參數(shù))就珠。 然而也有另外一種思路是采用NSMapTable我們剛好可以把兩個參數(shù)分別作為key-value存儲起來。好了醒颖,下面直接上代碼吧妻怎。

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface SharedObject : NSObject
@property (nonatomic,strong,readonly)NSArray *delegates;
+ (instancetype)shared;
- (void)addDelegate:(id)delegate dispathQueue:(dispatch_queue_t)queue_t;
@end

NS_ASSUME_NONNULL_END

新增一個接口方法支持傳兩個參數(shù)。

#import "SharedObject.h"

@implementation SharedObject
{
    NSMapTable *_mapTable;
}

+ (instancetype)shared {
    static SharedObject *object = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        object = [[self alloc] init];
    });
    return object;
};

- (instancetype)init {
    if (self=[super init]) {
        _mapTable = [NSMapTable weakToStrongObjectsMapTable];
    }
    return self;;
}

- (void)addDelegate:(id)delegate dispathQueue:(dispatch_queue_t)queue_t {
    if (delegate) {
        //這里需要在delegate上包一層作為key泞歉,因為key需要能夠實現(xiàn)NSCoping協(xié)議,同NSDictiony類似逼侦。
        NSMutableOrderedSet *orderSet = [NSMutableOrderedSet orderedSet];
        [orderSet addObject:delegate];
        [_mapTable setObject:queue_t?queue_t:dispatch_get_main_queue() forKey:orderSet.copy];
    }
}

- (NSArray *)delegates {
    return _mapTable.dictionaryRepresentation.allKeys;
}

@end

代理者使用地方

    self.sharedObject = [SharedObject shared];
    [self.sharedObject addDelegate:self dispathQueue:dispatch_get_main_queue()];

NSMapTable使用介紹

我們可以看到NSMapTable有下面幾種初始化方法:

- (instancetype)initWithKeyOptions:(NSPointerFunctionsOptions)keyOptions valueOptions:(NSPointerFunctionsOptions)valueOptions capacity:(NSUInteger)initialCapacity NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithKeyPointerFunctions:(NSPointerFunctions *)keyFunctions valuePointerFunctions:(NSPointerFunctions *)valueFunctions capacity:(NSUInteger)initialCapacity NS_DESIGNATED_INITIALIZER;
+ (NSMapTable<KeyType, ObjectType> *)mapTableWithKeyOptions:(NSPointerFunctionsOptions)keyOptions valueOptions:(NSPointerFunctionsOptions)valueOptions;

+ (NSMapTable<KeyType, ObjectType> *)strongToStrongObjectsMapTable API_AVAILABLE(macos(10.8), ios(6.0), watchos(2.0), tvos(9.0));
+ (NSMapTable<KeyType, ObjectType> *)weakToStrongObjectsMapTable API_AVAILABLE(macos(10.8), ios(6.0), watchos(2.0), tvos(9.0)); // entries are not necessarily purged right away when the weak key is reclaimed
+ (NSMapTable<KeyType, ObjectType> *)strongToWeakObjectsMapTable API_AVAILABLE(macos(10.8), ios(6.0), watchos(2.0), tvos(9.0));
+ (NSMapTable<KeyType, ObjectType> *)weakToWeakObjectsMapTable API_AVAILABLE(macos(10.8), ios(6.0), watchos(2.0), tvos(9.0)); // entries are not necessarily purged right away when the weak key or object is reclaimed

我們在選擇時根據(jù)需求來選擇,這里有個NSPointerFunctionsOptions提供的值類型也是和內(nèi)存相關腰耙。

typedef NS_OPTIONS(NSUInteger, NSPointerFunctionsOptions) {
    // Memory options are mutually exclusive
    
    // default is strong
    NSPointerFunctionsStrongMemory API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (0UL << 0),       // use strong write-barrier to backing store; use GC memory on copyIn
    NSPointerFunctionsZeroingWeakMemory API_DEPRECATED("GC no longer supported", macos(10.5, 10.8)) API_UNAVAILABLE(ios, watchos, tvos) = (1UL << 0),  // deprecated; uses GC weak read and write barriers, and dangling pointer behavior otherwise
    NSPointerFunctionsOpaqueMemory API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (2UL << 0),
    NSPointerFunctionsMallocMemory API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (3UL << 0),       // free() will be called on removal, calloc on copyIn
    NSPointerFunctionsMachVirtualMemory API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (4UL << 0),
    NSPointerFunctionsWeakMemory API_AVAILABLE(macos(10.8), ios(6.0), watchos(2.0), tvos(9.0)) = (5UL << 0),         // uses weak read and write barriers appropriate for ARC
    
    // Personalities are mutually exclusive
    // default is object.  As a special case, 'strong' memory used for Objects will do retain/release under non-GC
    NSPointerFunctionsObjectPersonality API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (0UL << 8),         // use -hash and -isEqual, object description
    NSPointerFunctionsOpaquePersonality API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (1UL << 8),         // use shifted pointer hash and direct equality
    NSPointerFunctionsObjectPointerPersonality API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (2UL << 8),  // use shifted pointer hash and direct equality, object description
    NSPointerFunctionsCStringPersonality API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (3UL << 8),        // use a string hash and strcmp, description assumes UTF-8 contents; recommended for UTF-8 (or ASCII, which is a subset) only cstrings
    NSPointerFunctionsStructPersonality API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (4UL << 8),         // use a memory hash and memcmp (using size function you must set)
    NSPointerFunctionsIntegerPersonality API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (5UL << 8),        // use unshifted value as hash & equality

    NSPointerFunctionsCopyIn API_AVAILABLE(macos(10.5), ios(6.0), watchos(2.0), tvos(9.0)) = (1UL << 16),      // the memory acquire function will be asked to allocate and copy items on input
};

可以看到榛丢,默認是NSPointerFunctionsStrongMemory強引用值,這里我們推薦使用NSPointerFunctionsWeakMemory來搭配使用沟优。
然后幾個類方法也分別對應了四種搭配涕滋。如下:
strongToStrongObjectsMapTable相當于

[NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions: NSPointerFunctionsStrongMemory];

weakToStrongObjectsMapTable相當于:

[NSMapTable mapTableWithKeyOptions:NSPointerFunctionsWeakMemory valueOptions:NSPointerFunctionsStrongMemory];

strongToWeakObjectsMapTable相當于:

[NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory];

weakToWeakObjectsMapTable相當于:

[NSMapTable mapTableWithKeyOptions:NSPointerFunctionsWeakMemory valueOptions: NSPointerFunctionsWeakMemory];

好了,簡單的梳理就介紹到這了挠阁。后面有更深入的理解時再來完成補充宾肺,謝謝溯饵。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市锨用,隨后出現(xiàn)的幾起案子丰刊,更是在濱河造成了極大的恐慌,老刑警劉巖增拥,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件啄巧,死亡現(xiàn)場離奇詭異,居然都是意外死亡掌栅,警方通過查閱死者的電腦和手機秩仆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猾封,“玉大人澄耍,你說我怎么就攤上這事∩卧担” “怎么了齐莲?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長磷箕。 經(jīng)常有香客問我选酗,道長,這世上最難降的妖魔是什么岳枷? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任芒填,我火速辦了婚禮,結果婚禮上嫩舟,老公的妹妹穿的比我還像新娘氢烘。我一直安慰自己,他們只是感情好家厌,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布播玖。 她就那樣靜靜地躺著,像睡著了一般饭于。 火紅的嫁衣襯著肌膚如雪蜀踏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天掰吕,我揣著相機與錄音果覆,去河邊找鬼。 笑死殖熟,一個胖子當著我的面吹牛局待,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼钳榨,長吁一口氣:“原來是場噩夢啊……” “哼舰罚!你這毒婦竟也來了?” 一聲冷哼從身側響起薛耻,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤营罢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饼齿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饲漾,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年缕溉,在試婚紗的時候發(fā)現(xiàn)自己被綠了考传。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡倒淫,死狀恐怖伙菊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情敌土,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布运翼,位于F島的核電站返干,受9級特大地震影響,放射性物質發(fā)生泄漏血淌。R本人自食惡果不足惜矩欠,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望悠夯。 院中可真熱鬧癌淮,春花似錦、人聲如沸沦补。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽夕膀。三九已至虚倒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間产舞,已是汗流浹背魂奥。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留易猫,地道東北人耻煤。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哈蝇。 傳聞我的和親對象是個殘疾皇子嘴办,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354