Printed on: August 7, 2024
When you begin migrating to the Swift 6 language mode, you may almost certainly activate strict concurrency first. As soon as you have accomplished this there shall be a number of warings and errors that you will encounter and these errors may be complicated at occasions.
I will begin by saying that having a strong understanding of actors, sendable, and information races is a large benefit whenever you need to undertake the Swift 6 language mode. Just about the entire warnings you may get in strict concurrency mode will inform you about potential points associated to working code concurrently. For an in-depth understanding of actors, sendability and information races I extremely suggest that you just check out my Swift Concurrency course which can get you entry to a collection of movies, workout routines, and my Sensible Swift Concurrency e book with a single buy.
WIth that out of the best way, let’s check out the next warning that you just may encounter in your undertaking:
Seize of non-sendable sort in @Sendable closure
This warning tells us that we’re capturing and utilizing a property within a closure. This closure is marked as @Sendable
which implies that we should always count on this closure to run in a concurrent atmosphere. The Swift compiler warns us that, as a result of this closure will run concurrently, we should always guarantee that any properties that we seize within this closure can safely be used from concurrent code.
In different phrases, the compiler is telling us that we’re risking crashes as a result of we’re passing an object that may’t be used from a number of duties to a closure that we should always count on to be run from a number of duties. Or not less than we should always count on our closure to be transferred from one activity to a different.
After all, there isn’t any ensures that our code will crash. Neither is it assured that our closure shall be run from a number of locations on the similar time. What issues right here is that the closure is marked as @Sendable
which tells us that we should always guarantee that something that is captured within the closure can also be Sendable
.
For a fast overview of Sendability, try my put up on the subject right here.
An instance of the place this warning may happen might appear like this:
func run(accomplished: @escaping TaskCompletion) {
guard !metaData.isFinished else {
DispatchQueue.most important.async {
// Seize of 'accomplished' with non-sendable sort 'TaskCompletion' (aka '(Consequence, any Error>) -> ()') in a `@Sendable` closure; that is an error within the Swift 6 language mode
// Sending 'accomplished' dangers inflicting information races; that is an error within the Swift 6 language mode
accomplished(.failure(TUSClientError.uploadIsAlreadyFinished))
}
return
}
// ...
}
The compiler is telling us that the accomplished
closure that we’re receiving within the run
perform cannot be handed toDispatchQueue.most important.async
safely. The explanation for that is that the run
perform is assumed to be run in a single isolation context, and the closure handed to DispatchQueue.most important.async
will run in one other isolation context. Or, in different phrases, run
and DispatchQueue.most important.async
may run as a part of totally different duties or as a part of totally different actors.
To repair this, we want. to guarantee that our TaskCompletion
closure is @Sendable
so the compiler is aware of that we will safely go that closure throughout concurrency boundaries:
// earlier than
typealias TaskCompletion = (Consequence<[ScheduledTask], Error>) -> ()
// after
typealias TaskCompletion = @Sendable (Consequence<[ScheduledTask], Error>) -> ()
In most apps, a repair like this may introduce new warnings of the identical type. The explanation for that is that as a result of the TaskCompletion
closure is now @Sendable
, the compiler goes to guarantee that each closure handed to our run
perform does not captuire any non-sendable varieties.
For instance, one of many locations the place I name this run
perform may appear like this:
activity.run { [weak self] end in
// Seize of 'self' with non-sendable sort 'Scheduler?' in a `@Sendable` closure; that is an error within the Swift 6 language mode
guard let self = self else { return }
// ...
}
As a result of the closure handed to activity.run
must be @Sendable
any captured varieties additionally have to be made Sendable.
At this level you may typically discover that your refactor is snowballing into one thing a lot larger.
On this case, I have to make Scheduler
conform to Sendable
and there is two methods for me to do this:
- Conform
Scheduler
toSendable
- Make
Scheduler
into anactor
The second possibility is almost certainly the best choice. Making Scheduler
an actor would enable me to have mutable state with out information races as a consequence of actor isolation. Making the Scheduler
conform to Sendable
with out making it an actor would imply that I’ve to do away with all mutable state since lessons with mutable state cannot be made Sendable
.
Utilizing an actor
would imply that I can now not instantly entry a number of the state and capabilities on that actor. It would be required to start out awaiting entry which implies that a number of my code has to turn out to be async
and wrapped in Process
objects. The refactor would get uncontrolled actual quick that approach.
To restrict the scope of my refactor it is smart to introduce a 3rd, non permanent possibility:
- Conform
Scheduler
toSendable
utilizing theunchecked
attribute
For this particular case I take into account, I do know that Scheduler
was written to be thread-safe. Because of this it’s very secure to work with Scheduler
from a number of duties, threads, and queues. Nonetheless, this security was applied utilizing outdated mechanisms like DispatchQueue
. Because of this, the compiler will not simply settle for my declare that Scheduler
is Sendable
.
By making use of @unchecked Sendable
on this class the compiler will settle for that Scheduler
is Sendable
and I can proceed my refactor.
As soon as I am able to convert Scheduler
to an actor
I can take away the @unchecked Sendable
, change my class
to an actor
and proceed updating my code and resolving warnings. That is nice as a result of it means I haven’t got to leap down rabbit gap after rabbit gap which might end in a refactor that will get approach out of hand and turns into virtually unimaginable to handle appropriately.