【问题标题】:How to avoid a retain cycle when using an array of delegates in Swift [duplicate]在 Swift 中使用委托数组时如何避免保留周期 [重复]
【发布时间】:2017-02-25 14:53:52
【问题描述】:

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

如何避免这种保留循环。

例子:

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

【问题讨论】:

  • 该问题的答案是指符合 AnyObject 的类。我有一组不符合 AnyObject 的协议对象。
  • 协议不能符合自己,因此在将协议本身用作包装对象时,不能立即使用链接的答案(我们可能会考虑用 SomeDelegate 替换 AnyObject 约束) '想抓住一个薄弱环节。但是,您可以查看 the following Q&A,实现一个自定义容器以允许通过弱引用保存 class 受约束的协议实例。
  • dfri 的链接有帮助。我使用了 Kyle Redfearn 的答案,因为我不需要通用解决方案。我会将我的解决方案放在这个问题的答案中,以便其他人更容易找到。
  • 既然你现在已经添加了你的答案,我现在重新关闭了这个问答,将它链接到我们发现最合适的欺骗目标(不是相同的问题,但解决方案是相同的/非常相似)。旁注:如果您不知道,您可以接受自己的答案。

标签: arrays swift delegates retain-cycle


【解决方案1】:

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

您的情况就是为什么类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 中。这就是我所说的“没有额外的簿记”; 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()

【讨论】:

  • 我试过这个。我可以声明'var test = NSHashTable.weakObjects()',但是当我声明'var test = NSHashTable.weakObjects()'时,我收到错误'使用 SomeDelegate 作为符合协议 AnyObject 的具体类型是不支持'。我认为问题在于大多数解决方案不适用于协议。 WeakDelegateContainer 给出的答案适用于协议。在这种情况下,引用也将被 nilified,但我将清理我的数组以获取 nilified 引用。
  • 良好的反应。是的,我落入了 cmets 指出的同一个陷阱,不是吗?
  • NSPointerArray 对我来说是新的。据我了解,我需要一个 UnsafeMutableRawPointer 来添加到弱对象数组中。我无法理解如何将 UnsafeMutableRawPointer 获取到我的委托对象。
  • 抱歉两次。修改了我的回答以推荐 NSHashTable,并添加了显示如何使用它来存储和检索 SomeDelegate 对象的代码。
  • 不错!感谢您的解释和示例。我更改了我的代码,这是一个工作正常且更清洁的解决方案!
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 2015-10-04
    • 1970-01-01
    • 1970-01-01
    • 2016-04-02
    • 2018-05-13
    • 2019-05-24
    • 2011-02-10
    • 2011-04-28
    • 1970-01-01
    相关资源
    最近更新 更多