NSMapTable、NSHashTable和NSPointerArray

在iOS使用集合主要是NSArray、NSDictionary和NSSet,这三个集合的特点有这几个:

  • 可是这些都是对元素做强引用的,引用计数器+1。
  • 不可以使用弱引用,如果要使用弱引用,就要用NSValue包装
  • 不可以放入nil

可是有时候需要集合的引用为弱引用,上面的这些集合就用不了了。而接下来介绍的集合就是解决这个问题的。

NSPointerArray

先来看看NSPointerArray的特点有哪些

  • 和传统的Array一样,可以有序的插入或移除
  • 和传统的Array不一样的地方就是可以添加null作为内容,并且null参与count的计数
  • 和传统的Array不一样,count可以set,多出来的元素null填充
  • 可以用weak来修饰,存储weak指针
  • 可以存储任何类型指针
  • 实现了NSFastEnumeration,可以通过for..in来遍历
初始化

NSPointerArray和NSArray的初始化很想,不过NSPointerArray的都说是可变的,最大的不同就是它们的初始化方法,NSPointerArray 有两个初始化方法:

- (instancetype)initWithOptions:(NSPointerFunctionsOptions)options;
- (instancetype)initWithPointerFunctions:(NSPointerFunctions *)functions;

通过NSPointerFunctionsOptions初始化

首先来看一下 NSPointerFunctionsOptions,它是个 option,主要分为三大类:

  • 内存管理
    • NSPointerFunctionsStrongMemory:缺省值,在 CG 和 MRC 下强引用成员
    • NSPointerFunctionsZeroingWeakMemory:已废弃,在 GC 下,弱引用指针,防止悬挂指针
    • NSPointerFunctionsMallocMemory 与 NSPointerFunctionsMachVirtualMemory: 用于 Mach 的虚拟内存管理
    • NSPointerFunctionsWeakMemory:在 CG 或者 ARC 下,弱引用成员
  • 特性,用于标明对象判等方式
    • NSPointerFunctionsObjectPersonality:hash、isEqual、对象描述
    • NSPointerFunctionsOpaquePersonality:pointer 的 hash 、直接判等
    • NSPointerFunctionsObjectPointerPersonality:pointer 的 hash、直接判等、对象描述
    • NSPointerFunctionsCStringPersonality:string 的 hash、strcmp 函数、UTF-8 编码方式的描述
    • NSPointerFunctionsStructPersonality:内存 hash、memcmp 函数
    • NSPointerFunctionsIntegerPersonality:值的 hash
  • 内存标识
    • NSPointerFunctionsCopyIn:根据第二类的选择,来具体处理。如果是 NSPointerFunctionsObjectPersonality,则根据 NSCopying 来拷贝。

所以在使用时,可以多个组合,比如:需要强引用成员、使用对象方式对比、并且 add 时 copy 对象

NSPointerFunctionsOptions *options = NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality | NSPointerFunctionsCopyIn;
NSPointerFunctions

除了使用上面给定的判断相等的方式外,还可以自定义判断

static BOOL IsEqual(const void *item1, const void *item2, NSUInteger (*size)(const void *item)) {
    return *(const int *)item1 == *(const int *)item2;
}

NSPointerFunctions *functions = [[NSPointerFunctions alloc] init];
[functions setIsEqualFunction:IsEqual];
compact

之前谈到,NSPointerArray 可以存储 NULL,作为补充,它也提供了 compact 方法,用于剔除数组中为 NULL 的成员。但是 compact 函数有个已经报备的 bug:

NSPointerArray *array = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsWeakMemory];
array.count = 10;
[array addPointer:[NSObject new]];
// compact 调用之后,NULL 并未被剔除
[array compact];

出现这种 bug 的原因,和 compact 函数的实现机制有关,当我们主动给 NSPointerArray 添加 NULL 时,数组会标记有空元素插入,此时 compact 函数才会生效,也就是说,compact 函数会先判断是否有标记,之后才会剔除。所以,当直接 set count,或者成员已经释放,自动置空的情况出现时,就会出现这个 bug。解决也很简答:

// 在调用 compact 之前,手动添加一个 NULL,触发标记
[array addPointer:NULL];
[array compact];

NSMapTable

NSMapTable对标的是NSMutableDictionary,它和NSHashTable差不多,不过最主要的是它的映射关系。NSMutableDictionary的key是需要可以实现Copying,因为需要保证key不变

与 NSMapTable 对应的,是 NSMutableDictionary。除了 集合的共有特点以外,比起传统字典,它还有一些优势:

  • key 可以不用遵循 NSCopying 协议;
  • key 和 value 的内存管理方式可以分开,如:key 是强引用,value 是弱引用;

相比起 NSPointerArray,NSMapTable 的初始化方法要多得多:

// 实例方法,虽然有 capacity 参数,但实际没用到
- (instancetype)initWithKeyOptions:(NSPointerFunctionsOptions)keyOptions valueOptions:(NSPointerFunctionsOptions)valueOptions capacity:(NSUInteger)initialCapacity;
- (instancetype)initWithKeyPointerFunctions:(NSPointerFunctions *)keyFunctions valuePointerFunctions:(NSPointerFunctions *)valueFunctions capacity:(NSUInteger)initialCapacity;

// 便利构造器
+ (NSMapTable *)mapTableWithKeyOptions:(NSPointerFunctionsOptions)keyOptions valueOptions:(NSPointerFunctionsOptions)valueOptions;

// 返回指定 key、value 内存管理类型的 map
+ (NSMapTable *)strongToStrongObjectsMapTable NS_AVAILABLE(10_8, 6_0);
+ (NSMapTable *)weakToStrongObjectsMapTable NS_AVAILABLE(10_8, 6_0);
+ (NSMapTable *)strongToWeakObjectsMapTable NS_AVAILABLE(10_8, 6_0);
+ (NSMapTable *)weakToWeakObjectsMapTable NS_AVAILABLE(10_8, 6_0);

其实,这么多的初始化方法就对应着四种搭配:

  • key 为 strong,value 为 strong
  • key 为 strong,value 为 weak
  • key 为 weak,value 为 strong
  • key 为 weak,value 为 weak
    当用 weak 修饰 key 或 value 时,有一方被释放,则该键值对移除。

NSHashTable

NSHashTable对标的是NSMutableSet,它的最主要是可以存储弱指针,在对象释放的时候,指针也变为nil。

初始化NSHashTable也比较简单

- (instancetype)initWithOptions:(NSPointerFunctionsOptions)options capacity:(NSUInteger)initialCapacity;
- (instancetype)initWithPointerFunctions:(NSPointerFunctions *)functions capacity:(NSUInteger)initialCapacity;

值得注意的是,NSHashTable 有一个 allObjectes 的属性,返回 NSArray,即使 NSHashTable 是弱引用成员,allObjects 依然会对成员进行强引用。

小结

这类集合类型在 10.5 之后引入,比传统的集合类型更为强大,但是它们的方法却没有传统集合类型多,比如对于 NSPointerArray 来说:

  • 操作均基于 index,无法通过 object 来进行操作;
  • 无法直接插入 array,或用 array 初始化;
  • 查找功能没有 NSArray 强大;
  • 没有逆序、排序等 API 提供
  • 插入的时候性能消耗是Array 的两倍,

以上几点仅仅是举的例子,所以 NSPointerArray 也并没有看起来的那么强大,一切选择标准,都应该依据具体需求。

参考资料

  • Cocoa 集合类型:NSPointerArray,NSMapTable,NSHashTable

你可能感兴趣的:(NSMapTable、NSHashTable和NSPointerArray)