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

How to avoid a retain cycle when using an array of delegates in Swift

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

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

示例:

protocol SomeDelegate: class {
    func someFunction()
}

我的Class

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...
}

我在 中找到了解决方案。所有学分归功于 Kyle Redfearn。

我的解决方案

protocol SomeDelegate: class {
    func someFunction()
}

class WeakDelegateContainer : AnyObject {
    weak var weakDelegate: SomeDelegate?
}

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

    fileprivate var weakDelegates = [WeakDelegateContainer]()

    func addDelegate(_ newDelegate: SomeDelegate) {
        let container = WeakDelegateContainer()
        container.weakDelegate = newDelegate
        weakDelegates.append(container)
    }

    func removeDelegate(_ delegateToRemove: SomeDelegate) {
        // In my case: SomeDelegate will always be of the type UIViewController
        if let vcDelegateToRemove = delegateToRemove as? UIViewController {
            for i in (0...weakDelegates.count - 1).reversed() {
                if weakDelegates[i].weakDelegate == nil {
                    // object that is referenced no longer exists
                    weakDelegates.remove(at: i)
                    continue
                }

                if let vcDelegate = weakDelegates[i].weakDelegate as? UIViewController {
                    if vcDelegate === vcDelegateToRemove {
                        weakDelegates.remove(at: i)
                    }
                }
            }
        }
    }

    ... other code ...
}

问题在于 weakDelegates 是一个强引用,它对其类型为 WeakDelegateContainer 的元素的引用是一个强引用。

你的情况就是 class NSHashTable 存在的原因。使用 weakObjects() 初始化。这将为您提供一组 ARC 弱引用,当引用的对象不存在时,每个引用都将被消除和删除(您不需要任何额外的簿记,也不需要您的 WeakDelegateContainer 类型)。

您的集合必须输入为持有 AnyObject,但您可以轻松调解以确保您提供和检索符合 SomeDelegate 的对象:

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]
}

函数retrieveAllFromList() 仅列出仍然存在的对象。任何已经不存在的对象在 NSHashTable 中被更改为 nil,并且不包含在 allObjects 中。这就是我所说的 "no extra bookkeeping"; NSHashTable 已经完成了簿记。

这是测试它的代码:

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
    }
}

或者,您可以使用 NSPointerArray。它的元素是指向空的指针,在 Swift 中使用可能有点冗长,但您只需编写一次访问器函数(归功于 ):

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
}

这是测试它的代码:

    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
            }
        }
    }

有趣的是,在我们的 SomeClass 不存在之后,我们数组的 count 仍然为 1 — 但通过它循环调用 someFunction,没有调用 someFunction。那是因为数组中的SomeClass指针已经被nil代替了。与 NSHashTable 不同,数组不会自动清除其 nil 元素。它们没有害处,因为我们的访问器代码已防止错误,但如果您想压缩数组,这里有一个技巧 ():

    self.parr.addPointer(nil)
    self.parr.compact()