iOS 数组中如何存储弱引用
今天在项目中遇到了一个有趣的问题。项目中有一个监听的服务,监听需要将控制器放入一个数组中为其进行相应的操作。不过这引发了一个循环引用的问题。那么问题就变成了,如何在数组中存储弱引用呢?
数组中存储弱引用我们可以有两种解决方式:
- 设置一个类来存储我们的控制器,这个类的中用于存储的属性自然是
weak类型。 - 使用
NSPointerArray。
使用自定义类存储控制器
1 | class WeakObject<T: AnyObject> { |
这样我们就不会出现相互持有的情况。详细代码如下:
1 | class DemoViewController: UIViewController { |
使用 NSPointerArray
NSPointerArray 类是一个稀疏数组,工作起来与 NSMutableArray 相似,但可以存储 NULL 值,并且 count 方法会反应这些空点。可以用 NSPointerFunctions 对其进行各种设置,也有应对常见的使用场景的快捷构造函数 strongObjectsPointerArray 和 weakObjectsPointerArray。
在能使用 insertPointer:atIndex: 之前,我们需要通过直接设置 count 属性来申请空间,否则会产生一个异常。另一种选择是使用 addPointer:,这个方法可以自动根据需要增加数组的大小。
你可以通过 allObjects 将一个 NSPointerArray 转换成常规的 NSArray。这时所有的 NULL 值会被去掉,只有真正存在的对象被加入到数组 — 因此数组的对象索引很有可能会跟指针数组的不同。注意:如果向指针数组中存入任何非对象的东西,试图执行 allObjects 都会造成 EXC_BAD_ACCESS 崩溃,因为它会一个一个地去 retain 对象。
从调试的角度讲,NSPointerArray 没有受到太多欢迎。description 方法只是简单的返回了<NSConcretePointerArray: 0x17015ac50>。为了得到所有的对象需要执行 [pointerArray allObjects],当然,如果存在 NULL 的话会改变索引。
这里我们只需要将上述的 var viewControllers = [WeakObject<UIViewController>]() 设置成 var viewControllers = NSPointerArray(options: .weakMemory) 即可。详细代码如下:
1 | class DemoViewController: UIViewController { |
虽然这里使用 NSPointerArray 非常的方便,不过就性能来说,NSPointerArray 真的非常非常慢(不过这里数据量很小,到看不出与 Array 的差别)。
当你打算在一个很大的数据集合上使用它的时候一定要三思。在本测试中我们比较了使用 NSNull 作为空标记的 NSMutableArray ,而对 NSPointerArray 我们用 NSPointerFunctionsStrongMemory 来进行设置 (这样对象会被适当的 retain)。在一个有 10,000 个元素的数组中,我们每隔十个插入一个字符串 Entry %d。此测试包括了用 NSNull.null 填充 NSMutableArray 的总时间。对于 NSPointerArray,我们使用 setCount: 来代替:
| 类 / 时间 [ms] | 10.000 elements |
|---|---|
| NSMutableArray, adding | 15.28 |
| NSPointerArray, adding | 3851.51 |
| NSMutableArray, random access | 0.23 |
| NSPointerArray, random access | 0.34 |
注意 NSPointerArray 需要的时间比 NSMutableArray 多了超过 250 倍(!) 。这非常奇怪和意外。跟踪内存是比较困难的,所以按理说 NSPointerArray 会更高效才对。不过由于我们使用的是同一个 NSNull 来标记空对象,所以除了指针也没有什么更多的消耗。
参考文档:
- 基础集合类