为什么Operation Queue中不需要[weak self]或[unowned self]?
Why is [weak self] or [unowned self] not needed in Operation Queue?
在了解了 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
,后者又强烈拥有添加的闭包。每个添加的闭包都强烈引用 SomeManager
。这在理论上应该会创建一个导致内存泄漏的保留循环。然而 Instruments 显示一切正常。
为什么会这样?在其他一些多线程、基于块的 API 中,例如 DispatchSource
,您似乎需要捕获列表。参见 Apple's sample code ShapeEdit
例如,在 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 答案,例如 this and this and this,但我仍然不知道为什么 OperationQueue
API 中不需要 [weak self]
或 [unowned self]
而 Dispatch
] API。我也不确定 OperationQueue
案例中如何没有发现泄漏。
任何澄清将不胜感激。
编辑
除了下面接受的答案外,我还发现 the comment by QuinceyMorris in Apple forums 很有帮助。
那里确实有一个保留循环,但这不会自动导致内存泄漏。队列完成操作后,它会释放它,从而打破循环。
这样的临时保留循环在某些情况下非常有用,因为您不必挂在一个对象上并仍然让它完成它的工作。
作为实验,您可以暂停队列。然后你会看到内存泄漏。
在了解了 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
,后者又强烈拥有添加的闭包。每个添加的闭包都强烈引用 SomeManager
。这在理论上应该会创建一个导致内存泄漏的保留循环。然而 Instruments 显示一切正常。
为什么会这样?在其他一些多线程、基于块的 API 中,例如 DispatchSource
,您似乎需要捕获列表。参见 Apple's sample code ShapeEdit
例如,在 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 答案,例如 this and this and this,但我仍然不知道为什么 OperationQueue
API 中不需要 [weak self]
或 [unowned self]
而 Dispatch
] API。我也不确定 OperationQueue
案例中如何没有发现泄漏。
任何澄清将不胜感激。
编辑
除了下面接受的答案外,我还发现 the comment by QuinceyMorris in Apple forums 很有帮助。
那里确实有一个保留循环,但这不会自动导致内存泄漏。队列完成操作后,它会释放它,从而打破循环。
这样的临时保留循环在某些情况下非常有用,因为您不必挂在一个对象上并仍然让它完成它的工作。
作为实验,您可以暂停队列。然后你会看到内存泄漏。