一. 使用 NSValue
NSValue 可以弱引用保存一个对象,我们可以使用这种方法间接的引用。
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
二. 使用 NSPointerArray,NSMapTable,NSHashTable
在iOS6.0之后出现了NSPointerArray,NSMapTable,NSHashTable。
用法分别对应 NSMutableArray,NSMutableDictionary,NSMutableSet。
- 1. NSPointerArray
特性介绍
NSPointerArray 是 NSArray 的通用版本,和 NSArray/NSMutableArray 不同的是,NSPointerArray 具有下面这些特性:
- 与 NSArray/NSMutableArray 相对应,NSArray/NSMutableArray 强引用集合对象
- NSPointerArray 可以弱引用集合对象,一旦对象没人持有了,NSPointerArray 中对应的项会被变成 NULL
- NSPointerArray 是可变的,没有不可变的版本
- NSPointerArray 可以存储 NULL,NULL 参与 count 计算
- NSPointerArray 的 count 可以被设置,如果直接设置 count,多余的位置会使用 NULL 占位
- NSPointerArray 存储的是指针类型 void * 而不是对象,所以需要 __bridge 进行转换
- 使用 addPointer 和 pointerAtIndex 来存取指针
- (instancetype)initWithOptions:(NSPointerFunctionsOptions)options;
- (instancetype)initWithPointerFunctions:(NSPointerFunctions *)functions;
NSPointerFunctionsOptions 枚举定义着内存管理策略、方法特性和内存标识,以下是几个常用的枚举值:
内存管理策略:
NSPointerFunctionsStrongMemory:强引用成员
NSPointerFunctionsMallocMemory: 用于 Mach 的 虚拟内存管理
NSPointerFunctionsMachVirtualMemory: 用于 Mach 的 虚拟内存管理
NSPointerFunctionsWeakMemory:弱引用成员
方法特性:
NSPointerFunctionsObjectPersonality:hash、isEqual、对象描述
NSPointerFunctionsOpaquePersonality:pointer 的 hash 、直接判等
内存标识:
NSPointerFunctionsCopyIn 添加成员时进行 copy 操作
提供 compact 方法剔除 NULL 元素
NSPointerArray 可以存储 NULL,作为补充,它也提供了 compact 方法,用于剔除数组中为 NULL 的成员。但是 compact 函数有个已经报备的 bug,每次 compact 之前需要添加一个 NULL,否则会 compact 失败
弱引用测试代码
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
- 2. NSHashTable
特性介绍
NSHashTable 是 NSSet 的通用版本,和 NSSet / NSMutableSet 不同的是,NSHashTable 具有下面这些特性:
- 与 NSSet/NSMutableSet 相对应,NSSet/NSMutableSet 强引用集合对象
- NSHashTable 可以弱引用集合对象,一旦对象没人持有了,NSHashTable 中的值也会被移除
- NSHashTable 是可变的,没有不可变的版本
- 除了存储对象,NSHashTable 也可以存储任意指针,比如 void *
初始化参数
+ (NSHashTable<ObjectType> *)hashTableWithOptions:(NSPointerFunctionsOptions)options;
NSHashTableOptions 的取值如下:
- NSHashTableStrongMemory: 默认值,强引用集合对象,与 NSSet 一样
- NSHashTableWeakMemory: 弱引用集合对象
- NSHashTableZeroingWeakMemory: 废弃,请使用 NSHashTableWeakMemory
- NSHashTableCopyIn: 在将对象添加到集合之前,会拷贝对象
- NSHashTableObjectPointerPersonality: 使用 shifted pointer 来做 hash 检测及确定两个对象是否相等
弱引用测试代码
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 {}
- 3. 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 的内存引用
+ (NSMapTable<KeyType, ObjectType> *)mapTableWithKeyOptions:(NSPointerFunctionsOptions)keyOptions valueOptions:(NSPointerFunctionsOptions)valueOptions;
- NSMapTableStrongMemory: 默认值,强引用 Key/Value
- NSMapTableWeakMemory: 弱引用 Key/Value
- NSHashTableZeroingWeakMemory: 废弃,请使用 NSMapTableWeakMemory
- NSMapTableCopyIn: 在将对象添加到集合之前,会拷贝对象
- NSMapTableObjectPointerPersonality: 使用 shifted pointer 来做 hash 检测及确定两个对象是否相等
弱引用测试代码
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 就会移除对应的项
参照
参考: 弱引用集合对象