Why is [weak self] or [unowned self] not needed in Operation Queue?

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.



No Leak Image



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.

Accepted Answer

There is a temporary references cycle in the OperationQueue example, but it is broken as soon as the operation is removed from the queue for execution. For that reason, it doesn't matter if the closure captures self strongly.


The same thing applies to the references between self and workerQueue in the second example. There's no permanent reference cycle.


However, there is a reference cycle between flushSource and self. (The DispatchSource references the event handler, which is a cloure that references self.) This cycle is permanent (unless broken manually by the code later) because it doesn't depend on whether there's anything in the DispatchQueue or not.

Why is [weak self] or [unowned self] not needed in Operation Queue?
 
 
Q