弱引用集合对象

NSPointerArray

特性介绍

NSPointerArray 是 NSArray 的通用版本,和 NSArray/NSMutableArray 不同的是,NSPointerArray 具有下面这些特性

  • 与 NSArray/NSMutableArray 相对应,NSArray/NSMutableArray 强引用集合对象
  • NSPointerArray 可以弱引用集合对象,一旦对象没人持有了,NSPointerArray 中对应的项会被变成 NULL
  • NSPointerArray 是可变的,没有不可变的版本
  • NSPointerArray 可以存储 NULLNULL 参与 count 计算
  • NSPointerArray 的 count 可以被设置,如果直接设置 count,多余的位置会使用 NULL 占位
  • NSPointerArray 存储的是指针类型 void * 而不是对象,所以需要 __bridge 进行转换
  • 使用 addPointer 和 pointerAtIndex 来存取指针

初始化参数

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

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

内存管理

  • NSPointerFunctionsStrongMemory:默认值,强引用集合对象
  • NSPointerFunctionsZeroingWeakMemory:废弃
  • NSPointerFunctionsMallocMemory 与 NSPointerFunctionsMachVirtualMemory: 用于 Mach 的虚拟内存管理
  • NSPointerFunctionsWeakMemory:弱引用集合对象

特性,用于标明对象判等方式

  • NSPointerFunctionsObjectPersonality:hash、isEqual、对象描述
  • NSPointerFunctionsOpaquePersonality:pointer 的 hash 、直接判等
  • NSPointerFunctionsObjectPointerPersonality:pointer 的 hash、直接判等、对象描述
  • NSPointerFunctionsCStringPersonality:string 的 hash、strcmp 函数、UTF-8 编码方式的描述
  • NSPointerFunctionsStructPersonality:内存 hash、memcmp 函数
  • NSPointerFunctionsIntegerPersonality:值的 hash

内存标识

  • NSPointerFunctionsCopyIn:根据第二类的选择,来具体处理。如果是 NSPointerFunctionsObjectPersonality,则根据 NSCopying 来拷贝

这里只关注弱引用,对象判别方式和 NSPointerFunctions 的初始化不展开

提供 compact 方法剔除 NULL 元素

NSPointerArray 可以存储 NULL,作为补充,它也提供了 compact 方法,用于剔除数组中为 NULL 的成员。但是 compact 函数有个已经报备的 bug,每次 compact 之前需要添加一个 NULL,否则会 compact 失败

弱引用测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
NSPointerArray *pointerArray = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsWeakMemory];
@autoreleasepool{
NSObject *obj = [NSObject new];
[pointerArray addPointer:(__bridge void *)obj];
NSLog(@"NSPointerArray is: %p count: %@", [pointerArray pointerAtIndex:0], @(pointerArray.count));
// 输出 NSPointerArray is: 0x60000000e800 count: 1
}
NSLog(@"After Release NSPointerArray is: %p count: %@", [pointerArray pointerAtIndex:0], @(pointerArray.count));
// 输出 After Release NSPointerArray is: 0x0 count: 1

// 每次 compact 之前需要添加 NULL,规避系统 Bug
[pointerArray addPointer:NULL];

[pointerArray compact];

NSLog(@"After Compact NSPointerArray count: %@", @(pointerArray.count));
// 输出 After Compact NSPointerArray count: 0

与 NSArray/NSMutableArray 的区别

NSArray/NSMutableArray 配合 NSValue 可以实现数组的弱引用

1
2
3
4
5
6
7
NSMutableArray *array = @[].mutableCopy;
// 添加
NSObject *obj = [NSObject new];
[array addObject:[NSValue valueWithNonretainedObject:obj]];
// 读取
NSValue *value = array[0];
NSObject *obj2 = [value nonretainedObjectValue];

注意:使用 NSValue 的方式,确实可以实现对对象的弱引用(即被添加到集合中时,对象的引用计数不会+1),但是当对象被释放的时候,数组中对应的对象会变成野指针,因此需要手动删除 NSArray 中对应对象的值,否则会在执行 [value nonretainedObjectValue] 时崩溃;而使用 NSPointerArray 不会有这个问题,对象的释放会使得集合中的对象变为 NULL

NSHashTable

特性介绍

NSHashTable 是 NSSet 的通用版本,和 NSSet / NSMutableSet 不同的是,NSHashTable 具有下面这些特性

  • 与 NSSet/NSMutableSet 相对应,NSSet/NSMutableSet 强引用集合对象
  • NSHashTable 可以弱引用集合对象,一旦对象没人持有了,NSHashTable 中的值也会被移除
  • NSHashTable 是可变的,没有不可变的版本
  • 除了存储对象,NSHashTable 也可以存储任意指针,比如 void *

