Tips: Weak Arrays

0x01 How

在通常情况下,假如元素是对象类型的话,集合类型会强引用其元素,而 struct 或者是其它基础数据类型如 StringInt 等则是值类型,直接复制并没有引用。所以为了打破强引用我们需要为元素创建一个包装:

1
2
3
4
5
6
final class WeakBox<A: AnyObject> {
weak var unbox: A?
init(_ value: A) {
unbox = value
}
}

使用 WeakBox,我们可以将任何强引用变为弱引用:

1
2
let instance = UIView()
let weakReference = WeakBox(instance)

为了在数组上实现弱引用,可以简单包装所有元素:

1
2
let strongArray = [UIView(), UIView()]
let weakArray = strongArray.map { WeakBox($0) }

为了更好地抽象出 WeakBoxes 中包装与解包的相关方法,可以给数组加多一层封装:

1
2
3
4
5
6
7
struct WeakArray<Element: AnyObject> {
private var items: [WeakBox<Element>] = []

init(_ elements: [Element]) {
items = elements.map { WeakBox($0) }
}
}

同时为了能够让 WeakArray 像 Swift 中的 Array 一样具有相同的特性,使用和 Array 一样的接口方法,我们可以使 WeakArray 遵循 Collection 协议:

1
2
3
4
5
6
7
8
9
10
11
12
extension WeakArray: Collection {
var startIndex: Int { return items.startIndex }
var endIndex: Int { return items.endIndex }

subscript(_ index: Int) -> Element? {
return items[index].unbox
}

func index(after idx: Int) -> Int {
return items.index(after: idx)
}
}

那么现在我们就能像使用其它集合类型一样使用 WeakArray,例如使用 filterfirstlast 等方法:

1
2
let weakArray = WeakArray([UIView(), UIView()])
let firstElement = weakArray.filter { $0 != nil }.first // 猜猜结果使多少

0x02 However

然而上述代码中 firstElement 的值为 nil ???

我们可以回到最初 WeakBox 的实现代码,对比以下代码:

1
2
let instance = UIView()
let weakReference = WeakBox(instance)
1
2
// let instance = UIView()
let weakReference = WeakBox(UIView())

是不是大概知道了些什么?原因就是 WeakBox 并不持有对象,对象在外部也没有引用计数,那么刚创建完就自动释放掉了~

0x03 使用场景🤔

  • 不采用 Notification 方式手动实现观察者模式
  • etc

原文地址:Weak Arrays · objc.io