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

时间:2017-02-25 14:53:53

标签: arrays swift delegates retain-cycle

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

如何避免这种保留周期。

示例:

protocol SomeDelegate: class {
    func someFunction()
}

我的班级

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

2 个答案:

答案 0 :(得分:3)

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

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

您的集合必须输入为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中。这就是我所说的“没有额外的簿记”; 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。它的元素是指向void的指针,在Swift中可能有点冗长,但你只需要编写一次你的访问函数(归功于https://stackoverflow.com/a/33310021/341994):

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元素。它们没有任何危害,因为我们的访问器代码已经防止了错误,但是如果你想压缩数组,这里有一个技巧(https://stackoverflow.com/a/40274426/341994):

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

答案 1 :(得分:1)

我在Using as a concrete type conforming to protocol AnyObject is not supported找到了解决方案。所有学分都归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 ...
}