SWIFT

Swift Escaping Closure: Syntax, Usage, and Practical Examples

The Swift escaping closure is a fundamental concept for handling asynchronous code, delayed execution, and any scenario where a closure might be called after the function it was passed into has returned. In Swift, closures are self-contained blocks of functionality that can be passed around and executed. By default, closures are non-escaping, which means they must be executed within the function they are passed to. If you need a closure to outlive the function, you must explicitly mark it as @escaping.

Understanding how and when to use an escaping closure Swift helps you manage memory properly, avoid retain cycles, and design APIs that integrate well with asynchronous operations like networking or background processing.


What Is an Escaping Closure in Swift?

A Swift escaping closure is a closure passed into a function, but instead of being executed within the function’s body, it gets stored and executed later—often after the function has already returned. This delayed execution requires special treatment because the closure may capture references from outside its original scope, including the function itself or its parameters.

To mark a closure as escaping, you use the @escaping attribute before the parameter type.

func performLater(_ closure: @escaping () -> Void) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        closure()
    }
}

In this example, the closure is not called immediately. Instead, it is stored and executed later on the main queue, so @escaping is required.


Syntax and Usage

To define a function that accepts an escaping closure, you use @escaping as part of the parameter declaration:

func storeClosure(_ completion: @escaping () -> Void) {
    closures.append(completion)
}

You must mark a closure as @escaping in any case where the closure will be:

  • Executed after the function ends
  • Stored in a variable or property
  • Passed to another function that retains it
  • Used in asynchronous code like networking, timers, or animation

This requirement ensures clarity and safety, especially around capturing variables and memory management.


Escaping vs Non-Escaping Closures

Non-Escaping Closures

By default, closures are non-escaping. This means they are guaranteed to be executed before the function returns.

func executeImmediately(_ closure: () -> Void) {
    closure()
}

In this case, there's no need for @escaping because the closure runs during the function call.

Escaping Closures

Closures that might be called later must be explicitly marked as escaping.

func executeLater(_ closure: @escaping () -> Void) {
    storedClosures.append(closure)
}

The key difference lies in when and where the closure is called. Escaping closures require special treatment by the compiler and may affect how values are captured and retained in memory.


Practical Examples of Swift Escaping Closure

1. Networking

func fetchData(completion: @escaping (Data?) -> Void) {
    URLSession.shared.dataTask(with: someURL) { data, _, _ in
        completion(data)
    }.resume()
}

This is one of the most common uses of escaping closures. The completion handler runs after the network call finishes, long after fetchData returns.

2. Storing Closures

var storedClosures: [() -> Void] = []

func remember(_ closure: @escaping () -> Void) {
    storedClosures.append(closure)
}

Here, closures are added to a list and called later, so they must be marked as escaping.


Optional Escaping Closure in Swift

You can also define an optional escaping closure. This means a closure might or might not be provided, and if it is, it may be used after the function scope.

func performAction(callback: (@escaping () -> Void)?) {
    if let callback = callback {
        storedClosures.append(callback)
    }
}

This is useful in customizable APIs or situations where the user might not need a callback every time.


Capturing self in Escaping Closures

One of the critical challenges with escaping closure Swift is managing memory. If you reference self inside an escaping closure, it must be marked explicitly using self, and this can lead to strong reference cycles unless handled correctly.

Example of potential retain cycle:

class ViewModel {
    func fetch() {
        networkService.request { self.handleResponse() }
    }
}

To prevent memory leaks, use [weak self] or [unowned self] depending on your needs:

networkService.request { [weak self] in
    self?.handleResponse()
}

This ensures that the closure does not strongly retain self, which would otherwise prevent deinitialization.


Real-World Use Cases

Here are some common real-world scenarios where Swift escaping closure is essential:

  • Asynchronous APIs: Completion handlers in networking, animation, database operations.
  • Event listeners: Passing handlers to be invoked later based on user interaction or external events.
  • Dependency injection: Passing closures that return instances or configure services later.
  • Retry mechanisms: Storing closures to retry a failed operation.

These patterns often involve closures that are executed outside of the original function’s scope, which necessitates the use of @escaping.


Best Practices

To use escaping closures effectively:

  • Only mark closures as @escaping if necessary. Overuse can lead to accidental memory issues.
  • Capture self weakly unless you are sure there's no retain cycle.
  • Avoid deep nesting of escaping closures to keep logic clear.
  • Label your closure parameters for better clarity (completion, handler, callback, etc.).
  • Use Result<T, Error> in escaping closures to improve error handling in asynchronous tasks.

Limitations and Considerations

  • You cannot use inout parameters with escaping closures.
  • Escaping closures are often the source of memory leaks if not handled correctly.
  • Escaping closures execute outside the original function, so they may execute at unpredictable times or even on different threads.
  • They introduce complexity in testability and debugging.

Understanding how closures are stored and executed helps avoid issues and keeps your asynchronous logic safe and maintainable.


Summary

A Swift escaping closure allows the closure to be called after the function it was passed into has returned. You declare these using the @escaping attribute. This feature is essential for asynchronous tasks like network calls, delayed UI updates, and stored event handlers. Escaping closures are more flexible but also introduce challenges around memory management and execution timing.

Use escaping closure Swift carefully by managing captured references, especially self, and applying best practices to keep your code efficient, readable, and safe from memory leaks.

Learn to Code in Swift for Free
Start learning now
button icon
To advance beyond this tutorial and learn Swift by doing, try the interactive experience of Mimo. Whether you're starting from scratch or brushing up your coding skills, Mimo helps you take your coding journey above and beyond.

Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.

You can code, too.

© 2025 Mimo GmbH

Reach your coding goals faster