The adoption of strict concurrency in Swift is not an easy process in some projects. Implicit assumptions we usually make are often challenged by the compiler, and we don’t know how to solve them in many cases - leading to frustration among us, developers. The amount of keywords also doesn’t help: tasks, sendability, isolation domains and boundaries, actors, preconcurrency, and the list goes on.
But the main focus of the conversation is Swift 6’s strict concurrency. We discuss the factors one should consider before adopting it, the feelings we face when encountering enigmatic errors, and also suggest practical solutions and strategies to upgrade to strict checking with peace of mind.
Swift Toolkit
Matt
I'm doing great, thank you!
Swift Toolkit
Thanks for showing up! We have a lot to talk about Swift concurrency today. Before we jump in, do you want to introduce yourself a little bit and talk about your previous experience?
Matt
Sure. I'm a very long time Apple developer. I started working with Apple platforms before Swift, before Objective-C - I started with C. That was before Mac OS X, so I was developing for classic Mac OS originally. I've gone through many transitions and it's been funny actually to see this new transition happening in Swift. It's been an interesting one.
Swift Toolkit
And you've been at Apple also for a while, right?
Matt
Yeah, that was a while ago. I joined Apple right after iOS, right after the iPhone was released and I worked on the iPhone team. I did performance and power there. I worked with many different teams, but it was all about understanding performance and how it relates to battery life.
Swift Toolkit
So I assume a lot of low-level stuff.
Matt
Almost zero UI actually, just a little bit. Then, after Apple, I worked for a company called Crashlytics, which did crash reporting - that product still exists today, owned by Google now.
That was a lot of fun and I learned a whole bunch. Up until that point, I had done almost all development on Apple platforms. That was the first time when I really got interested in and had the opportunity to do backend work. The system had a very complicated backend component to it.
Swift Toolkit
Yeah, I assume, because it involves dSym and processing crashes associated with specific dSYM files, right?
Matt
Yes, that's right. Crashlytics was a very popular service - and still is - so it processed an enormous amount of traffic. I got a chance to see a very big scale backend. That was really cool, and there were many languages in there. I worked with Ruby, Go, and Java. I did a little bit of Scala because, for a while, Crashlytics was owned by Twitter, and they used an enormous amount of Scala. That was really cool to learn about as well.
Then I went off on my own to work on some personal projects just to experiment, and I ended up getting more and more interested in doing open source work. While doing open source work, which I continue to do a lot of, I kind of accidentally discovered there was an opportunity to do consultancy work. It started very small, working with friends actually.
I just got this really weird specialization in Swift concurrency. And now that is what I do - probably 80% of my time is related to doing consultant work for either people adopting, having trouble with, or just general concurrency work.
Swift Toolkit
Yeah, a lot of people when they have questions on Mastodon, they mention you, right? You became a reference. But how did you get there? How was the process of learning?
Matt
Complete accident. Everything about it was a total accident. I have this policy, because I've had trouble in the past, that in general I don't adopt a new Apple technology until it's been out for at least a year. I had never used the async/await keywords in any other language before. I had no idea how this system worked, so I waited about a year.
I was working on one of my open source projects and a component of that was a public API. I thought: "well, I'm building a new public API, everybody seems to be talking about async sequence and async/await, which I know nothing about. So I might as well try them." I started trying to learn, and I felt things were going okay. But as I began putting them more and more into the application, I started running into an enormous number of problems.
Swift Toolkit
Matt
It was over the course of me realizing that I actually didn't know what I was doing. It's very easy to use Swift concurrency wrong - it was so easy, it's almost the default to use Swift concurrency wrong. I started trying to understand how it works for real. As part of that, you discover that you can turn on these warnings that are off by default for some reason, and when you turn them on, you realize "wait a minute, I don't even understand what the warnings mean."
Through that process of learning, I wrote a couple of blog posts about it. I ended up getting really lucky and got connected with the Swift compiler team. One of the contracts that I did over the summer was to write the Swift 6 migration guide for swift.org.
Swift Toolkit
Oh wow, I wasn't aware of this guide! Okay, we'll leave a link for that.
Matt
Yeah, so that was a close collaboration with the compiler team. There's this big migration guide and I wrote a significant portion of it.
Swift Toolkit
Very cool! I was watching also your talk where you talk about these problems that they are in a scale of... fear level?

