Back to skills
extension
Category: Development & EngineeringNo API key required

swift-lang

Swift 6.2 language patterns and critical corrections for LLM code generation. Covers the concurrency paradigm shift (default MainActor isolation, @concurrent for background work, isolated conformances, nonisolated opt-out), @Observable vs ObservableObject, structured concurrency, modern error handling, property wrappers, InlineArray, Span, and value generics. Use whenever generating Swift code, especially async/concurrent code, class/struct design, or performance-critical paths. This skill prevents the most common LLM mistakes in Swift code generation.

personAuthor: jakexiaohubgithub

Swift 6.2 Language Patterns

Corrective guide for Swift code generation. Swift 6.2 fundamentally changed the concurrency model — most LLM training data reflects the old model.

Critical Constraints

  • ❌ DO NOT use ObservableObject + @Published for macOS 14+ / iOS 17+ → ✅ Use @Observable macro (import Observation)
  • ❌ DO NOT use @StateObject → ✅ Use @State with @Observable classes
  • ❌ DO NOT use @ObservedObject → ✅ Use @Bindable for bindings, or just pass the object
  • ❌ DO NOT use @EnvironmentObject → ✅ Use @Environment with custom key
  • ❌ DO NOT assume async functions always hop off the actor → ✅ In Swift 6.2, async functions run on the caller's actor by default
  • ❌ DO NOT use Task.detached to get background execution → ✅ Use @concurrent attribute on the function
  • ❌ DO NOT add @Sendable to everything to silence warnings → ✅ Use default MainActor isolation mode, opt out with nonisolated
  • ❌ DO NOT use DispatchQueue.main.async for main thread work → ✅ Use @MainActor or MainActor.run {}
  • ❌ DO NOT use DispatchQueue.global().async for background work → ✅ Use @concurrent functions with await
  • ❌ DO NOT create NavigationView → ✅ Use NavigationSplitView or NavigationStack

Decision Tree: Concurrency Model

Starting a new project or file?
├── Is this an app/script/executable target?
│   └── YES → Enable default MainActor isolation (recommended)
│         └── All code is @MainActor by default
│         └── No data-race errors for single-threaded code
│         └── Use nonisolated + @concurrent for background work
├── Is this a library/framework?
│   └── YES → Use explicit annotations (@MainActor, nonisolated, Sendable)
│
Need background execution?
├── Use @concurrent on the function
├── Mark the containing type as nonisolated
├── Add async to the function signature
└── Callers must await the result

Decision Tree: Observable Pattern

Need observable state in a view?
├── macOS 14+ / iOS 17+ target?
│   ├── YES → Use @Observable macro
│   │   ├── View owns it? → @State private var model = MyModel()
│   │   ├── View receives it? → just use the parameter (no wrapper needed)
│   │   ├── Need binding? → @Bindable var model
│   │   └── Environment? → @Environment(MyModel.self) var model
│   └── NO (older targets) → Use ObservableObject + @Published
│       ├── View owns it? → @StateObject
│       ├── View receives it? → @ObservedObject
│       └── Environment? → @EnvironmentObject

Verified Patterns

See references/concurrency.md for comprehensive Swift 6.2 concurrency patterns. See references/performance.md for InlineArray, Span, and memory optimization.

Pattern: @Observable Class (Modern)

<!-- Verified: macOS 14+, Swift 5.9+, Xcode 15+ -->
import SwiftUI
import Observation

@Observable
class StickerModel {
    var stickers: [Sticker] = []
    var isLoading = false

    func loadStickers() async {
        isLoading = true
        stickers = await StickerService.fetch()
        isLoading = false
    }
}

struct StickerListView: View {
    @State private var model = StickerModel()  // NOT @StateObject

    var body: some View {
        List(model.stickers) { sticker in
            StickerRow(sticker: sticker)
        }
        .task { await model.loadStickers() }
    }
}

// Passing to child — no wrapper needed
struct StickerRow: View {
    var sticker: Sticker  // plain property, NOT @ObservedObject
    var body: some View {
        Text(sticker.name)
    }
}

