在 Swift 中使用委托数组时如何避免保留循环

Leo*_*ien 7 arrays delegates retain-cycle swift

在我的一个类中,我使用了一组委托(该类是一个单例)。这导致了一个保留周期。我知道当我只使用一个委托时,我可以通过使委托变弱来避免保留周期。但这对我的一系列代表不起作用。

我怎样才能避免这种保留周期。

例子:

protocol SomeDelegate: class {
    func someFunction()
}
Run Code Online (Sandbox Code Playgroud)

我的课

class SomeClass {
    // This class is a singleton!
    static let sharedInstance = SomeClass()

    var delegates = [SomeDelegate]()   // this is causing a retain cycle
    weak var delegate: SomeDelegate?   // this is ok.

    ... other code...
}
Run Code Online (Sandbox Code Playgroud)

mat*_*att 8

The problem is that weakDelegates is a strong reference and its reference to its elements of type WeakDelegateContainer is a strong reference.

Your situation is why the class NSHashTable exists. Initialize using weakObjects(). This will give you a set of ARC-weak references, each of which will be nilified and removed when the referenced object goes out of existence (with no need for any extra bookkeeping on your part, and no need for your WeakDelegateContainer type).

Your set will have to be typed as holding AnyObject, but you can easily mediate to ensure that you are supplying and retrieving SomeDelegate-conformant objects:

let list = NSHashTable<AnyObject>.weakObjects()
func addToList(_ obj:SomeDelegate) {
    list.add(obj)
}
func retrieveFromList(_ obj:SomeDelegate) -> SomeDelegate? {
    if let result = list.member(obj) as? SomeDelegate {
        return result
    }
    return nil
}
func retrieveAllFromList() -> [SomeDelegate] {
    return list.allObjects as! [SomeDelegate]
}
Run Code Online (Sandbox Code Playgroud)

The function retrieveAllFromList() lists only objects that still exist. Any object that has gone out existence has been changed to nil in the NSHashTable and is not included in allObjects. That is what I mean by "no extra bookkeeping"; the NSHashTable has already done the bookkeeping.

Here is code that tests it:

func test() {
    let c = SomeClass() // adopter of SomeDelegate
    self.addToList(c)
    if let cc = self.retrieveFromList(c) {
        cc.someFunction() 
    }
    print(self.retrieveAllFromList()) // one SomeClass object
    delay(1) {
        print(self.retrieveAllFromList()) // empty
    }
}
Run Code Online (Sandbox Code Playgroud)

Alternatively, you can use NSPointerArray. Its elements are pointer-to-void, which can be a little verbose to use in Swift, but you only have to write your accessor functions once (credit to /sf/answers/2331701501/):

let parr = NSPointerArray.weakObjects()
func addToArray(_ obj:SomeDelegate) {
    let ptr = Unmanaged<AnyObject>.passUnretained(obj).toOpaque()
    self.parr.addPointer(ptr)
}
func fetchFromArray(at ix:Int) -> SomeDelegate? {
    if let ptr = self.parr.pointer(at:ix) {
        let obj = Unmanaged<AnyObject>.fromOpaque(ptr).takeUnretainedValue()
        if let del = obj as? SomeDelegate {
            return del
        }
    }
    return nil
}
Run Code Online (Sandbox Code Playgroud)

Here is code to test it:

    let c = SomeClass()
    self.addToArray(c)
    for ix in 0..<self.parr.count {
        if let del = self.fetchFromArray(at:ix) {
            del.someFunction() // called
        }
    }
    delay(1) {
        print(self.parr.count) // 1
        for ix in 0..<self.parr.count {
            if let del = self.fetchFromArray(at:ix) {
                del.someFunction() // not called
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

Interestingly, after our SomeClass goes out of existence, our array's count remains at 1 — but cycling through it to call someFunction, there is no call to someFunction. That is because the SomeClass pointer in the array has been replaced by nil. Unlike NSHashTable, the array is not automatically purged of its nil elements. They do no harm, because our accessor code has guarded against error, but if you would like to compact the array, here's a trick for doing it (/sf/answers/2819209851/):

    self.parr.addPointer(nil)
    self.parr.compact()
Run Code Online (Sandbox Code Playgroud)