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
来标记空对象,所以除了指针也没有什么更多的消耗。
参考文档:
- 基础集合类