ProgrammingiOS Developer

What are escaping and non-escaping closures in Swift? How to properly organize work with closures, what nuances exist in their usage, and what could this lead to when working with asynchronous code?

Pass interviews with Hintsage AI assistant

Answer.

Escaping closure is a closure that "escapes" the scope of the calling function (for example, it is saved for asynchronous execution).

By default, in Swift, functions take non-escaping closures: the closure is executed within the function call.

To explicitly indicate escaping, the @escaping keyword is used:

func asyncTask(completion: @escaping () -> Void) { DispatchQueue.main.async { completion() } }

Key differences:

  • Escaping closures can be stored and called later.
  • To capture self in an escaping closure, you need to explicitly specify [weak self] or [unowned self] to prevent memory leaks (retain cycle).

Trick question.

Do you always need to write @escaping for closures passed as parameters to functions calling DispatchQueue.async?

— Yes. Since DispatchQueue.async retains the closure until it is executed, the closure becomes escaping.

Example:

func foo(action: () -> Void) { DispatchQueue.main.async { action() // Will not compile: closure must be escaping. } } // Needs to be: func bar(action: @escaping () -> Void) { ... }

Examples of real mistakes due to ignorance of the topic's nuances.


Story

The controller created a strong reference to self inside an escaping closure (for example, a network request). The controller was not released after going off-screen — a strong retain cycle. Solution: use [weak self] or [unowned self].


Story

The function passed a closure to DispatchQueue.async, but did not mark it as escaping. The project did not compile, and errors were hard to find due to nested functions.


Story

Inside the closure, an object was accessed that had already been deinitialized by the time the closure was called (used [unowned self]). As a result — runtime crash. Solution: use [weak self] and nil checking.