Matt's talk in Swift TO 2023
Things can get very complicated and that's why I feel that doing simple try await stuff and writing async functions, that's easy. But once you start digging deeper or having more complicated scenarios, then they get really complicated. And that's why I have mixed feelings about structured concurrency, because it's amazing when you start using it, it makes things so much simpler to write code and it's much more readable and everything.
But if you don't know what you're doing, you can get in very bad shape.
Matt
You know, I don't even understand how anybody could know what they're doing because the compiler feedback is all off by default. If you just start experimenting with these things or you do some Google searches and you find some blog posts about how this is supposed to work, it's very easy to find information that's based on an understanding without any compiler feedback turned on.
And so it can feel, and this is what happened to me - easy to get started, but it's not easy at all. You had mixed feelings about it and I think that's correct. That's the correct feeling to have. I think some people feel extremely negative about Swift concurrency and it makes a lot of sense because it can be very hard to take an existing project and migrate it to the Swift 6 language mode, for example, can be impossible to get to for some projects.
Even just understanding, forget going to the Swift 6 language mode, just understanding some basic usage of it can be very, very challenging. You said that it can be simple in simple cases. I don't even agree with that. I think that it can be quite hard to use even for very simple uses.
Now that said, I will add that I don't think that locks and queues are easy to use either. In fact, the more concurrency consulting I've been doing, I've been running into so much code that uses locks and queues that is totally wrong. I mean, completely wrong in every possible way.
Swift Toolkit
And the advantage is that with Swift 6, we have that at the compiler level, right?
Matt
That's right. Yes. And you're getting this compiler feedback. It's happened with my own code too, that I've migrated quite a bit of my own stuff from using more traditional synchronization primitives to concurrency.
And I thought this code was correct. And as I was doing this, I discovered that I really did have some problems and it wasn't the compiler making my life difficult. What was making my life difficult was I had bugs and the way that I had organized and really architected the system was never gonna work properly.
And so it's painful to get that information.
Swift Toolkit
So how did you solve it? Because we said that mixed feelings is legit and I think it's part of the process...
We feel we understand, then we realize the mixed feelings come when we realize we don't. And how you bridged this gap?
Matt
Well, I think the feelings come from frustration about understanding there's so many tools in the language. The language has expanded in an enormous way over the past two or three years. So one part is just frustration. How do I learn how all these things work? And that is hard.
I talked about this migration guide and it covers large portions of how the language works, but it doesn't cover everything. And also, it's not stopping. There are additional things that are gonna keep changing. I don't think it's gonna change in dramatic ways for the most part, but just learning how does everything work is hard. So that's one source of frustration.
And then another totally independent one is: How do I take the code that I have right now, which I believe works correctly, how do I take that code and move it into, let's say, the Swift 6 language mode, error-free? You can't even compile when you do that.
And most of the time, what I talk about with teams is why are you doing it? What is the motivation for you to move from where you are now? You have a working application, you have other projects, things that you want to do that are not related to just making the compiler happy.
Swift Toolkit
Yes, you need to ask yourself "why do you want to spend your time on this?"
Matt
Right! And so it could be that you are very concerned you have real races. Maybe these real races are impacting your users. And I'm working with a team now, they're very concerned about race conditions. They have, and we have identified, an enormous number of real races in their code. How can we address this? And the problem is intractable. Without compiler feedback, it will never happen. We will never be able to get a handle on these problems. And that's the reason why they're so excited about using Swift 6 to do it.
But not every application is in that situation. You might have a working code base. I don't know why you would migrate to Swift 6. I tell many people don't do it. There's no pressing need to do this. Maybe you have a reason why maybe you want to start a brand new module with a small amount of functionality and you want to turn Swift 6 mode on for that particular module. That does make sense. But to spend time... especially if it's hard to spend so much time migrating your code.
Swift Toolkit
But for those teams that do decide to move and they have large projects, what are the tips you usually give? Where to start from?
Matt
If you have a large project, hopefully you also have a lot of modularity. And there's a lot of tools in the language to control what kind of warnings you see. Up until the language mode is also per module. So if you have a big project with a lot of modules, one module at a time, that's what you have to do. And it's tricky because...
I don't think there's a universal solution. I've had pretty good success with bouncing around between "I'll make some change" and I'll realize, "I need this struct to be Sendable and this type has to be MainActor" and I'll make those changes, and then I'll reevaluate where I am. And sometimes that can involve changes in multiple modules. But usually I don't think there really is one strategy, especially for bigger projects. You're talking about having many teams or many developers.
You're going to have to tackle it module by module. And those teams that are doing this work, they will have to understand more deeply how the language works the further away from the UI you get. If you're working purely from like Swift UI or UIKit, and you're very much at the top level of the application, for the most part, you're thinking MainActor or not MainActor. And almost everything is going to be MainActor. It can be easy.
But as soon as you start building libraries or going into deeper systems, these will be more complicated. They won't necessarily work only on the MainActor. And then you have to understand some of these maybe more complicated language tools to build the APIs you need to build.
Swift Toolkit
Yeah, makes sense. And I assume that starting from a lower level, from a more core framework, that would affect other, every framework that depends on it? Or not necessarily?
Matt
It depends what the changes are. For the most part, there's a poorly understood tool, which is the @preconcurrency annotation. It is really useful for this particular operation where you have some core library and you want to add some annotations, but you don't want to force clients to then have to update their stuff as well.
This is what SwiftUI uses: it annotates its views with MainActor, but it also puts pre-concurrency, which means if you're using concurrency warnings or Swift 6 mode, I will enforce those annotations. But if you're not, if you're just a regular Swift 5 user with warnings off, you'll see nothing.
Those tools can be really useful for you to introduce. I always think about it as introducing truth into your code base. Like this callback always happens on the main thread. Put the annotation MainActor on there!
But then also put pre-concurrency so that if there are clients that don't want to see this warning, they don't see them. But expressing the reality of your code base is a component of adopting Swift concurrency. And usually you can do that without too much pain in the rest of the code base, unless you have to do something like maybe make a new function asynchronous. That can be a very painful thing to do, especially at a lower level.
Swift Toolkit
And there's also @preconcurrency when you do the import, right?
Matt
Yeah, pre-concurrency is so tricky because it has two different meanings depending on whether you're using an API. If you're the consumer, then you use maybe a pre-concurrency import. If you're the vendor of an API, if you're making it, then you can annotate your own stuff. It's all related, but it has two different uses.
That's another strategy that people will take is some people's idea is I want to take my existing code base and make the minimum number of changes. But I want to go to Swift 6 language mode. This is very hard to do, but if you really want to, you have to get good at using pre-concurrency to be able to suppress warnings that come around. And the trade-off is then, that's an even harder justification. You're, why are you using Swift 6 when you're turning off all the compiler feedback?
You're telling when you're using a pre-concurrency import, you're promising the compiler, the way I'm using this API is exactly correct. But that might not be true. And now you've turned all the warnings off, so you have none of the feedback.
Swift Toolkit
Yeah, basically you enable and then you quietly disable. It's just like adding a linter and disabling it in some places!
Matt
Yeah, that's exactly what it's like, yes!
Swift Toolkit
Okay, and while watching your talk in the Toronto conference, Swift TO, you mentioned a few packages that you wrote to help with some challenges about Swift concurrency. There was Main Offender and Queue. What do they solve?
Do you feel the language is missing things like this?
Matt
Okay, so I'll start with MainOffender because it's an interesting one. It actually was a primary part of that talk, discussing how you might go from an existing project that's using DispatchQueues, to make them warning free in the Swift 6 mode. That talk didn't age very well because a lot of the things that I discussed have since been addressed by Apple by annotating their APIs more correctly.
So the original reason I made that package, MainOffender, was because I just found it incredibly frustrating to have to constantly add these things to my code base. But, they've done a okay job, I'll say of updating the language and the frameworks to be annotated.
Such that it's relatively easy to use these things. Some of the code that I presented in that talk now can be error-free. In fact, it's actually amazing how much you can get error-free in Swift 6 with a Swift 6 compiler. It's pretty incredible. But they didn't do a perfect job, right? Because there's some very large, important APIs. I'll give you an example is OperationQueue.
I don't think it's in wide use now, as it has been in the past. But OperationQueue received very little updates and it didn't match the updates that were done to DispatchQueue, for example. And so I at the time used OperationQueue a little, hadn't quite removed it completely from all of my code base. At one point I used it a lot. So I added just a few little helpers to be able to move towards being warning free with OperationQueue.
Swift Toolkit
Matt
What I discovered as I was doing this was I didn't want unsafe opt-outs. I didn't want to continue to use these tools that turned off the warnings. My goal wasn't, "I don't want no warnings... I want no races!". I want to be able to understand the language to achieve that goal. So, over time, that package has kind of evolved from being a way to turn off warnings to just a few targeted tools to be able to temporarily deal with situations that Apple doesn't give you tools to otherwise address.
There aren't that many, but there are some important ones. For example, run loops. I've interacted with a whole bunch of APIs that are run loop only. I mean, a common one is NSTimer. It is probably used by so many applications, but it integrates very poorly with Swift concurrency. I don't think there's too many other commonly used APIs that are run loop only. But you might need a tool to deal with that. And so if you do, then Apple provides nothing.
Swift Toolkit
What other frameworks are missing annotations?
Matt
I have heard of the most anyways from people is Combine. Combine is this API that was used extensively by many, many, many projects. And it received basically no annotations at all. And I was surprised by that.
Swift Toolkit
All the point of Combine is dealing with streams and asynchronous stuff!
Matt
Yeah. And I understand too that the way that combine models concurrency is not really possible to express purely using the type system, which is what Swift concurrency does. You just can't model that correctly with Swift concurrency. So they needed to provide some sort of tools to deal with it.
That's what this thing called dynamic isolation is all about. It's a way to write your code. I know the compiler can't see how this is going to work, but I'm going to promise you that this is the way that it works, and the compiler will then just verify at runtime that you've made correct promises. But it's really hard to do that, and even understand what's going wrong.
Combine as missing all kinds of important annotations. And these have really terrible consequences for your application. Yeah, if you start adopting Swift 6. I don't really have any good solutions to this one.
This is the thing really you asked me before about like adopting Swift 6. I think this is one of the things where you've decided, as a team: "we're going to adopt Swift 6". Part of that decision is, and we are going to take on adopting a technology that's a major language change in the first year that it's been introduced. When we know not all of Apple's APIs have been properly migrated. You're taking on a lot of responsibility by doing that.
Swift Toolkit
Yeah, it's not an easy choice.
I think that these problems, they can be divided into Apple platforms, which we have zero influence, and there is the language domain, which we could, in theory, suggest or draft a new proposal... that could maybe address DispatchQueue issues, for example.
Matt
While the language can be changed, some fundamental limitations exist in what can be described using the type system, which is what Swift concurrency relies on. Take DispatchQueue's async functions for example - whether they run on the main or background thread depends on runtime queue instances, which can't be modeled in the type system.
Similar limitations exist with Combine - some patterns simply aren't describable using the type system. While new language features are coming (as improvements to deinitialization), most problems people face are actually related to their application architecture or missing annotations in Apple's frameworks.
Apple is making progress though - from Xcode 16.0 to 16.1, they fixed many annotation issues in WidgetKit, which was previously very problematic. If you encounter API issues, filing feedback is very helpful.
Swift Toolkit
That makes sense. Is it fair to assume that new frameworks, should be annotated correctly, or not so much?
Matt
I think it's fair to assume that, but I don't think that it's the reality all the time. I'm pretty sure there was a new framework added in iOS 18 that had no concurrency annotations.
Swift Toolkit
I think that every case is a different one... You spoke about it before, that you must have a good reason to do it now and if you're in no rush then maybe it's better to wait. One thing I didn't know is that it's totally opt-in. Even if you are using Swift 6, you don't need to opt-in.
Matt
Yeah, it's so confusing because there's the Swift 6 compiler, and then there's the Swift 6 language mode, and they are independent of each other (sort of).
It's so confusing because many people thought typed throws was this feature that came around with the Swift 6 compiler and it was exciting. It's a really cool feature and it enables all kinds of neat things. And many people thought, but I can't use typed throws until I adopt the Swift 6 language mode. And that's not true.
You can use type throws, which is a Swift 6 compiler only feature. It's totally fine to use that with code that has absolutely no concurrency stuff turned on.
Swift Toolkit
You mentioned the migration guide as a good resource. Are there other resources do you think you could point people to?
Matt
Well, something that I have used personally a lot is the Swift forums. They're really great because there are many questions that have already been answered there, but people are very happy to answer the same question again. There's a lot of people that know exactly how the language works, including the engineers who work on the compiler themselves. I think it's a really, really underutilized resource.
So it's not great for asking about API specific questions, but for the language and the standard library it's a wonderful place to get information.
You can also check out my blog (link below), find me on social media, and also email me. If you're just looking at people, ask me for questions about concurrency. I'm happy to help. So reach out to me however you want. And I would be, I would love to hear from people.
Swift Toolkit
Yeah, and if people on teams, want to schedule some consultancy time with you also via email.
Matt
Absolutely, that's something I do. If you want more formal help, mean just small questions, I'm happy to just answer them. But if you want something more dedicated, then totally reach out to me and we'll schedule something.
Swift Toolkit
Awesome, thank you so much, thanks for your tips. was great having you here and we'll see you online solving doubts about Swift concurrency. Thank you so much!
Matt