Swift 6 has been obtainable to us for the higher a part of a yr now, and an increasing number of groups are contemplating or migrating to the Swift 6 language mode. This usually entails making an attempt to activate the language mode or turning on strict concurrency, seeing a complete bunch of warnings or errors, after which deciding that at the moment is just not the day to proceed with this migration.
At this time I wish to suggest an strategy to how one can plan your migration in a manner that gained’t scare you out of trying the migration earlier than you’ve even began.
Earlier than you undergo this complete submit anticipating me to inform you easy methods to go to Swift 6 inside a matter of days or perhaps weeks, I am afraid I will should disappoint you.
Migrating to Swift 6, for lots of apps, goes to be a really sluggish 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 might extremely suggest that you simply take stock of the state of your codebase. Because of this it’s best to check out how modularized your codebase is, which dependencies you’ve gotten in your codebase, and possibly most significantly how a lot concurrency you’re actually utilizing proper now. Learn how typically 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 also needs to take a look at your staff and work out how up-to-date your staff is, how comfy they’re with Swift concurrency already. Ultimately, your complete staff will want to have the ability to work on and along with your Swift 6 codebase.
On a code stage, it is important to grasp how a lot concurrency you really want as a result of Swift concurrency is, by design, going to introduce quite a lot of concurrency into your app the place possibly you do not really want all of that concurrency. That’s why it’s so necessary so that you can determine the quantity of concurrency you’ll require beforehand by analyzing what you’ve gotten now.
For instance, if in case you 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 other place, and when your networking associated capabilities invoke their callbacks, these will usually run on a background thread, and then you definately come again to the primary thread to replace your UI.
On this situation, you do not want quite a lot of concurrency; the truth is, I might say that you do not want concurrency past what URLSession supplies 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 may have already got adopted async-await, and that is utterly fantastic—it in all probability implies that you do have extra concurrency than you really want. Each nonisolated async operate that you simply write will, by default, run on a background thread. You don’t all the time want this; you’ll almost certainly wish to explicitly isolate a few of your work to the primary actor to forestall 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’ve gotten and the much less abstractions and modularization you’ve gotten, the extra complexities you may run into. Understanding your codebase deeply is a prerequisite to shifting to Swift 6.
When you perceive your codebase, you may wish to take a look at modularizing. I might say that is the most suitable choice. It does make migrating a little bit bit simpler.
So let’s speak about modularization subsequent.
Modularizing your codebase
While you migrate to Swift 6, you may discover that quite a lot 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 pressured emigrate something that is dependent upon that a part of your codebase.
Having a modularized codebase means which you could take your codebase and migrate it over time. You’ll be able to transfer element by element, relatively than being pressured 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 working into every kind of isolation or sendability warnings till your app additionally opts in to strict concurrency.
Should you do not wish to modularize your codebase otherwise you really feel your codebase is manner too small to be modularized, that is utterly fantastic. I am simply saying that the smaller your parts are, the simpler your migration goes to be.
As soon as you realize the state your codebase is in and you are 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’s endorsed to activate strict concurrency checking for the modules that you simply wish to migrate. You are able to do this for each SPM and in Xcode on your app goal.
I might suggest to do that on a module by module foundation.
So if you wish to refactor your fashions bundle first, activate strict concurrency checks on your mannequin bundle, however not but on 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.
With the ability to migrate one bundle at a time is tremendous helpful as a result of it makes all the pieces rather a lot simpler to purpose about because you’re reasoning about smaller bits of your code.
After you have your strict concurrency checks turned on you are going to see a complete 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 seemingly that you’re going to run into points like fundamental 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 all your warnings are resolved earlier than you activate Swift 6 language mode, and also you wish to just be sure you’ve bought 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 completely happy typically simply is not sufficient. You will ceaselessly wish to just be sure you really purpose in regards to the intent of your code relatively than simply making the compiler completely happy.
Take into account the next code instance:
func loadPages() {
for web page in 0..<10 {
loadPage(web page) { end in
// use end result
}
}
}
We’re iterating over an inventory of numbers and we’re making a bunch of community calls. These community calls occur concurrently and our operate does not anticipate all of them to finish. Now, the quickest method to migrate this over to Swift concurrency is likely to be to jot down an async operate and a for loop that appears like this:
func loadPages() async throws {
for web page in 0..<10 {
let end result = strive await loadPage(web page)
// use end result
}
}
The which means of this code has now modified fully. We’re making community calls one after the other and the operate does not return till each name is full. If we do wish to introduce Swift concurrency right here and preserve the identical semantics we must create unstructured duties for each single community name or we might use a job group and kick off all our community calls in parallel that manner.
Utilizing a job group would change the way in which this operate works, as a result of the operate must anticipate the duty group to finish relatively than simply letting unstructured duties run. On this refactor, it’s essential to grasp what structured concurrency is and when it is smart 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 preserve all the pieces the identical, it is typically not sufficient to maintain the compiler completely happy.
Whereas educating Groups about Swift Concurrency, I discovered it actually necessary to know precisely which instruments you’ve gotten 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 complete staff is aware of what to do.
Making certain your staff has all of the information they want
I’ve seen a number of firms try migrations to SwiftUI, Swift Knowledge, and Swift Concurrency. They typically take approaches the place a small staff does all of the legwork when it comes to exploring and studying these applied sciences earlier than the remainder of the staff is requested to be taught them too and to undertake them. Nonetheless, this typically means that there is a small staff inside the corporate that you may contemplate to be specialists. They’re going to have had entry to sources, they will have had time to coach, and as soon as they give you the final huge image of how issues ought to be carried out, the remainder of the staff form of has to be taught on the job. Typically this works effectively, however typically this breaks down as a result of the remainder of the staff merely wants to totally perceive what they’re coping with earlier than they’ll successfully be taught.
So I all the time suggest if you wish to migrate over to Swift Concurrency have your staff enroll in one in all my workshops or use my books or my course or discover some other useful resource that can train the staff 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 simple, however understanding what occurs is basically what you want when you’re planning emigrate and go all-in on concurrency.
As soon as you have determined that you will go for Swift 6 and did you wish to stage up your staff’s concurrency abilities ensure you really give all people on the staff an opportunity to correctly be taught!
Migrating from the surface 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 smart emigrate from the surface in. You possibly can additionally work from the within out and in the long run, it actually is dependent upon the place you wish to begin. That stated, I typically like to begin within the view layer as soon as all of the back-end stuff is finished 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 fundamental actor annotation to remain on the primary actor).
For instance, when 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 simply did up entrance comes into play since you already know that within the previous manner of doing issues the view mannequin would run its capabilities on the primary thread. I might extremely suggest that you don’t change this. In case your view mannequin used to run on the primary thread which is just about normal, preserve it that manner.
You will wish to apply a fundamental actor annotation to your view mannequin class.
This isn’t a nasty factor by any means, it is not a hack both. It is a manner so that you can make sure 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 quite a lot of sense. When you begin migrating like this you may work out that you simply actually did not want that a lot concurrency which you already ought to know as a result of that is what you discovered 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 anticipated.
You’ll be able to remedy sendability issues with actors. That stated, making issues actors is often not what you are on the lookout for particularly when it is associated to fashions.
Nonetheless, whenever you’re coping with a reference kind that has mutable state, that is the place you may introduce actors. It’s all about determining whether or not you have been anticipating to make use of that kind 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 may simply make one thing an actor, and it would not influence your code that a lot. However you might be introducing quite a lot 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 usually do not want quite a lot of concurrency inside an software. I am unable to stress this sufficient.
Pitfalls, caveats, and risks
Migrating to Swift 6 undoubtedly comes with its risks and uncertainties. Should you’re migrating all the pieces all of sudden, you are going to be embarking on an enormous refactor that can contain touching virtually each single object in your code. Should you introduce actors the place they actually should not belong, you all of a sudden have all the pieces in your code changing into concurrent as a result of interacting with actors is an asynchronous proces.
Should you did not comply with the steps on this weblog submit, you are in all probability going to have asynchronous capabilities all over, they usually is likely to be members of lessons or your view or the rest. A few of your async capabilities are going to be remoted to the primary actor, however most of them can be non-isolated by default, which implies that they’ll run wherever. This additionally implies that when you move fashions or objects out of your view to your few mannequin to another place that you simply’re skipping isolation contexts on a regular basis. Typically that is utterly fantastic, and the compiler will work out that issues are literally secure, however in quite a lot of instances, the compiler goes to complain about this, and you may be very pissed off about this as a result of you haven’t any concept what’s mistaken.
There’s additionally the matter of interacting with Apple’s code. Not all of Apple’s code is essentially Swift 6 suitable or Swift 6 pleasant. So that you may end up having to jot down workarounds for interacting with issues like a CLLocationManagerDelegate
or different objects that come from Apple’s frameworks. Typically it is trivial to know what to do when you totally perceive how isolation works, however quite a lot of the occasions you are going to be left guessing about what makes probably 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 identical time, Apple is Swift as a language and determining that Swift 6 is basically not within the place the place they need it to be for basic adoption.
Should you’re adopting Swift 6 proper now, there are some issues which may change down the road. You need to be prepared to take care of that. Should you’re not prepared to take care of that, I might suggest that you simply go for strict concurrency and do not go all-in on Swift 6 as a result of issues may 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 large endeavor for many groups. If you have not began already and also you’re about to begin now, I might urge you to take it sluggish – take it simple and just be sure you perceive what you are doing as a lot as attainable each step of the way in which.
Swift concurrency is fairly sophisticated, and Apple continues to be actively engaged on bettering 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 positive that migrating to Swift 6 ought to be one in all your major targets at this cut-off date.
Understanding all the pieces round Swift 6 I believe is extraordinarily helpful as a result of it does enable you to jot down higher and safer code. Nonetheless, I do consider that sticking with the Swift 5 language mode and going for strict concurrency might be your most secure wager as a result of it lets you write code that is probably not totally Swift 6 compliant however works utterly fantastic (a minimum of you possibly can nonetheless compile your undertaking even if in case you have a complete bunch of warnings).
I might 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 if in case you have tales about your migration and your journey with Swift 6, I might love to listen to that.