Skip to content
Programing

Swift 6: The Industry-Wide Migration to Data-Race Safety

Published: Duration: 4:44
0:00 0:00

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!

Tags

mobile development ios swift compiler concurrency