简介

开发中经常使用的集合类型有 NSArray、NSDictionary、NSSet,这三种集合类型可以解决大部分需求场景,但如果涉及到弱引用集合类、NSCopying 相关问题,则对应可以使用 NSPointerArray、NSMapTable、NSHashTable。

对应关系

NSPointerArray 对应 NSArray、NSMutableArray

NSMapTable 对应 NSDictionary、NSMutableDictionary

NSHashTable 对应 NSSet、NSMutableSet

特点

1.NSPointerArray、NSMapTable、NSHashTable 三者都是可变类型,没有不可变的父类
2.在添加元素时,无须判空,可以直接添加 NULL、nil 等空值,见下方示例代码:

示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    NSPointerArray *pointerArray = [[NSPointerArray alloc] init];
    [pointerArray addPointer:nil];
    [pointerArray addPointer:NULL];

    NSMapTable *mapTable = [[NSMapTable alloc] init];
    [mapTable setObject:NULL forKey:NULL];

    NSHashTable *hashTable = [[NSHashTable alloc] init];
    [hashTable addObject:NULL];
    
    NSLog(@"\n\npointerArray = %@count = %ld\n\nmapTable = %@count = %ld\n\nhashTable = %@count = %ld", pointerArray,pointerArray.count, mapTable,mapTable.count, hashTable, hashTable.count);

打印:

1
2
3
4
5
6
7
8
9
pointerArray = <NSConcretePointerArray: 0x600001d50460>count = 2

mapTable = NSMapTable {
}
count = 0

hashTable = NSHashTable {
}
count = 0
3.三者都拥有初始化方法 - (instancetype)initWithOptions:(NSPointerFunctionsOptions)options,参数 options 代表其所支持的放入对象的指针管理选项:
1
2
3
    NSPointerArray *pointerArray = [[NSPointerArray alloc] initWithOptions:<#(NSPointerFunctionsOptions)#>];
    NSMapTable *mapTable = [[NSMapTable alloc] initWithKeyOptions:<#(NSPointerFunctionsOptions)#> valueOptions:<#(NSPointerFunctionsOptions)#> capacity:<#(NSUInteger)#>];
    NSHashTable *hashTable = [[NSHashTable alloc] initWithOptions:<#(NSPointerFunctionsOptions)#> capacity:<#(NSUInteger)#>];

options 值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
typedef NS_OPTIONS(NSUInteger, NSPointerFunctionsOptions) {
	// 每种类别的选项互斥,只能每种类别选择一个
    // Memory Options(内存语义管理选项)
    NSPointerFunctionsStrongMemory, // 和 strong 一样,默认
    NSPointerFunctionsZeroingWeakMemory, // 已废弃,在 GC 下,弱引用指针,防止悬挂指针
    NSPointerFunctionsOpaqueMemory, // 在指针去除时不做任何动作
    NSPointerFunctionsMallocMemory, // 去除时调用 free() , 加入时 calloc()
    NSPointerFunctionsMachVirtualMemory, // 使用可执行文件的虚拟内存
    NSPointerFunctionsWeakMemory, // 和 weak 一样
    
    // Personaility Options(对象处理选项-如何进行哈希算法,判定等同性,描述)
    NSPointerFunctionsObjectPersonality, // 使用 NSObject 的 hash、isEqual、description,默认
    NSPointerFunctionsOpaquePersonality, // 使用偏移后指针,进行 hash 和直接比较等同性
    NSPointerFunctionsObjectPointerPersonality, // 和上一个相同,多了 description 方法
    NSPointerFunctionsCStringPersonality, // 使用 c 字符串的 hash 和 strcmp 比较,%s 作为 decription
    NSPointerFunctionsStructPersonality, // 使用内存的 hash 和 memcmp
    NSPointerFunctionsIntegerPersonality, // 使用偏移量作为 hash 和等同性判断

    // Copy Options(对象拷贝选项)
    NSPointerFunctionsCopyIn, // 通过 NSCopying 方法,复制后存入
};

由上述可知,当我们选择 NSPointerFunctionsWeakMemory 时,属于弱引用。

4.NSMapTable 的 key 无须遵守 NSCopying 协议

我们在使用 NSDcitionary 时,可以看到 key 的类型遵守了 NSCopying 协议,因为 value 是根据 key 值来查找的,因此 key 值不可改变,为了保证这个特性所以使用了 NSCopying 协议。总结来说,NSDcitionary 的映射关系是 object : string,而 NSMapTabTable 的映射关系是 object : object

1
NSDictionary dictionaryWithObject:<#(nonnull ObjectType)#> forKey:<#(nonnull id<NSCopying>)#>

可以看个例子:

1
2
3
4
5
6
7
8
	NSObject *a1 = [[NSObject alloc] init];
    NSObject *a2 = [[NSObject alloc] init];
    
    NSObject *b1 = [[NSObject alloc] init];
    NSObject *b2 = [[NSObject alloc] init];

    NSDictionary *dict = @{a1:b1,a2:b2};
    NSLog(@"%@", dict);

上述代码会报错:-[NSObject copyWithZone:]: unrecognized selector sent to instance 0x600000998180,因为它的 key 是 NSObject 类型,没有遵守 NSCopying 协议;

而我们使用 NSMapTabTable 来处理这种场景时,就不会有什么问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    NSObject *a1 = [[NSObject alloc] init];
    NSObject *a2 = [[NSObject alloc] init];
    
    NSObject *b1 = [[NSObject alloc] init];
    NSObject *b2 = [[NSObject alloc] init];

    NSMapTable *table = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsWeakMemory valueOptions:NSPointerFunctionsWeakMemory capacity:2];
    [table setObject:b1 forKey:a1];
    [table setObject:b2 forKey:a2];
    NSLog(@"table:%@", table);

打印结果:

1
2
3
4
table:NSMapTable {
[10] <NSObject: 0x600001fe4180> -> <NSObject: 0x600001fe41f0>
[11] <NSObject: 0x600001fe4200> -> <NSObject: 0x600001fe41e0>
}
5.NSPointerArray、NSMapTable、NSHashTable 在 add.. 时性能欠佳

具体可以参考 NSArray, NSSet, NSOrderedSet, and NSDictionary

6.API 提供相对较少

这个可以直接到相关 API 中去查看,比如 NSPointerArray 并没有提供 sort、contain 等 API。

总结

基于 NSPointerArray、NSMapTable、NSHashTable 三者的特点,可以做一些 NSArray、NSDictionary、NSSet 无法做到的事情,比如弱引用、或者存储键值对 key 值非 string 类型的对象等等,但鉴于三者在 add 元素时性能较差,以及相对可用的 API 也相对较少,还是要谨慎使用。我目前了解到的是一般第三方库会使用的较多,比如 SDWebImage、IGListKit、YYText 等等,当然项目里也会使用,但使用相对较少,主要还是使用 NSArray、NSDictionary、NSSet 这些。

参考文献: