Swift 6 has been accessible to us for the higher a part of a yr now, and an increasing number of groups are contemplating or taking a look at migrating to the Swift 6 language mode. This sometimes entails attempting to activate the language mode or turning on strict concurrency, seeing an entire bunch of warnings or errors, after which deciding that at this time is just not the day to proceed with this migration.
As we speak I want to suggest an strategy to how one can plan your migration in a method that received’t scare you out of making an attempt the migration earlier than you’ve even began.
Earlier than you undergo this whole publish anticipating me to inform you methods to go to Swift 6 inside a matter of days or perhaps weeks, I am afraid I will need to disappoint you.
Migrating to Swift 6, for lots of apps, goes to be a really gradual and prolonged course of and it is actually a course of that you do not wish to rush.
Taking an preliminary stock
Earlier than you begin to migrate your codebase, I’d extremely suggest that you just take stock of the state of your codebase. Which means you must check out how modularized your codebase is, which dependencies you might have in your codebase, and possibly most significantly how a lot concurrency you’re actually utilizing proper now. Learn the way usually you’re explicitly, and purposefully you’re leaving the primary thread. And attempt to perceive how a lot of your code will run on the primary thread.
You must also take a look at your group and work out how up-to-date your group is, how comfy they’re with Swift concurrency already. In the long run, your complete group will want to have the ability to work on and together with your Swift 6 codebase.
On a code degree, it is important to know how a lot concurrency you really want as a result of Swift concurrency is, by design, going to introduce plenty of concurrency into your app the place possibly you do not really want all of that concurrency. That’s why it’s so vital so that you can determine the quantity of concurrency you’ll require beforehand by analyzing what you might have now.
For instance, you probably have a view and you’ve got a view mannequin, and that view mannequin talks to a different layer, then in all probability you might be doing a lot of the work on the primary thread proper now.
When you hit your networking layer, your community calls will run some place else, and when your networking associated capabilities invoke their callbacks, these will sometimes run on a background thread, and then you definately come again to the primary thread to replace your UI.
On this state of affairs, you do not want plenty of concurrency; actually, I’d say that you do not want concurrency past what URLSession offers in any respect. So when you’re adopting Swift Concurrency, you’ll wish to perceive how one can construction your code to not depart the primary thread for each async name.
You would possibly have already got adopted async-await, and that is fully superb—it in all probability signifies that you do have extra concurrency than you really want. Each nonisolated async perform that you just write will, by default, run on a background thread. You don’t all the time want this; you’ll most certainly wish to explicitly isolate a few of your work to the primary actor to stop leveraging concurrency in locations the place it’s merely not benefitting you.
You will additionally wish to just be sure you perceive how dependent or how coupled your codebase is as a result of the extra coupling you might have and the much less abstractions and modularization you might have, the extra complexities you would possibly run into. Understanding your codebase deeply is a prerequisite to transferring to Swift 6.
When you perceive your codebase, you would possibly wish to take a look at modularizing. I’d say that is the best choice. It does make migrating a bit bit simpler.
So let’s discuss modularization subsequent.
Modularizing your codebase
If you migrate to Swift 6, you may discover that plenty of objects in your code are being handed from one place to a different, and whenever you begin to introduce concurrency in a single a part of the code, you’re basically compelled emigrate something that depends upon that a part of your codebase.
Having a modularized codebase means that you may take your codebase and migrate it over time. You’ll be able to transfer part by part, fairly than being compelled to maneuver all the pieces all of sudden.
You need to use options like @preconcurrency to guarantee that your app can nonetheless use your Swift 6 modules with out operating into all types of isolation or sendability warnings till your app additionally opts in to strict concurrency.
In case you do not wish to modularize your codebase otherwise you really feel your codebase is method too small to be modularized, that is fully superb. I am simply saying that the smaller your parts are, the better your migration goes to be.
As soon as you understand the state your codebase is in and you’re feeling comfy with how all the pieces is, it is time to activate strict concurrency checks.
Turning on strict concurrency checks
Earlier than you activate Swift 6 language mode, it is strongly recommended to activate strict concurrency checking for the modules that you just wish to migrate. You are able to do this for each SPM and in Xcode to your app goal.
I’d suggest to do that on a module by module foundation.
So if you wish to refactor your fashions bundle first, activate strict concurrency checks to your mannequin bundle, however not but to your app. Turning on strict concurrency for just one module lets you work on that bundle with out forcing your app to decide into the entire sendability and isolation checks associated to the bundle you’re refactoring.
Having the ability to migrate one bundle at a time is tremendous helpful as a result of it makes all the pieces loads simpler to purpose about because you’re reasoning about smaller bits of your code.
Upon getting your strict concurrency checks turned on you are going to see an entire bunch of warnings for the packages and targets the place you have enabled strict concurrency and you can begin fixing them. For instance, it’s probably that you’re going to run into points like predominant actor remoted objects to sendable closures.
You will wish to just be sure you perceive these errors earlier than you proceed.
You wish to guarantee that your whole warnings are resolved earlier than you activate Swift 6 language mode, and also you wish to just be sure you’ve received a extremely good sense of how your code is meant to work.
The toughest half in fixing your strict concurrency warnings is that making the compiler comfortable typically simply is not sufficient. You will often wish to just be sure you truly purpose concerning the intent of your code fairly than simply making the compiler comfortable.
Contemplate the next code instance:
func loadPages() {
for web page in 0..<10 {
loadPage(web page) { end in
// use outcome
}
}
}
We’re iterating over a listing of numbers and we’re making a bunch of community calls. These community calls occur concurrently and our perform would not await all of them to finish. Now, the quickest solution to migrate this over to Swift concurrency may be to write down an async perform and a for loop that appears like this:
func loadPages() async throws {
for web page in 0..<10 {
let outcome = attempt await loadPage(web page)
// use outcome
}
}
The that means of this code has now modified completely. We’re making community calls one after the other and the perform would not return till each name is full. If we do wish to introduce Swift concurrency right here and hold the identical semantics we must create unstructured duties for each single community name or we might use a activity group and kick off all our community calls in parallel that method.
Utilizing a activity group would change the best way this perform works, as a result of the perform must await the duty group to finish fairly than simply letting unstructured duties run. On this refactor, it’s essential to know what structured concurrency is and when it is sensible to create unstructured duties.
You are having to consider what the intent of the code is earlier than you migrate after which additionally how and if you wish to change that in your migration. If you wish to hold all the pieces the identical, it is usually not sufficient to maintain the compiler comfortable.
Whereas educating Groups about Swift Concurrency, I discovered it actually vital to know precisely which instruments you might have on the market and to consider how try to be reasoning about your code.
As soon as you have turned on Swift Concurrency checks, it is time to guarantee that your whole group is aware of what to do.
Guaranteeing your group has all of the information they want
I’ve seen a number of firms try migrations to SwiftUI, Swift Information, and Swift Concurrency. They usually take approaches the place a small group does all of the legwork when it comes to exploring and studying these applied sciences earlier than the remainder of the group is requested to study them too and to undertake them. Nonetheless, this usually means that there is a small group inside the corporate that you possibly can contemplate to be specialists. They will have had entry to assets, they’re going to have had time to coach, and as soon as they give you the overall large image of how issues must be achieved, the remainder of the group form of has to study on the job. Generally this works nicely, however usually this breaks down as a result of the remainder of the group merely wants to totally perceive what they’re coping with earlier than they’ll successfully study.
So I all the time suggest if you wish to migrate over to Swift Concurrency have your group enroll in one among my workshops or use my books or my course or discover some other useful resource that may educate the group all the pieces they should know. It is actually not trivial to select up Swift Concurrency, particularly not if you wish to go into strict concurrency mode. Writing async-await capabilities is comparatively straightforward, however understanding what occurs is actually what you want in case you’re planning emigrate and go all-in on concurrency.
As soon as you have determined that you’re going to go for Swift 6 and did you wish to degree up your group’s concurrency abilities be sure to truly give all people on the group an opportunity to correctly study!
Migrating from the skin in
As soon as you have began refactoring your packages and it is time to begin working in your app goal I discovered that it actually is sensible emigrate from the skin in. You would additionally work from the within out and ultimately, it actually depends upon the place you wish to begin. That mentioned, I usually like to start out within the view layer as soon as all of the back-end stuff is completed as a result of it helps me decide at which level within the app I wish to depart the primary actor (or when yo apply a predominant actor annotation to remain on the primary actor).
For instance, in case you’re utilizing MVVM and you’ve got a view mannequin that holds a bunch of capabilities, the place ought to these capabilities run?
That is the place the work that you just did up entrance comes into play since you already know that within the outdated method of doing issues the view mannequin would run its capabilities on the primary thread. I’d extremely suggest that you don’t change this. In case your view mannequin used to run on the primary thread which is just about commonplace, hold it that method.
You will wish to apply a predominant actor annotation to your view mannequin class.
This isn’t a nasty factor by any means, it isn’t a hack both. It is a method so that you can be certain that you are not switching isolation contexts on a regular basis. You actually do not want a ton of concurrency in your app code.
So so that you can default your view fashions and possibly another objects in your code base to the primary actor merely makes plenty of sense. When you begin migrating like this you may work out that you just actually did not want that a lot concurrency which you already ought to know as a result of that is what you found out early on into course of.
That is additionally the place you begin to encounter warnings which might be associated to sendability and isolation contexts. When you begin to see these warnings and errors, you resolve that the mannequin ought to or should not be sendable relying on whether or not the swap of isolation context that’s inflicting the warning is predicted.
You’ll be able to remedy sendability issues with actors. That mentioned, making issues actors is normally not what you are on the lookout for particularly when it is associated to fashions.
Nonetheless, whenever you’re coping with a reference sort that has mutable state, that is the place you would possibly introduce actors. It’s all about determining whether or not you have been anticipating to make use of that sort in a number of isolation contexts.
Having to deeply purpose about each error and warning can typically really feel tedious as a result of it actually slows you down. You’ll be able to simply make one thing sendable, you possibly can simply make one thing an actor, and it would not influence your code that a lot. However you might be introducing plenty of complexity into your codebase whenever you’re introducing isolation contexts and whenever you’re introducing concurrency.
So once more, you actually wish to just be sure you restrict the quantity of concurrency in your app. You sometimes do not want plenty of concurrency inside an utility. I am unable to stress this sufficient.
Pitfalls, caveats, and risks
Migrating to Swift 6 undoubtedly comes with its risks and uncertainties. In case you’re migrating all the pieces all of sudden, you are going to be embarking on an enormous refactor that may contain touching virtually each single object in your code. In case you introduce actors the place they actually should not belong, you all of the sudden have all the pieces in your code changing into concurrent as a result of interacting with actors is an asynchronous proces.
In case you did not observe the steps on this weblog publish, you are in all probability going to have asynchronous capabilities in every single place, they usually may be members of courses or your view or anything. A few of your async capabilities are going to be remoted to the primary actor, however most of them might be non-isolated by default, which signifies that they’ll run anyplace. This additionally signifies that in case you go fashions or objects out of your view to your few mannequin to another place that you just’re skipping isolation contexts on a regular basis. Generally that is fully superb, and the compiler will work out that issues are literally protected, however in plenty of instances, the compiler goes to complain about this, and you’ll be very annoyed about this as a result of you don’t have any concept what’s unsuitable.
There’s additionally the matter of interacting with Apple’s code. Not all of Apple’s code is essentially Swift 6 appropriate or Swift 6 pleasant. So that you would possibly end up having to write down workarounds for interacting with issues like a CLLocationManagerDelegate
or different objects that come from Apple’s frameworks. Generally it is trivial to know what to do when you absolutely perceive how isolation works, however plenty of the occasions you are going to be left guessing about what makes essentially the most sense.
That is merely unavoidable, and we want Apple to work on their code and their annotations to guarantee that we are able to undertake Swift 6 with full confidence.
On the similar time, Apple is taking a look at Swift as a language and determining that Swift 6 is actually not within the place the place they need it to be for common adoption.
In case you’re adopting Swift 6 proper now, there are some issues that may change down the road. You need to be prepared to take care of that. In case you’re not prepared to take care of that, I’d suggest that you just go for strict concurrency and do not go all-in on Swift 6 as a result of issues would possibly change down the road and you do not wish to be doing a ton of labor that seems to be out of date. A pair variations of Swift down the road, and we’re in all probability speaking months, not years, earlier than this occurs.
In Abstract
General, I believe adopting Swift 6 is a big endeavor for many groups. If you have not began already and also you’re about to start out now, I’d urge you to take it gradual – take it straightforward and just be sure you perceive what you are doing as a lot as doable each step of the best way.
Swift concurrency is fairly difficult, and Apple continues to be actively engaged on enhancing and altering it as a result of they’re nonetheless studying about issues which might be inflicting issues for folks on a regular basis. So for that purpose, I am not even certain that migrating to Swift 6 must be one among your major targets at this time limit.
Understanding all the pieces round Swift 6 I believe is extraordinarily helpful as a result of it does assist you to to write down higher and safer code. Nonetheless, I do imagine that sticking with the Swift 5 language mode and going for strict concurrency might be your most secure guess as a result of it lets you write code that is probably not absolutely Swift 6 compliant however works fully superb (a minimum of you may nonetheless compile your mission even you probably have an entire bunch of warnings).
I’d like to know your ideas and progress on migrating to Swift 6. In my workshops I all the time hear actually cool tales about firms which might be engaged on their migration and so you probably have tales about your migration and your journey with Swift 6, I’d love to listen to that.