【问题标题】:Why is [weak self] or [unowned self] not needed in Operation Queue?为什么 Operation Queue 中不需要 [weak self] 或 [unowned self]?
【发布时间】:2018-01-06 03:50:10
【问题描述】:

在了解了Swift's capture list 以及如何使用它来避免保留循环之后,我不禁注意到OperationQueue 的一些令人费解的地方:它不需要[weak self][unowned self] 来防止内存泄漏.

class SomeManager {
    let queue = OperationQueue()
    let cache: NSCache = { () -> NSCache<AnyObject, AnyObject> in
        let cache = NSCache<AnyObject, AnyObject>()
        cache.name = "huaTham.TestOperationQueueRetainCycle.someManager.cache"
        cache.countLimit = 16
        return cache
    }()

    func addTask(a: Int) {
        queue.addOperation { // "[unowned self] in" not needed?
            self.cache.setObject(a as AnyObject, forKey: a as AnyObject)
            print("hello \(a)")
        }
    }
}

class ViewController: UIViewController {

    var someM: SomeManager? = SomeManager()

    override func viewDidLoad() {
        super.viewDidLoad()
        someM?.addTask(a: 1)
        someM?.addTask(a: 2)
    }

    // This connects to a button.
    @IBAction func invalidate() {
        someM = nil  // Perfectly fine here. No leak.
    }
}

我不明白为什么添加操作不会导致保留周期:SomeManager 强烈拥有 queue,而 queue 又强烈拥有添加的闭包。每个添加的闭包都强烈引用回SomeManager。从理论上讲,这应该会创建一个导致内存泄漏的保留周期。然而 Instruments 表明一切都很好。

为什么会这样?在其他一些多线程、基于块的 API 中,例如 DispatchSource,您似乎需要捕获列表。参见Apple's sample codeShapeEdit,例如ThumbnailCache.swift

fileprivate var flushSource: DispatchSource
...
flushSource.setEventHandler { [weak self] in   // Here
    guard let strongSelf = self else { return }

    strongSelf.delegate?.thumbnailCache(strongSelf, didLoadThumbnailsForURLs: strongSelf.URLsNeedingReload)
    strongSelf.URLsNeedingReload.removeAll()
}

但在同一个代码文件中,OperationQueue 不需要捕获列表,尽管具有相同的语义:你交出一个引用 self 的闭包以异步执行:

fileprivate let workerQueue: OperationQueue { ... }
...
self.workerQueue.addOperation {
    if let thumbnail = self.loadThumbnailFromDiskForURL(URL) {
        ...
        self.cache.setObject(scaledThumbnail!, forKey: documentIdentifier as AnyObject)
    }
}

我已经阅读了上面的 Swift's capture list 以及相关的 SO 答案,例如 thisthisthis,但我仍然不知道为什么不需要 [weak self][unowned self] OperationQueue API,而它们在 Dispatch API 中。我也不确定在OperationQueue 案例中如何没有发现泄漏。

任何澄清将不胜感激。

编辑

除了下面接受的答案,我还发现the comment by QuinceyMorris in Apple forums 很有帮助。

【问题讨论】:

    标签: swift memory-leaks automatic-ref-counting nsoperationqueue retain-cycle


    【解决方案1】:

    你确实有一个保留周期,但这不会自动导致内存泄漏。队列完成操作后,它会释放它,从而打破循环。

    这种临时保留周期在某些情况下非常有用,因为您不必挂在一个对象上并仍然让它完成它的工作。

    作为一项实验,您可以暂停队列。然后你会看到内存泄漏。

    【讨论】:

    • 但是如何释放操作呢?从Apple's ARC guide,在PersonApartment 示例中,一旦你有一个强引用循环,即使你将两个引用都设置为nil,你似乎永远无法释放任何内存。跨度>
    • 你一定误会了。一旦循环中的一个强引用设置为 nil,其余的将被自动清理。您不能使用deinit 来执行此操作,因为在循环中断之前它不会运行。
    • 恕我直言,我不认为我误解了这一点。明确指出“当你将这两个变量设置为 nil 时,没有调用 deinitializer。强引用循环防止 PersonApartment 实例被释放,从而导致你的应用程序中的内存泄漏。”所以这意味着一旦你有一个强引用循环,你就会有内存泄漏。这就是为什么我想知道为什么weak / unowned 没有被用于OperationQueue。同一指南中的 HTMLElement 示例也是如此。
    • 如果你不相信我自己试试。这是一个示例程序,它首先形成一个循环,然后打破它:gist.github.com/5sw/819bdae89fb6e5ef21355adf982554f8 ARC 指南中的示例也是正确的。如果您无法再访问循环中的任何对象,则无法破坏它。因此,如果您从我的要点中获取示例并将弱引用 a 设置为 nil,您将无法再打破循环,并且您确实会泄漏内存。但这不会发生在您的操作队列中。
    • 感谢您的评论和回答。有时间我会看看这个,然后告诉你。
    猜你喜欢
    • 2015-07-02
    • 1970-01-01
    • 1970-01-01
    • 2019-12-03
    • 2014-08-02
    • 1970-01-01
    • 2016-12-08
    • 1970-01-01
    • 2023-04-03
    相关资源
    最近更新 更多