What
在 Swift 中,当我们创建一个数组,那么数组本身对于添加进去的对象元素默认是强引用(Strong)的,这会使得元素的引用计数自增:
class Pencil {
var type: String
var price: Double
init(_ type: String, _ price: Double) {
self.type = type
self.price = price
}
}
let pencilBox = [pencil2B, pencilHB]
CFGetRetainCount(pencil2B as CFTypeRef) // 3
CFGetRetainCount(pencilHB as CFTypeRef) // 3
那么今天的问题是,如何在 Swift 中尝试对数组元素弱引用呢?
如何在 Swift 中获取对象的引用计数?
在 Swift 中,我们可以使用
func CFGetRetainCount(_ cf: CoreFoundation.CFTypeRef!) -> CFIndex
函数,即传入一个CFTypeRef
类型的对象即可获取其引用计数。什么是CFTypeRef
?查阅官方文档即可得知typealias CFTypeRef = AnyObject
,所以CFTypeRef
其实就是AnyObject
。而AnyObject
又是所有类隐含遵守的协议。CFGetRetainCount(Pencil("2B", 1.0) as CFTypeRef) // 1 let pencil2B = Pencil("2B", 1.0) let pencilHB = Pencil("HB", 2.0) CFGetRetainCount(pencil2B as CFTypeRef) // 2 CFGetRetainCount(pencilHB as CFTypeRef) // 2
How
WeakBox
首先,我们构造一个 WeakBox
即封装了弱引用元素的类:
final class WeakBox<A: AnyObject> {
weak var unbox: A?
init(_ value: A) {
unbox = value
}
}
接下来,创建一个容纳 WeakBox
数组的类型,其构造方法可以将 Array
的元素封装为 WeakBox
类型:
struct WeakArray<Element: AnyObject> {
private var items: [WeakBox<Element>] = []
init(_ elements: [Element]) {
items = elements.map { WeakBox($0) }
}
}
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)
}
}
此时,我们就可以使用这一新类型存储弱引用的数组元素:
let weakPencilBox1 = WeakArray([pencil2B, pencilHB])
CFGetRetainCount(pencil2B as CFTypeRef) // 3
CFGetRetainCount(pencilHB as CFTypeRef) // 3
let firstElement = weakPencilBox1.filter { $0 != nil }.first
firstElement!!.type // 2B
CFGetRetainCount(pencil2B as CFTypeRef) // 4
CFGetRetainCount(pencilHB as CFTypeRef) // 3
如上,WeakyArray
就可以存储弱引用的元素了。注意,这里 pencil2B
的引用计数由于 firstElement
的持有(Retain),因而导致其引用计数自增。
NSPointerArray
我们也可以使用 NSPointerArray
实现弱引用元素的数组:
let weakPencilBox2 = NSPointerArray.weakObjects()
let pencil2BPoiter = Unmanaged.passUnretained(pencil2B).toOpaque()
let pencilHBPoiter = Unmanaged.passUnretained(pencilHB).toOpaque()
CFGetRetainCount(pencil2B as CFTypeRef) // 4
CFGetRetainCount(pencilHB as CFTypeRef) // 3
weakPencilBox2.addPointer(pencil2BPoiter)
weakPencilBox2.addPointer(pencilHBPoiter)
CFGetRetainCount(pencil2B as CFTypeRef) // 4
CFGetRetainCount(pencilHB as CFTypeRef) // 3
根据官方文档:
A collection similar to an array, but with a broader range of available memory semantics.
即 NSPointerArray
比普通的 NSArray
多了一层内存语义。可以更方便的控制其中元素的引用关系,但少了 Swift 中着重强调的类型安全,所以更推荐第一种做法。
Extension
其实不只是数组,集合类型的数据结构对其中的元素默认均是强引用。所以为了更加方便地自定义内存管理方式,Objective-C / Swift 中均有普通类型的对应。但在目前的 Swift 中,NSHashTable
和 NSMapTable
均需要指定泛型,使得更加类型安全(在网上的过时资料中可以看出,之前的 Swift 也没有规定需指定类型),而在 Objective-C 中只要满足 id
类型即可。
NSHashTable
:
// NSHashTable - NSSet
let weakPencilSet = NSHashTable<Pencil>(options: .weakMemory)
weakPencilSet.add(pencil2B)
weakPencilSet.add(pencilHB)
NSMapTable
:
// NSMapTable - NSDictionary
class Eraser {
var type: String
init(_ type: String) {
self.type = type
}
}
let weakPencilDict = NSMapTable<Eraser, Pencil>(keyOptions: .strongMemory, valueOptions: .weakMemory)
let paintingEraser = Eraser("Painting")
weakPencilDict.setObject(pencil2B, forKey: paintingEraser)
- Objective-C:
NSHashTable *set = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
[set addObject:@"Test"];
[set addObject:@12];