This is a Swift's capture list and how it can be used to avoid retain cycle, I can't help noticing something puzzling about OperationQueue: it doesn't need either `[weak self]` or `[unowned self]` to prevent memory leak.
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.
}
}I don't see why adding an operation would not cause a retain cycle: `SomeManager` strongly owns the `queue`, which in turns strongly owns the added closures. Each added closure strongly refers back to `SomeManager`. This should theoretically create a retain cycle leading to memory leak. Yet Instruments shows that everything is perfectly fine.
Why is this the case? In some other multi-threaded, block-based APIs, like `DispatchSource`, you seem to need the capture list. See Apple's sample code `ShapeEdit for example, in `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()
}But in the same code file, `OperationQueue` doesn't need the capture list, despite having the same semantics: you hand over a closure with reference to `self` to be executed asynchronously:
fileprivate let workerQueue: OperationQueue { ... }
...
self.workerQueue.addOperation {
if let thumbnail = self.loadThumbnailFromDiskForURL(URL) {
...
self.cache.setObject(scaledThumbnail!, forKey: documentIdentifier as AnyObject)
}
}I have read about Swift's capture list mentioned above, as well as related SO answers (mentioned in the original post on SO), but I still don't know why `[weak self]` or `[unowned self]` are not needed in `OperationQueue` API while they are in `Dispatch` API. I'm also not sure how no leaks are found in `OperationQueue` case.
Any clarifications would be much appreciated.