初始化参数

可以在初始化 NSHashTable 时指定 NSHashTableOptions 来确定其内存引用

1
+ (NSHashTable<ObjectType> *)hashTableWithOptions:(NSPointerFunctionsOptions)options;

NSHashTableOptions 的取值如下:

  • NSHashTableStrongMemory: 默认值,强引用集合对象,与 NSSet 一样
  • NSHashTableWeakMemory: 弱引用集合对象
  • NSHashTableZeroingWeakMemory: 废弃,请使用 NSHashTableWeakMemory
  • NSHashTableCopyIn: 在将对象添加到集合之前,会拷贝对象
  • NSHashTableObjectPointerPersonality: 使用 shifted pointer 来做 hash 检测及确定两个对象是否相等

弱引用测试代码

1
2
3
4
5
6
7
8
9
10
NSHashTable *hashTable = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
@autoreleasepool {
NSObject *obj = [NSObject new];
[hashTable addObject:obj];
NSLog(@"hashTable is: %@", hashTable);
// hashTable is: NSHashTable {[3] <NSObject: 0x6000035e3f60>}
}

NSLog(@"hashTable is: %@", hashTable);
// hashTable is: NSHashTable {}

NSMapTable

特性介绍

NSMapTable 是 NSDictionary 的通用版本,和 NSDictionary/NSMutableDictionary 不同的是,NSMapTable 具有下面这些特性

  • 与 NSDictionary/NSMutableDictionary 相对应,NSDictionary/NSMutableDictionary 对 Key 拷贝,对 Value 强引用
  • key 和 value 的内存管理方式可以分开,如:key 是强引用,value 是弱引用
  • NSMapTable 可以弱引用 Key 和 Value,一旦 Key 或 Value 中的某一个没人持有了,NSMapTable 中对应的项也会被移除
  • NSMapTable 是可变的,没有不可变的版本
  • 除了存储对象,NSMapTable 也可以存储任意指针,比如 void *

总结起来一共有 4 种可能:

  • key 为 strong,value 为 strong
  • key 为 strong,value 为 weak
  • key 为 weak,value 为 strong
  • key 为 weak,value 为 weak

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

初始化参数

可以在初始化 NSMapTable 时指定 NSPointerFunctionsOptions 来分别确定对 Key 和 Value 的内存引用

1
+ (NSMapTable<KeyType, ObjectType> *)mapTableWithKeyOptions:(NSPointerFunctionsOptions)keyOptions valueOptions:(NSPointerFunctionsOptions)valueOptions;
  • NSMapTableStrongMemory: 默认值,强引用 Key/Value
  • NSMapTableWeakMemory: 弱引用 Key/Value
  • NSHashTableZeroingWeakMemory: 废弃,请使用 NSMapTableWeakMemory
  • NSMapTableCopyIn: 在将对象添加到集合之前,会拷贝对象
  • NSMapTableObjectPointerPersonality: 使用 shifted pointer 来做 hash 检测及确定两个对象是否相等

弱引用测试代码

1
2
3
4
5
6
7
8
9
10
11
12
NSMapTable *mapTable = [NSMapTable weakToStrongObjectsMapTable];
@autoreleasepool {
NSObject *key = [NSObject new];
NSObject *value = [NSObject new];
[mapTable setObject:value forKey:key];
NSLog(@"mapTable is: %@", mapTable);
// mapTable is: NSMapTable {<NSObject: 0x6000008df890> -> <NSObject: 0x6000008df870>}
}

NSLog(@"mapTable is: %@", mapTable);
// mapTable is: NSMapTable {}
// key 是 weak 引用,所以析构之后 NSMapTable 就会移除对应的项

何时使用

NSHashTable 和 NSMapTable 都比 NSSet 和 NSDictionary 都更加强大,但是大部分情况下,我们用 NSSet 和 NSDictionary 就已经足够,只有在对内存有特别要求的情况下,才使用 NSHashTable 和 NSMapTable

参考文章

  1. NSPointerArray
    1. 特性介绍
    2. 初始化参数
    3. 提供 compact 方法剔除 NULL 元素
    4. 弱引用测试代码
    5. 与 NSArray/NSMutableArray 的区别
  2. NSHashTable
    1. 特性介绍
    2. 初始化参数
    3. 弱引用测试代码
  3. NSMapTable
    1. 特性介绍
    2. 初始化参数
    3. 弱引用测试代码
  4. 何时使用
  5. 参考文章