Is optional closure always escaping? Should we use [weak self] or [unowned self] on it?

Viewed 457

Give the following function signature in UICollectionView.

open func performBatchUpdates(_ updates: (() -> Void)?, completion: ((Bool) -> Void)? = nil)

There are some options over the Internet, that optional closures are always escaping. But, I am really not sure is that true, or this is still debatable.

  1. https://forums.swift.org/t/allowing-escaping-for-optional-closures-in-method-signature/27556
  2. https://gist.github.com/gringoireDM/1f18f3bb4e74e2d914a748d89486db56

For the above case, do we ever need [weak self], or [unowned self] in the 1st block, and 2nd black?

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    collectionView!.performBatchUpdates({ () -> Void in
        for operation: BlockOperation in self.blockOperations {
            operation.start()
        }
    }, completion: { (finished) -> Void in
        self.blockOperations.removeAll(keepingCapacity: false)

        self.collectionView.reloadData()
    })
}
1 Answers

According to the compiler (which has the only "opinion" that matters for something like this), an optional closure parameter is always implicitly @escaping. Test this yourself by writing a function that takes an optional closure and mark it as @escaping. The compiler will inform you that it's already escaping.

As to whether you should capture self as weak or unowned, that depends. First I'd stay away from capturing self as unowned, unless you want your program to crash if you're wrong about the lifetime of self - and you might. I'd rather my program crash than silently do the wrong thing.

But that leaves whether to capture as weak. My opinion is yes, if you're in any doubt capturing a strong reference creating a reference cycle that would prevent it from properly deinitializing. But I wouldn't go so far as to say always or never do something. If you are able to reason about your object's life time so that you know the closure will be run and disposed of before the object should normally be deinitialized, then just capture as a strong reference. Also capture by strong reference if you purposefully want to extend the life of your object for the sake of the closure and you know that the closure will be executed and disposed of.

Also there is the case where you know that despite being implicitly @escaping that it doesn't actually escape. Usually that's for a function defined in your code base so you can look at the implementation, but really any time the closure is just a customization point for work that has to be done before the function you're calling returns, you can infer that it doesn't actually escape. That's not the case for completion handlers, but it might be for other things.

That's my opinion. I like to think it's an informed one, but others might disagree.

Related