// When child needs to mutate
struct StickerEditor: View {
    @Bindable var model: StickerModel  // NOT @ObservedObject
    var body: some View {
        TextField("Name", text: $model.stickers[0].name)
    }
}

Pattern: Environment with @Observable

<!-- Verified: macOS 14+, Swift 5.9+ -->
import SwiftUI

// Define environment key
struct AppModelKey: EnvironmentKey {
    static let defaultValue = AppModel()
}

extension EnvironmentValues {
    var appModel: AppModel {
        get { self[AppModelKey.self] }
        set { self[AppModelKey.self] = newValue }
    }
}

// Inject
@main
struct MyApp: App {
    @State private var appModel = AppModel()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.appModel, appModel)
        }
    }
}

// Consume
struct ContentView: View {
    @Environment(\.appModel) private var appModel
    var body: some View { /* use appModel */ }
}

Pattern: Default MainActor Isolation (Swift 6.2)

<!-- Verified: Swift 6.2, Xcode 26 -->
// With default MainActor isolation enabled in build settings,
// everything is @MainActor by default. No annotations needed.

final class StickerLibrary {
    static let shared = StickerLibrary()  // Safe — implicitly @MainActor
    var stickers: [Sticker] = []           // Safe — on main actor
}

final class StickerModel {
    let photoProcessor = PhotoProcessor()

    func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
        guard let data = try await item.loadTransferable(type: Data.self) else {
            return nil
        }
        // No data-race error in Swift 6.2 — runs on caller's actor
        return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
    }
}

Pattern: @concurrent for Background Work

<!-- Verified: Swift 6.2, Xcode 26 -->
// To explicitly run work OFF the main actor, use @concurrent
nonisolated struct PhotoProcessor {
    @concurrent
    func process(data: Data) async -> ProcessedPhoto? {
        // This runs on the concurrent thread pool, NOT the main actor
        // Heavy image processing here
        return ProcessedPhoto(data: data)
    }
}

// Caller — must await
let processor = PhotoProcessor()
let result = await processor.process(data: imageData)

Pattern: Isolated Conformances

<!-- Verified: Swift 6.2, Xcode 26 -->
protocol Exportable {
    func export()
}

// Mark the conformance as @MainActor — safe because compiler
// ensures it's only used on the main actor
extension StickerModel: @MainActor Exportable {
    func export() {
        photoProcessor.exportAsPNG()  // Can access MainActor state
    }
}

// This works — ImageExporter is also on MainActor
@MainActor
struct ImageExporter {
    var items: [any Exportable]
    mutating func add(_ item: StickerModel) {
        items.append(item)  // OK — same actor isolation
    }
}

Common Mistakes & Fixes

| Mistake | Fix | |---------|-----| | class MyModel: ObservableObject with @Published | @Observable class MyModel — remove @Published, remove protocol | | @StateObject var model = MyModel() | @State private var model = MyModel() | | @ObservedObject var model: MyModel | var model: MyModel (or @Bindable for bindings) | | @EnvironmentObject var model: MyModel | @Environment(MyModel.self) var model or custom key | | Task.detached { await heavy() } | Mark heavy() as @concurrent nonisolated | | DispatchQueue.global().async { } | @concurrent func doWork() async { } | | static let shared causes Sendable warning | Enable default MainActor isolation, or add @MainActor | | Adding @Sendable everywhere | Use actor isolation instead — @MainActor or explicit actors | | await MainActor.run { } inside already-MainActor code | Just call directly — already on MainActor |

Build Settings for Swift 6.2

Enable approachable concurrency in Xcode Build Settings → Swift Compiler - Concurrency:

  • Default Actor Isolation: MainActor (infer @MainActor by default)
  • Strict Concurrency Checking: complete

Or in Package.swift:

.target(
    name: "MyTarget",
    swiftSettings: [
        .defaultIsolation(MainActor.self)
    ]
)

References