Capturing the Elusive ‘self’ in a `@Sendable` Closure
Image by Alfrey - hkhazo.biz.id

Capturing the Elusive ‘self’ in a `@Sendable` Closure

Posted on

One of the most thrilling experiences inSwift programming is stumbling upon an error that leaves you scratching your head. And one such error that can leave even the most seasoned developers bewildered is the infamous “Capture of ‘self’ with non-sendable type ‘TypeName’ in a `@Sendable` closure” error. In this article, we’ll delve into the world of `@Sendable` closures and explore the reasons behind this error, as well as provide clear, step-by-step instructions on how to overcome it.

What is a `@Sendable` closure?

A `@Sendable` closure is a special type of closure that allows it to be executed concurrently with the main thread. This is particularly useful when working with asynchronous code, as it enables the closure to be executed in a separate thread, improving the performance and responsiveness of your app.


@Sendable func performAsyncTask(completion: @escaping () -> Void) {
    // Perform some asynchronous task here
    completion()
}

Why does the error occur?

The error “Capture of ‘self’ with non-sendable type ‘TypeName’ in a `@Sendable` closure” occurs when you try to capture a reference to `self` (or any other object) within a `@Sendable` closure, but the type of `self` is not marked as `@Sendable`. This means that the type of `self` cannot be safely passed between threads, which is a requirement for `@Sendable` closures.

Understanding the ‘TypeName’ in the error message

The `TypeName` in the error message refers to the type of the object that is being captured within the `@Sendable` closure. For example, if you’re trying to capture a reference to a `UIViewController` instance within a `@Sendable` closure, the error message might read “Capture of ‘self’ with non-sendable type ‘UIViewController’ in a `@Sendable` closure”.


class MyViewController: UIViewController {
    @Sendable func performAsyncTask(completion: @escaping () -> Void) {
        completion()
        // Error: Capture of 'self' with non-sendable type 'MyViewController' in a `@Sendable` closure
        performSomeTask { [self] in
            // Do something with self
        }
    }
}

Solving the Error: Marking the type as `@Sendable`

The simplest solution to this error is to mark the type of `self` (or the object being captured) as `@Sendable`. This can be done by adding the `@Sendable` attribute to the type definition.


@Sendable class MyViewController: UIViewController {
    @Sendable func performAsyncTask(completion: @escaping () -> Void) {
        completion()
        performSomeTask { [self] in
            // Do something with self
        }
    }
}

But what if I can’t mark the type as `@Sendable`?

In some cases, you might not be able to mark the type as `@Sendable`, perhaps because it’s a system-provided type or a third-party library. In such cases, you’ll need to find alternative solutions.

Using a `weak` or `unowned` reference

One approach is to use a `weak` or `unowned` reference to the object being captured. This allows the closure to capture a reference to the object without causing the error.


class MyViewController: UIViewController {
    @Sendable func performAsyncTask(completion: @escaping () -> Void) {
        completion()
        performSomeTask { [weak self] in
            // Do something with self
        }
    }
}

Using an `actor` type

Another approach is to use an `actor` type, which is a special type of type that is designed to work with concurrent code. By marking the type as an `actor`, you can ensure that it can be safely passed between threads.


actor MyActor {
    @Sendable func performAsyncTask(completion: @escaping () -> Void) {
        completion()
        performSomeTask { [self] in
            // Do something with self
        }
    }
}

Best Practices for Working with `@Sendable` Closures

To avoid running into the “Capture of ‘self’ with non-sendable type ‘TypeName’ in a `@Sendable` closure” error, follow these best practices:

  • Whenever possible, mark the type of the object being captured as `@Sendable`.
  • If you can’t mark the type as `@Sendable`, use a `weak` or `unowned` reference to the object.
  • Consider using an `actor` type if you’re working with concurrent code.
  • Avoid capturing references to objects that are not designed to be used in concurrent code.

Conclusion

In conclusion, the “Capture of ‘self’ with non-sendable type ‘TypeName’ in a `@Sendable` closure” error can be a frustrating obstacle to overcome. However, by understanding the underlying causes of the error and following the solutions outlined in this article, you can ensure that your code is safe, efficient, and easy to maintain. Remember to mark your types as `@Sendable`, use `weak` or `unowned` references when necessary, and consider using `actor` types for concurrent code.

Scenario Solution
Type can be marked as `@Sendable` Mark the type as `@Sendable`
Type cannot be marked as `@Sendable` Use a `weak` or `unowned` reference
Working with concurrent code Consider using an `actor` type

By following these guidelines and best practices, you’ll be well on your way to mastering the art of working with `@Sendable` closures and overcoming the “Capture of ‘self’ with non-sendable type ‘TypeName’ in a `@Sendable` closure” error.

  1. Swift Language Guide: Closures
  2. Swift Language Guide: Concurrency
  3. Sendable and Actor in Swift

Additional resources:

Frequently Asked Questions

Get answers to your burning questions about “Capture of ‘self’ with non-sendable type ‘TypeName’ in a `@Sendable` closure”!

What is the meaning of “Capture of ‘self’ with non-sendable type ‘TypeName'” error?

This error occurs when you try to capture ‘self’ (an instance of a class) in a closure that is marked as `@Sendable`, but the type of ‘self’ (TypeName) does not conform to the `Sendable` protocol. This is a problem because `@Sendable` closures can be executed asynchronously, and the type of ‘self’ must be able to be safely passed between threads.

Why does the compiler complain about ‘TypeName’ not being Sendable?

The compiler complains because the type ‘TypeName’ does not conform to the `Sendable` protocol, which is required for types that are captured in `@Sendable` closures. This means that ‘TypeName’ may contain non-thread-safe resources, and capturing it in a `@Sendable` closure could lead to data races or other concurrency issues.

How can I fix the “Capture of ‘self’ with non-sendable type ‘TypeName'” error?

To fix this error, you need to ensure that the type of ‘self’ (TypeName) conforms to the `Sendable` protocol. If ‘TypeName’ is a class, you can make it conform to `Sendable` by adding the `@objc` attribute and making it inherit from `NSObject`. Alternatively, you can refactor your code to avoid capturing ‘self’ in the `@Sendable` closure.

Can I use a non-`Sendable` type in a `@Sendable` closure?

No, you cannot use a non-`Sendable` type in a `@Sendable` closure. The `@Sendable` attribute requires that all captured types conform to the `Sendable` protocol, which ensures that the types can be safely passed between threads. If you need to use a non-`Sendable` type, you should refactor your code to avoid capturing it in the `@Sendable` closure.

What are the consequences of ignoring the “Capture of ‘self’ with non-sendable type ‘TypeName'” error?

If you ignore this error, you risk introducing concurrency bugs into your code. Non-`Sendable` types captured in `@Sendable` closures can lead to data races, crashes, or other unpredictable behavior. It’s essential to address this error to ensure the correctness and reliability of your code.