Practice - 在 Swift 中對集合類型元素的弱引用

For more, please visit my GitHub repo: github.com/kingcos/Per…html

Date Notes Swift Xcode
2018-03-15 更新部分表述,並將題目擴展至集合類型 4.0 9.2
2018-03-08 首次提交 4.0 9.2

Preface

爲了方便下述 Demo,這裏定義一個 Pencil 類,並會使用 func CFGetRetainCount(_ cf: CoreFoundation.CFTypeRef!) -> CFIndex 方法,即傳入一個 CFTypeRef 類型的對象便可獲取其引用計數。什麼是 CFTypeRef?查閱官方文檔便可得知 typealias CFTypeRef = AnyObject,因此 CFTypeRef 其實就是 AnyObject。而 AnyObject 又是全部類隱含遵照的協議。ios

class Pencil {
    var type: String
    var price: Double
    
    init(_ type: String, _ price: Double) {
        self.type = type
        self.price = price
    }
}

CFGetRetainCount(Pencil("2B", 1.0) as CFTypeRef)
// 1

let pencil2B = Pencil("2B", 1.0)
let pencilHB = Pencil("HB", 2.0)

CFGetRetainCount(pencil2B as CFTypeRef)
CFGetRetainCount(pencilHB as CFTypeRef)
// 2 2
複製代碼

What

在 Swift 中,當建立一個數組時,數組自己對於添加進去的對象元素默認是強引用(Strong),會使得其引用計數自增。git

let pencilBox = [pencil2B, pencilHB]

CFGetRetainCount(pencil2B as CFTypeRef)
CFGetRetainCount(pencilHB as CFTypeRef)
// 3 3
複製代碼

那麼今天的問題便是,如何使得數組自己對數組元素進行弱引用?github

How

WeakBox

final class WeakBox<A: AnyObject> {
    weak var unbox: A?
    init(_ value: A) {
        unbox = value
    }
}

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)
    }
}
複製代碼

定義好一個能夠將全部類型的對象轉化爲弱引用的類,再經過構建好的新類型,將每一個強引用元素轉換爲弱引用元素。利用 Extension,還能夠遵照協議,擴展一些集合方法。web

let weakPencilBox1 = WeakArray([pencil2B, pencilHB])

CFGetRetainCount(pencil2B as CFTypeRef)
CFGetRetainCount(pencilHB as CFTypeRef)
// 3 3

let firstElement = weakPencilBox1.filter { $0 != nil }.first
firstElement!!.type
// 2B

CFGetRetainCount(pencil2B as CFTypeRef)
CFGetRetainCount(pencilHB as CFTypeRef)
// 4 3 Note: 這裏的 4 是由於 firstElement 持有(Retain)了 pencil2B,致使其引用計數增 1
複製代碼

NSPointerArray

let weakPencilBox2 = NSPointerArray.weakObjects()

let pencil2BPoiter = Unmanaged.passUnretained(pencil2B).toOpaque()
let pencilHBPoiter = Unmanaged.passUnretained(pencilHB).toOpaque()

CFGetRetainCount(pencil2B as CFTypeRef)
CFGetRetainCount(pencilHB as CFTypeRef)
// 4 3

weakPencilBox2.addPointer(pencil2BPoiter)
weakPencilBox2.addPointer(pencilHBPoiter)

CFGetRetainCount(pencil2B as CFTypeRef)
CFGetRetainCount(pencilHB as CFTypeRef)
// 4 3
複製代碼

A collection similar to an array, but with a broader range of available memory semantics.swift

Apple Documentation數組

NSPointerArray 比普通的 NSArray 多了一層內存語義。能夠更方便的控制其中元素的引用關係,但少了 Swift 中着重強調的類型安全,因此更推薦第一種作法。安全

Extension

其實不僅是數組,集合類型的數據結構對其中的元素默認均是強引用。因此爲了更加方便地自定義內存管理方式,Objective-C/Swift 中均有普通類型的對應。但在目前的 Swift 中,NSHashTableNSMapTable 均須要指定類型,更加的類型安全(在網上的過期資料中能夠看出,以前的 Swift 也沒有規定需指定類型),而在 Objective-C 中只要知足 id 類型便可。bash

  • 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];
複製代碼

也歡迎您關注個人微博 @萌面大道V數據結構

Reference

相關文章
相關標籤/搜索