Swift 6: The Industry-Wide Migration to Data-Race Safety
Transcript
Guest
Hey Alex! Thanks for having me. It’s... well, it’s an "interesting" time to be a Swift developer, that’s for sure. I feel like my life has been one giant quest to satisfy the compiler lately.
Host
(Laughs) I bet! I’ve seen the memes of people opening their projects in Swift 6 mode and seeing two thousand errors pop up. Before we get into the "how-to" of the migration, let’s go back to basics for a second. Why is Apple making us do this? What is a data race, actually, and why is it so insidious?
Guest
Right, so, at its simplest, a data race is when two different parts of your code—two different threads—try to touch the same piece of memory at the same time. And at least one of them is trying to change it.
Host
And that’s the "ghost" I was talking about. It’s non-deterministic.
Guest
Exactly! It depends on the CPU timing, the heat of the phone, what else is running... it’s a nightmare. Swift 6 is basically saying: "We aren't going to guess anymore. We’re going to prove, at compile time, that this can't happen."
Host
Which sounds amazing in theory, but when you actually flip that switch to Swift 6 mode in Xcode... what happens? Walk me through that "aha" moment—or maybe that "oh no" moment—you had at OrbitScale.
Guest
(Chuckles) Oh, it was definitely an "oh no" moment first. We have a codebase that’s about six years old. We thought we were pretty good at concurrency. We used Combine, we used locks where necessary. But when we opted into "Strict Concurrency Checking," the compiler basically screamed at us.
Host
Interesting! So for those who haven't hit this yet, what does `Sendable` actually mean? Why is the compiler suddenly so grumpy about our objects?
Guest
It’s basically a marker. It tells the compiler, "This type is safe to be passed across different threads." If it’s a `struct` with only value types, it’s usually Sendable by default because a copy is made. But if it’s a `class`—a reference type—you’re passing a pointer. If two threads have a pointer to the same object, you’re back to square one with data races.
Host
Let’s talk about Actors. That’s the big solution Swift 6 pushes, right? Moving legacy patterns into the Actor model. How has that refactoring process been for your team?
Guest
It’s a total shift in mindset. Actors are great because they ensure that only one task can access their internal state at a time. It’s like having a bouncer at the door of your object.
Host
That sounds like it could lead to a lot of "callback hell" or just a massive amount of `Task { @MainActor in ... }` blocks being sprinkled everywhere. Is that a trap people are falling into?
Guest
Oh, absolutely. That’s the "Band-Aid" phase. You just want the errors to go away, so you wrap everything in a `Task` or use `@MainActor` on every single class. But if you put `@MainActor` on everything, you’re basically making your app single-threaded again, which defeats the purpose of having powerful multi-core processors.
Host
I love that you mentioned deleting code. Sometimes the best refactor is just simplification. Was there a specific part of the app that was particularly stubborn during this migration?
Guest
Our networking layer was the boss fight. We had these complex completion handlers that were capturing self, doing some logic, then calling another completion handler. Making all of that "Sendable" was a nightmare because the compiler couldn't guarantee that the closure wouldn't be called on a different thread while modifying the caller's state.
Host
That sounds like a huge win. But for a smaller team or a solo dev listening to this, who might be overwhelmed by the sheer volume of changes... where do they start? You can’t just rewrite your whole app in a weekend.
Guest
Definitely don’t do it all at once! Swift 6 allows for a staged migration. You can keep your project in Swift 5 mode but turn on "targeted" concurrency warnings.
Host
That’s a great tip. The `@preconcurrency` thing is definitely a lifesaver when you're stuck with an SDK that hasn't been touched in two years.
Guest
(Pauses) That’s a tough one. In the short term? Yes. The learning curve is steeper. You can't just throw a `class` together and call it a day anymore. But in the long term? I think it makes it easier.
Host
"The compiler is the mentor." I love that. It’s frustrating when it’s yelling at you, but it’s doing it because it cares about your users! (Laughs)
Guest
Exactly! It’s a very protective mentor.
Host
So, looking forward—once the industry-wide migration is mostly "done," let’s say two years from now—what does the Swift ecosystem look like?
Guest
I think we’ll see a new standard of stability. We’re going to stop seeing those "unexplained" crashes. We’ll see more performant apps because we’ll actually be using concurrency correctly instead of being afraid of it. Honestly, I think we’ll look back at the Swift 5 era and think, "How did we ever ship code without these checks?" It’ll feel as primitive as manual memory management feels to us now.
Host
Marcus, this has been so enlightening. It’s clear that while the migration is a mountain to climb, the view from the top is going to be worth it. Thank you so much for sharing your "war stories" from OrbitScale.
Guest
My pleasure, Alex. Good luck to everyone out there battling those Sendable errors! You can do it!