Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Diagnose and fix Swift Concurrency issues: async/await, actor isolation, Sendable, and Swift 6 migration.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/migration.md
1# Migration to Swift 6 and Strict Concurrency23Use this when:45- You are moving an existing codebase toward Swift 6 or stricter concurrency checking.6- Compiler diagnostics depend on language mode, default isolation, or upcoming features.7- You need the smallest safe migration sequence instead of a full architectural rewrite.89Skip this file if:1011- You already know the exact diagnostic and only need a local fix. Start from `actors.md`, `sendable.md`, or `threading.md`.12- You are looking for debounce, stream composition, or FRP operator replacements. Use `async-algorithms.md`.1314Jump to:1516- Project Settings17- Six Migration Habits18- Step-by-Step Migration19- Migration Tooling20- Rewriting Closures to Async/Await21- Migrating from Combine/RxSwift22- Concurrency-Safe Notifications (iOS 26+)23- Anti-Patterns2425---2627## Why Migrate to Swift 6?2829Swift 6 doesn't fundamentally change how Swift Concurrency works—it **enforces existing rules more strictly**:3031- **Compile-time safety**: Catches data races and threading issues at compile time instead of runtime32- **Warnings become errors**: Many Swift 5 warnings become hard errors in Swift 6 language mode33- **Future-proofing**: New concurrency features will build on this stricter foundation34- **Better maintainability**: Code becomes safer and easier to reason about3536> **Important**: You can adopt strict concurrency checking gradually while still compiling under Swift 5. You don't need to flip the Swift 6 switch immediately.3738> **Course Deep Dive**: This topic is covered in detail in [Lesson 12.2: The impact of Swift 6 on Swift Concurrency](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)3940---4142## Project Settings That Change Concurrency Behavior4344Before interpreting diagnostics or choosing a fix, confirm the target/module settings. These settings can materially change how code executes and what the compiler enforces.4546### Quick matrix4748| Setting / feature | Where to check | Why it matters |49|---|---|---|50| Swift language mode (Swift 5.x vs Swift 6) | Xcode build settings (`SWIFT_VERSION`) / SwiftPM `// swift-tools-version:` | Swift 6 turns many warnings into errors and enables stricter defaults. |51| Strict concurrency checking | Xcode: Strict Concurrency Checking (`SWIFT_STRICT_CONCURRENCY`) / SwiftPM: strict concurrency flags | Controls how aggressively Sendable + isolation rules are enforced. |52| Default actor isolation | Xcode: Default Actor Isolation (`SWIFT_DEFAULT_ACTOR_ISOLATION`) / SwiftPM: `.defaultIsolation(MainActor.self)` | Changes the default isolation of declarations; can reduce migration noise but changes behavior and requirements. |53| `NonisolatedNonsendingByDefault` | Xcode upcoming feature / SwiftPM `.enableUpcomingFeature("NonisolatedNonsendingByDefault")` | Changes how nonisolated async functions execute (can inherit the caller’s actor unless explicitly marked `@concurrent`). |54| Approachable Concurrency | Xcode build setting / SwiftPM enables the underlying upcoming features | Bundles multiple upcoming features; recommended to migrate feature-by-feature first. |5556## The Concurrency Rabbit Hole5758A common migration experience:59601. Enable strict concurrency checking612. See 50+ errors and warnings623. Fix a bunch of them634. Rebuild and see 80+ new errors appear6465**Why this happens**: Fixing isolation in one place often exposes issues elsewhere. This is normal and manageable with the right strategy.6667> **Course Deep Dive**: This topic is covered in detail in [Lesson 12.1: Challenges in migrating to Swift Concurrency](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)6869---7071## Six Migration Habits for Success7273### 1. Don't Panic—It's All About Iterations7475Break migration into small, manageable chunks:7677```swift78// Day 1: Enable strict concurrency, fix a few warnings79// Build Settings → Strict Concurrency Checking = Complete8081// Day 2: Fix more warnings8283// Day 3: Revert to minimal checking if needed84// Build Settings → Strict Concurrency Checking = Minimal85```8687Allow yourself 30 minutes per day to migrate gradually. Don't expect completion in a few days for large projects.8889### 2. Sendable by Default for New Code9091When writing new types, make them `Sendable` from the start:9293```swift94// ✅ Good: New code prepared for Swift 695struct UserProfile: Sendable {96let id: UUID97let name: String98}99100// ❌ Avoid: Creating technical debt101class UserProfile { // Will need migration later102var id: UUID103var name: String104}105```106107It's easier to design for concurrency upfront than to retrofit it later.108109### 3. Use Swift 6 for New Projects and Packages110111For new projects, packages, or files:112- Enable Swift 6 language mode from the start113- Use Swift Concurrency features (async/await, actors)114- Reduce technical debt before it accumulates115116You can enable Swift 6 for individual files in a Swift 5 project to prevent scope creep.117118### 4. Resist the Urge to Refactor119120**Focus solely on concurrency changes**. Don't combine migration with:121- Architecture refactors122- API modernization123- Code style improvements124125Create separate tickets for non-concurrency refactors and address them later.126127### 5. Focus on Minimal Changes128129- Make small, focused pull requests130- Migrate one class or module at a time131- Get changes merged quickly to create checkpoints132- Avoid large PRs that are hard to review133134### 6. Don't Just @MainActor All the Things135136Don't blindly add `@MainActor` to fix warnings. Consider:137- Should this actually run on the main actor?138- Would a custom actor be more appropriate?139- Is `nonisolated` the right choice?140141**Exception**: For app projects (not frameworks), consider enabling **Default Actor Isolation** to `@MainActor`, since most app code needs main thread access.142143> **Course Deep Dive**: This topic is covered in detail in [Lesson 12.3: The six migration habits for a successful migration](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)144145---146147## Step-by-Step Migration Process148149### 1. Find an Isolated Piece of Code150151Start with:152- Standalone packages with minimal dependencies153- Individual Swift files within a package154- Code that's not heavily used throughout the project155156**Why**: Fewer dependencies = less risk of falling into the concurrency rabbit hole.157158### 2. Update Related Dependencies159160Before enabling strict concurrency:161162```swift163// Update third-party packages to latest versions164// Example: Vapor, Alamofire, etc.165```166167Apply these updates in a separate PR before proceeding with concurrency changes.168169### 3. Add Async Alternatives170171Provide async/await wrappers for existing closure-based APIs:172173```swift174// Original closure-based API175@available(*, deprecated, renamed: "fetchImage(urlRequest:)",176message: "Consider using the async/await alternative.")177func fetchImage(urlRequest: URLRequest,178completion: @escaping @Sendable (Result<UIImage, Error>) -> Void) {179// ... existing implementation180}181182// New async wrapper183func fetchImage(urlRequest: URLRequest) async throws -> UIImage {184return try await withCheckedThrowingContinuation { continuation in185fetchImage(urlRequest: urlRequest) { result in186continuation.resume(with: result)187}188}189}190```191192**Benefits**:193- Colleagues can start using async/await immediately194- You can migrate callers before rewriting implementation195- Tests can be updated to async/await first196197**Tip**: Use Xcode's **Refactor → Add Async Wrapper** to generate these automatically.198199### 4. Change Default Actor Isolation (Swift 6.2+)200201For app projects, set default isolation to `@MainActor`:202203**Xcode Build Settings**:204```205Swift Concurrency → Default Actor Isolation = MainActor206```207208**Swift Package Manager**:209```swift210.target(211name: "MyTarget",212swiftSettings: [213.defaultIsolation(MainActor.self)214]215)216```217218This drastically reduces warnings in app code where most types need main thread access.219220### 5. Enable Strict Concurrency Checking221222**Xcode Build Settings**: Search for "Strict Concurrency Checking"223224Three levels available:225226- **Minimal**: Only checks code that explicitly adopts concurrency (`@Sendable`, `@MainActor`)227- **Targeted**: Checks all code that adopts concurrency, including `Sendable` conformances228- **Complete**: Checks entire codebase (matches Swift 6 behavior)229230**Swift Package Manager**:231```swift232.target(233name: "MyTarget",234swiftSettings: [235.enableExperimentalFeature("StrictConcurrency=targeted")236]237)238```239240**Strategy**: Start with Minimal → Targeted → Complete, fixing errors at each level.241242### 6. Add Sendable Conformances243244Even if the compiler doesn't complain, add `Sendable` to types that will cross isolation domains:245246```swift247// ✅ Prepare for future use248struct Configuration: Sendable {249let apiKey: String250let timeout: TimeInterval251}252```253254This prevents warnings when the type is used in concurrent contexts later.255256### 7. Enable Approachable Concurrency (Swift 6.2+)257258**Xcode Build Settings**: Search for "Approachable Concurrency"259260Enables multiple upcoming features at once:261- `DisableOutwardActorInference`262- `GlobalActorIsolatedTypesUsability`263- `InferIsolatedConformances`264- `InferSendableFromCaptures`265- `NonisolatedNonsendingByDefault`266267**⚠️ Warning**: Don't just flip this switch for existing projects. Use migration tooling (see below) to migrate to each feature individually first.268269> **Course Deep Dive**: This topic is covered in detail in [Lesson 12.5: The Approachable Concurrency build setting (Updated for Swift 6.2)](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)270271### 8. Enable Upcoming Features272273**Xcode Build Settings**: Search for "Upcoming Feature"274275Enable features individually:276277**Swift Package Manager**:278```swift279.target(280name: "MyTarget",281swiftSettings: [282.enableUpcomingFeature("ExistentialAny"),283.enableUpcomingFeature("InferIsolatedConformances")284]285)286```287288Find feature keys in Swift Evolution proposals (e.g., SE-335 for `ExistentialAny`).289290### 9. Change to Swift 6 Language Mode291292**Xcode Build Settings**:293```294Swift Language Version = Swift 6295```296297**Swift Package Manager**:298```swift299// swift-tools-version: 6.0300```301302If you've completed all previous steps, you should have minimal new errors.303304> **Course Deep Dive**: This topic is covered in detail in [Lesson 12.4: Steps to migrate existing code to Swift 6 and Strict Concurrency Checking](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)305306---307308## Migration Tooling for Upcoming Features309310Swift 6.2+ includes **semi-automatic migration** for upcoming features.311312### Xcode Migration3133141. Go to Build Settings → Find the upcoming feature (e.g., "Require Existential any")3152. Set to **Migrate** (temporary setting)3163. Build the project3174. Warnings appear with **Apply** buttons3185. Click Apply for each warning319320**Example warning**:321```swift322// ⚠️ Use of protocol 'Error' as a type must be written 'any Error'323func fetchData() throws -> Data // Before324func fetchData() throws -> any Data // After applying fix325```326327### Package Migration328329Use the `swift package migrate` command:330331```bash332# Migrate all targets333swift package migrate --to-feature ExistentialAny334335# Migrate specific target336swift package migrate --target MyTarget --to-feature ExistentialAny337```338339**Output**:340```341> Applied 24 fix-its in 11 files (0.016s)342> Updating manifest343```344345The tool automatically:346- Applies all fix-its347- Updates `Package.swift` to enable the feature348349**Available migrations** (as of Swift 6.2):350- `ExistentialAny` (SE-335)351- `InferIsolatedConformances` (SE-470)352- More features will add migration support over time353354> **Course Deep Dive**: This topic is covered in detail in [Lesson 12.6: Migration tooling for upcoming Swift features](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)355356**Additional resource**: [Migration Tooling Video](https://youtu.be/FK9XFxSWZPg?si=2z_ybn1t1YCJow5k)357358---359360## Rewriting Closures to Async/Await361362### Using Xcode Refactoring363364Three refactoring options available:3653661. **Add Async Wrapper**: Wraps existing closure-based method (recommended first step)3672. **Add Async Alternative**: Rewrites method as async, keeps original3683. **Convert Function to Async**: Replaces method entirely369370**⚠️ Known Issue**: Refactoring can be unstable in Xcode. If you get "Connection interrupted" errors:371- Clean build folder372- Clear derived data373- Restart Xcode374- Simplify complex methods (shorthand if statements can cause failures)375376### Manual Rewriting Example377378**Before** (closure-based):379```swift380func fetchImage(urlRequest: URLRequest,381completion: @escaping @Sendable (Result<UIImage, Error>) -> Void) {382URLSession.shared.dataTask(with: urlRequest) { data, _, error in383do {384if let error = error { throw error }385guard let data = data, let image = UIImage(data: data) else {386throw ImageError.conversionFailed387}388completion(.success(image))389} catch {390completion(.failure(error))391}392}.resume()393}394```395396**After** (async/await):397```swift398func fetchImage(urlRequest: URLRequest) async throws -> UIImage {399let (data, _) = try await URLSession.shared.data(for: urlRequest)400guard let image = UIImage(data: data) else {401throw ImageError.conversionFailed402}403return image404}405```406407**Benefits**:408- Less code to maintain409- Easier to reason about410- No nested closures411- Automatic error propagation412413> **Course Deep Dive**: This topic is covered in detail in [Lesson 12.7: Techniques for rewriting closures to async/await syntax](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)414415---416417## Using @preconcurrency418419Suppresses `Sendable` warnings from modules you don't control.420421### When to Use422423```swift424// ⚠️ Third-party library doesn't support Swift Concurrency yet425@preconcurrency import SomeThirdPartyLibrary426427actor DataProcessor {428func process(_ data: LibraryType) { // No Sendable warning429// ...430}431}432```433434### Risks435436- **No compile-time safety**: You're responsible for ensuring thread safety437- **Hides real issues**: Library might not be thread-safe at all438- **Technical debt**: Easy to forget to revisit later439440### Best Practices4414421. **Don't use by default**: Only add when compiler suggests it4432. **Check for updates first**: Library might have a newer version with concurrency support4443. **Document why**: Add a comment explaining why it's needed4454. **Revisit regularly**: Set reminders to check if library has been updated446447```swift448// TODO: Remove @preconcurrency when SomeLibrary adds Sendable support449// Last checked: 2026-01-07 (version 2.3.0)450@preconcurrency import SomeLibrary451```452453The compiler will warn if `@preconcurrency` is unused:454```455'@preconcurrency' attribute on module 'SomeModule' is unused456```457458> **Course Deep Dive**: This topic is covered in detail in [Lesson 12.8: How and when to use @preconcurrency](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)459460---461462## Migrating from Combine/RxSwift463464### Observation Alternative465466Swift 6 will include **Transactional Observation** (SE-475):467468```swift469// Future API (not yet implemented)470let names = Observations { person.name }471472Task.detached {473for await name in names {474print("Name updated to: \(name)")475}476}477```478479**Current alternatives**:480- Use `@Observable` macro for SwiftUI481- Use `AsyncStream` for custom observation482- Consider [AsyncExtensions](https://github.com/sideeffect-io/AsyncExtensions) package483484### Debouncing Example485486**Combine**:487```swift488$searchQuery489.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)490.sink { [weak self] query in491self?.performSearch(query)492}493.store(in: &cancellables)494```495496**Swift Concurrency**:497```swift498func search(_ query: String) {499currentSearchTask?.cancel()500501currentSearchTask = Task {502do {503try await Task.sleep(for: .milliseconds(500))504performSearch(query)505} catch {506// Search was cancelled507}508}509}510```511512**SwiftUI Integration**:513```swift514struct SearchView: View {515@State private var searchQuery = ""516@State private var searcher = ArticleSearcher()517518var body: some View {519List(searcher.results) { result in520Text(result.title)521}522.searchable(text: $searchQuery)523.onChange(of: searchQuery) { _, newValue in524searcher.search(newValue)525}526}527}528```529530### Mindset Shift531532**Don't think in Combine pipelines**. Many problems are simpler without FRP:533534```swift535// ❌ Looking for AsyncSequence equivalent of complex Combine pipeline536somePublisher537.debounce(for: .seconds(0.5))538.removeDuplicates()539.flatMap { ... }540.sink { ... }541542// ✅ Rethink the problem with Swift Concurrency543Task {544var lastValue: String?545for await value in stream {546guard value != lastValue else { continue }547lastValue = value548try await Task.sleep(for: .seconds(0.5))549await process(value)550}551}552```553554**For complex operators**: Check [Swift Async Algorithms](https://github.com/apple/swift-async-algorithms) package.555556### ⚠️ Critical: Actor Isolation with Combine557558**Problem**: `sink` closures don't respect actor isolation at compile time.559560```swift561@MainActor562final class NotificationObserver {563private var cancellables: [AnyCancellable] = []564565init() {566NotificationCenter.default.publisher(for: .someNotification)567.sink { [weak self] _ in568self?.handleNotification() // ⚠️ May crash if posted from background569}570.store(in: &cancellables)571}572573private func handleNotification() {574// Expects to run on main actor575}576}577```578579**Why it crashes**: Notification observers run on the same thread as the poster. If posted from a background thread, the `@MainActor` method is called off the main actor.580581**Solutions**:5825831. **Migrate to Swift Concurrency** (recommended):584```swift585Task { [weak self] in586for await _ in NotificationCenter.default.notifications(named: .someNotification) {587await self?.handleNotification() // ✅ Compile-time safe588}589}590```5915922. **Use Task wrapper** (temporary):593```swift594.sink { [weak self] _ in595Task { @MainActor in596self?.handleNotification()597}598}599```600601> **Course Deep Dive**: This topic is covered in detail in [Lesson 12.9: Migrating away from Functional Reactive Programming like RxSwift or Combine](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)602603---604605## When to Use AsyncAlgorithms606607When migrating from Combine or RxSwift, you have multiple options for handling asynchronous patterns:608609### Use AsyncAlgorithms for:610611- **Time-based operations**: debounce, throttle, timers612- **Combining multiple async sequences**: merge, combineLatest, zip613- **Multi-consumer scenarios**: AsyncChannel for backpressure614- **Complex operator chains**: FRP-like patterns in Swift Concurrency615- **Specific operators**: removeDuplicates, chunks, adjacentPairs, compacted616617### Use Standard Library for:618619- **Bridging callbacks**: AsyncStream is sufficient620- **Simple iteration**: for await in sequence621- **Single-value operations**: async/await622- **Basic transformations**: map, filter, contains623624### Use SwiftUI for:625626- **UI observation**: @Observable macro627- **State management**: @State, @Published properties628- **User interactions**: onChange, onReceive modifiers629630> **See**: [async-algorithms.md](async-algorithms.md) for detailed AsyncAlgorithms usage examples.631632---633634## Real-World Migration Examples635636### Example: ArticleSearcher with AsyncAlgorithms637638**Before: Manual Debouncing**639640```swift641final class ArticleSearcher {642@MainActor private(set) var results: [Article] = []643private var currentSearchTask: Task<Void, Never>?644645func search(_ query: String) {646currentSearchTask?.cancel()647648currentSearchTask = Task {649do {650try await Task.sleep(for: .milliseconds(500))651await MainActor.run {652self.results = []653}654self.results = await APIClient.searchArticles(query)655} catch {656// Search was cancelled657}658}659}660}661662// SwiftUI integration663struct SearchView: View {664@State private var searchQuery = ""665@State private var searcher = ArticleSearcher()666667var body: some View {668List(searcher.results) { result in669Text(result.title)670}671.searchable(text: $searchQuery)672.onChange(of: searchQuery) { _, newValue in673searcher.search(newValue)674}675}676}677```678679**After: AsyncAlgorithms Debounce**680681```swift682import AsyncAlgorithms683684@Observable685final class ArticleSearcher {686@MainActor private(set) var results: [Article] = []687private var searchQueryContinuation: AsyncStream<String>.Continuation?688689private lazy var searchQueryStream: AsyncStream<String> = {690AsyncStream { continuation in691searchQueryContinuation = continuation692}693}()694695func search(_ query: String) {696searchQueryContinuation?.yield(query)697}698699func startDebouncedSearch() {700Task { @MainActor in701for await query in searchQueryStream.debounce(for: .milliseconds(500)) {702self.results = []703self.results = await APIClient.searchArticles(query)704}705}706}707}708709// SwiftUI integration710struct SearchView: View {711@State private var searchQuery = ""712@State private var searcher = ArticleSearcher()713714var body: some View {715List(searcher.results) { result in716Text(result.title)717}718.searchable(text: $searchQuery)719.onChange(of: searchQuery) { _, newValue in720searcher.search(newValue)721}722.onAppear {723searcher.startDebouncedSearch()724}725}726}727```728729**Benefits of using AsyncAlgorithms**:730- Automatic cancellation when new values arrive731- Backpressure handling (producer respects consumer pace)732- Cleaner code than manual Task.sleep management733- No need to track and cancel tasks manually734735### Example: Notification Stream Migration736737**Before: Combine Publisher**738739```swift740import Combine741742final class NotificationObserver: ObservableObject {743@Published private(set) var notifications: [AppNotification] = []744private var cancellables = Set<AnyCancellable>()745746init() {747NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)748.compactMap { notification in749notification.object as? AppNotification750}751.receive(on: DispatchQueue.main)752.assign(to: &$notifications)753}754}755```756757**After: Standard Library Notifications**758759```swift760@Observable761final class NotificationObserver {762@MainActor private(set) var notifications: [AppNotification] = []763764func startObserving() {765Task {766for await notification in NotificationCenter.default.notifications(named: UIApplication.didBecomeActiveNotification) {767if let appNotification = notification.object as? AppNotification {768notifications.append(appNotification)769}770}771}772}773}774```775776**When to use each approach**:777- Use `notifications(named:)` for standard system notifications778- Use `AsyncChannel` for custom multi-consumer notification scenarios779- Use `@Observable` + SwiftUI for UI state updates780781### Example: Multi-Source Data Loading782783**Before: Combine Merge**784785```swift786import Combine787788final class MultiSourceLoader: ObservableObject {789@Published private(set) var items: [Item] = []790private var cancellables = Set<AnyCancellable>()791792func loadFromAllSources() {793let source1 = APIClient.fetchItems(from: .source1)794let source2 = APIClient.fetchItems(from: .source2)795let source3 = APIClient.fetchItems(from: .source3)796797Publishers.Merge3(source1, source2, source3)798.flatMap { items in799Just(items)800.delay(for: .seconds(0.1), scheduler: DispatchQueue.main)801}802.scan([]) { accumulated, new in803accumulated + new804}805.receive(on: DispatchQueue.main)806.assign(to: &$items)807.store(in: &cancellables)808}809}810```811812**After: AsyncAlgorithms Merge + TaskGroup**813814```swift815import AsyncAlgorithms816817@Observable818final class MultiSourceLoader {819@MainActor private(set) var items: [Item] = []820821func loadFromAllSources() async {822let sources = [823APIClient.fetchItems(from: .source1),824APIClient.fetchItems(from: .source2),825APIClient.fetchItems(from: .source3)826]827828Task { @MainActor in829for await stream in sources.map { $0.values }.merge() {830for await newItems in stream {831self.items.append(contentsOf: newItems)832}833}834}835}836837// Alternative: Using TaskGroup for parallel execution838func loadFromAllSourcesParallel() async {839await withTaskGroup(of: [Item].self) { group in840group.addTask {841await APIClient.fetchItems(from: .source1)842}843group.addTask {844await APIClient.fetchItems(from: .source2)845}846group.addTask {847await APIClient.fetchItems(from: .source3)848}849850for await newItems in group {851await MainActor.run {852self.items.append(contentsOf: newItems)853}854}855}856}857}858```859860**Key differences**:861- Combine `merge()` combines publishers; AsyncAlgorithms `merge()` combines sequences862- For parallel execution, use `TaskGroup` instead of `flatMap`863- State updates can use `@MainActor` instead of `.receive(on:)`864865---866867## Anti-Patterns to Avoid868869### ❌ Don't Use Task.sleep for Debouncing870871```swift872// ❌ Bad: Manual debouncing without backpressure873func search(_ query: String) {874Task {875try? await Task.sleep(for: .milliseconds(500))876await performSearch(query)877}878}879```880881**Problem**: Every keystroke spawns a new task. If user types fast, multiple tasks execute simultaneously after 500ms, causing out-of-order results and wasted API calls.882883**Solution**: Use `debounce()` from AsyncAlgorithms for automatic backpressure and cancellation.884885### ❌ Don't Manually Combine Values886887```swift888// ❌ Bad: Manual combination without operator889actor FormValidator {890private var currentUsername: String = ""891private var currentEmail: String = ""892private var currentPassword: String = ""893894func updateUsername(_ username: String) {895currentUsername = username896checkForm()897}898899func updateEmail(_ email: String) {900currentEmail = email901checkForm()902}903904func updatePassword(_ password: String) {905currentPassword = password906checkForm()907}908909private func checkForm() {910let state = validate(911username: currentUsername,912email: currentEmail,913password: currentPassword914)915// Update UI or emit validation state916}917}918```919920**Problems**:921- More state management922- Boilerplate code for each field923- Harder to add new fields924- No stream composition benefits925926**Solution**: Use `combineLatest()` for cleaner, composable validation.927928### ❌ Don't Share Streams Without AsyncChannel929930```swift931// ❌ Bad: Multiple consumers sharing same stream932let stream = AsyncStream<Int> { continuation in933for i in 1...10 {934continuation.yield(i)935}936continuation.finish()937}938939Task {940for await value in stream {941print("Consumer 1: \(value)")942}943}944945Task {946for await value in stream {947print("Consumer 2: \(value)")948}949}950```951952**Problem**: Values are split between consumers unpredictably. Each value goes to only one consumer.953954**Solution**: Use `AsyncChannel` for true multi-consumer scenarios with backpressure.955956---957958---959960## Concurrency-Safe Notifications (iOS 26+)961962Swift 6.2 introduces **typed, thread-safe notifications**.963964### MainActorMessage965966For notifications that should be delivered on the main actor:967968```swift969// Old way970NotificationCenter.default.addObserver(971forName: UIApplication.didBecomeActiveNotification,972object: nil,973queue: .main974) { [weak self] _ in975self?.handleDidBecomeActive() // ⚠️ Concurrency warning976}977978// New way (iOS 26+)979token = NotificationCenter.default.addObserver(980of: UIApplication.self,981for: .didBecomeActive982) { [weak self] message in983self?.handleDidBecomeActive() // ✅ No warning, guaranteed main actor984}985```986987**Key difference**: Observer closure is guaranteed to run on `@MainActor`.988989### AsyncMessage990991For notifications delivered asynchronously on arbitrary isolation:992993```swift994struct RecentBuildsChangedMessage: NotificationCenter.AsyncMessage {995typealias Subject = [RecentBuild]996let recentBuilds: Subject997}998999// Enable static member lookup1000extension NotificationCenter.MessageIdentifier1001where Self == NotificationCenter.BaseMessageIdentifier<RecentBuildsChangedMessage> {1002static var recentBuildsChanged: NotificationCenter.BaseMessageIdentifier<RecentBuildsChangedMessage> {1003.init()1004}1005}1006```10071008**Posting**:1009```swift1010let builds = [RecentBuild(appName: "Stock Analyzer")]1011let message = RecentBuildsChangedMessage(recentBuilds: builds)1012NotificationCenter.default.post(message)1013```10141015**Observing**:1016```swift1017// Old way: Unsafe casting1018NotificationCenter.default.addObserver(forName: .recentBuildsChanged, object: nil, queue: nil) { notification in1019guard let builds = notification.object as? [RecentBuild] else { return }1020handleBuilds(builds)1021}10221023// New way: Strongly typed, thread-safe1024token = NotificationCenter.default.addObserver(1025of: [RecentBuild].self,1026for: .recentBuildsChanged1027) { message in1028handleBuilds(message.recentBuilds) // ✅ Direct access, no casting1029}1030```10311032**Benefits**:1033- Strongly typed (no `Any` casting)1034- Compile-time thread safety1035- Clear isolation guarantees10361037> **Course Deep Dive**: This topic is covered in detail in [Lesson 12.10: Migrating to concurrency-safe notifications](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)10381039---10401041## Common Challenges10421043### "It's Too Much Work"10441045Break it down:1046- Migrate one package at a time1047- Use 30-minute daily sessions1048- Create checkpoints with small PRs1049- Celebrate incremental progress10501051### "My Team Isn't Ready"10521053Start small:1054- Enable Swift 6 for new files only1055- Make new types `Sendable` by default1056- Share learnings in team meetings1057- Pair program on tricky migrations10581059### "Dependencies Aren't Ready"10601061Options:1062- Update to latest versions first1063- Use `@preconcurrency` temporarily1064- Contribute fixes to open-source dependencies1065- Wrap third-party APIs in your own concurrency-safe layer10661067### "I Keep Going in Circles"10681069This is the "concurrency rabbit hole":1070- Take breaks and revisit later1071- Temporarily disable strict checking to make progress elsewhere1072- Focus on one module at a time1073- Don't try to fix everything at once10741075> **Course Deep Dive**: This topic is covered in detail in [Lesson 12.11: Frequently Asked Questions (FAQ) around Swift 6 Migrations](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)10761077---10781079## Common Mistakes Agents Make10801081- **Blanket `@MainActor`**: Do not slap `@MainActor` on everything to silence errors. Ask whether the code truly needs main-actor isolation.1082- **Mixing migration with unrelated refactors**: Focus solely on concurrency changes. Architectural improvements belong in separate PRs.1083- **Using `@unchecked Sendable` as a first response**: Prefer immutable value types or actors. Reserve escape hatches for documented, temporary exceptions.1084- **Giving pre-Swift 6.2 execution advice without checking the active feature set**: `nonisolated async` behavior depends on whether `NonisolatedNonsendingByDefault` is enabled.1085- **Using Approachable Concurrency without migrating feature-by-feature first**: Enable individual upcoming features before the full bundle to understand each change's impact.10861087## Summary10881089Migration to Swift 6 is a journey, not a sprint:109010911. **Start small**: Find isolated code with minimal dependencies10922. **Be incremental**: Use the three strict concurrency levels (Minimal → Targeted → Complete)10933. **Use tooling**: Leverage Xcode refactoring and `swift package migrate`10944. **Create checkpoints**: Small, focused PRs that can be merged quickly10955. **Stay positive**: The concurrency rabbit hole is real, but manageable with the right habits10966. **Think differently**: Let go of the threading mindset; trust the compiler10971098The result is **compile-time thread safety**, more maintainable code, and a future-proof codebase.10991100**Additional resources**:1101- [Approachable Concurrency Video](https://youtu.be/y_Qc8cT-O_g?si=y4C1XQDGtyIOLW81)1102- [Migration Tooling Video](https://youtu.be/FK9XFxSWZPg?si=2z_ybn1t1YCJow5k)1103- [Swift Concurrency Course](https://www.swiftconcurrencycourse.com) for in-depth migration strategies11041105