---
title: "skill by agenticluke · skilld"
canonical_url: "https://skilld.dev/gh/agenticluke/swift-approachable-concurrency-plus"
meta:
  description: "Use Swift 6.2 approachable concurrency. Keep code on the caller's actor by default. Use @concurrent for clear background work. Use isolated… From agenticluke/swift-approachable-concurrency-plus."
  "og:description": "Use Swift 6.2 approachable concurrency. Keep code on the caller's actor by default. Use @concurrent for clear background work. Use isolated… From agenticluke/swift-approachable-concurrency-plus."
  "og:title": "skill by agenticluke"
  "twitter:description": "Use Swift 6.2 approachable concurrency. Keep code on the caller's actor by default. Use @concurrent for clear background work. Use isolated… From agenticluke/swift-approachable-concurrency-plus."
  "twitter:title": "skill by agenticluke"
---

`

[All skills](https://skilld.dev/skills)

[![agenticluke avatar](https://skilld.dev/_img/avatar?url=https%3A%2F%2Fgithub.com%2Fagenticluke.png%3Fsize%3D96)](https://skilld.dev/gh/agenticluke)

# **/skill**

[@ed5b77a](https://github.com/agenticluke/swift-approachable-concurrency-plus/commit/ed5b77ac75947ed10a10e6bb8c9c2055d4c5faef "Your agent reads SKILL.md at commit ed5b77a")

by [agenticluke](https://skilld.dev/gh/agenticluke)· [agenticluke](https://skilld.dev/gh/agenticluke)/ [swift-approachable-concurrency-plus](https://skilld.dev/gh/agenticluke/swift-approachable-concurrency-plus)

Use Swift 6.2 approachable concurrency. Keep code on the caller's actor by default. Use @concurrent for clear background work. Use isolated conformances for MainActor types.

- 1 file
- 9.7 KB
- Updated 2 weeks ago
- [GitHub](https://github.com/agenticluke/swift-approachable-concurrency-plus/blob/ed5b77ac75947ed10a10e6bb8c9c2055d4c5faef/skill/SKILL.md "View SKILL.md on GitHub")

## SKILL.md

9.7 KB

**≈45** tokens always: the name and description. **≈2.4k** when used: this file.

## Swift 6.2 Approachable Concurrency

Use this skill to write or move code to the Swift 6.2 concurrency model.

The main rule is simple:

- Keep code on the caller's actor by default.
- Use `@concurrent` only for work that should run at the same time.
- Use `@MainActor` for UI state and shared app state.
- Use an isolated conformance when a protocol must follow an actor.

This model helps the compiler find data races before the app runs.

### Use This Skill When

Use these steps when:

- Moving a Swift 5.x, 6.0, or 6.1 project to Swift 6.2
- Fixing compiler errors about sending values or data races
- Building an app around `MainActor`
- Moving slow CPU work off the main actor
- Making a `MainActor` type follow a protocol
- Turning on approachable concurrency in Xcode 26

### Check the Build Settings First

Swift 6.2 features can depend on target settings. Before changing code, check the settings for each app, test, and package target.

Look for these Swift compiler options:

- Default actor isolation
- Nonisolated async functions that stay on the caller's actor
- Strict concurrency checks
- Swift language mode

For an app target, `MainActor` is often a good default actor. A library may need a different default because its code can be called from many actors.

Do not assume all targets use the same settings.

### Async Does Not Mean Background Work

An `async` function can pause. It does not always move work to a background thread.

With the Swift 6.2 setting for caller actor isolation, a plain async function stays on the caller's actor unless its code asks to run elsewhere.

```
@MainActor
final class StickerModel {
    let photoProcessor = PhotoProcessor()

    func extractSticker(
        from item: PhotosPickerItem
    ) async throws -> Sticker? {
        guard let data = try await item.loadTransferable(
            type: Data.self
        ) else {
            return nil
        }

        return await photoProcessor.extractSticker(
            data: data,
            id: item.itemIdentifier
        )
    }
}
```

This can prevent errors caused by sending non-`Sendable` state to another actor.

It does not make slow work safe for the main actor. A long loop can still freeze the UI.

### Use `@MainActor` for UI and Shared App State

Put UI state and app-wide mutable state on `MainActor`.

```
@MainActor
final class StickerLibrary {
    static let shared = StickerLibrary()

    private(set) var stickers: [Sticker] = []

    func add(_ sticker: Sticker) {
        stickers.append(sticker)
    }
}
```

Code outside `MainActor` must use `await` to read or change this state.

```
await StickerLibrary.shared.add(sticker)
```

A global actor protects access. It does not make each stored value `Sendable`.

### Default `MainActor` Isolation

Swift 6.2 can make `MainActor` the default isolation for a target.

```
final class StickerModel {
    var selection: [PhotosPickerItem] = []
}
```

In a target with default `MainActor` isolation, this type is treated as main actor code unless it says otherwise.

This setting is useful for:

- UI apps
- Scripts
- Other executable targets

Use care in library targets. A library often needs types that work from many actors.

Do not rely on this setting when code is shared across targets. Add `@MainActor` when the rule must be clear in the source.

### Use an Isolated Protocol Conformance

A `MainActor` type may follow a protocol only on `MainActor`.

```
protocol Exportable {
    func export()
}

@MainActor
final class StickerModel {
    let photoProcessor = PhotoProcessor()
}

extension StickerModel: @MainActor Exportable {
    func export() {
        photoProcessor.exportAsPNG()
    }
}
```

Use the value from a matching actor:

```
@MainActor
struct ImageExporter {
    private var items: [any Exportable] = []

    mutating func add(_ item: StickerModel) {
        items.append(item)
    }
}
```

A nonisolated caller cannot use this conformance without moving to `MainActor`.

```
func exportOnMain(_ item: StickerModel) async {
    await MainActor.run {
        item.export()
    }
}
```

Check generic code and stored protocol values. They must keep the same actor rule.

### Use `@concurrent` for Slow CPU Work

Use `@concurrent` when work should run away from the caller's actor.

Good uses include:

- Image changes
- File compression
- Audio work
- Large math tasks
- Parsing a large block of data

The function should use values that are safe to send. Its result should also be safe to send.

```
struct Sticker: Sendable {
    let pixels: [UInt8]
}

enum StickerProcessor {
    @concurrent
    static func findSubject(
        in pixels: [UInt8]
    ) async -> Sticker {
        let output = runSlowImageWork(pixels)
        return Sticker(pixels: output)
    }
}
```

Call it with `await`:

```
let sticker = await StickerProcessor.findSubject(
    in: imagePixels
)
```

`@concurrent` does not make shared mutable state safe. Do not read or write a normal class property from several calls at once.

Use an actor for a cache:

```
actor StickerCache {
    private var values: [String: Sticker] = [:]

    func value(for id: String) -> Sticker? {
        values[id]
    }

    func store(_ sticker: Sticker, for id: String) {
        values[id] = sticker
    }
}
```

### Full Usage Example

This example keeps UI state on `MainActor`, runs image work with `@concurrent`, and protects the cache with an actor.

```
struct Sticker: Sendable {
    let pixels: [UInt8]
}

actor StickerCache {
    private var values: [String: Sticker] = [:]

    func value(for id: String) -> Sticker? {
        values[id]
    }

    func store(_ sticker: Sticker, for id: String) {
        values[id] = sticker
    }
}

enum StickerProcessor {
    @concurrent
    static func process(_ pixels: [UInt8]) async -> Sticker {
        let output = runSlowImageWork(pixels)
        return Sticker(pixels: output)
    }
}

@MainActor
final class StickerViewModel {
    private let cache = StickerCache()
    private(set) var currentSticker: Sticker?

    func load(id: String, pixels: [UInt8]) async {
        if let saved = await cache.value(for: id) {
            currentSticker = saved
            return
        }

        let sticker = await StickerProcessor.process(pixels)
        await cache.store(sticker, for: id)
        currentSticker = sticker
    }
}
```

### How to Add `@concurrent`

Before adding it:

1. Find a slow CPU task with a profiler.
2. Make the input safe to send.
3. Make the result safe to send.
4. Remove access to unsafe shared state.
5. Mark the async function with `@concurrent`.
6. Add `await` at each call.
7. Test cancellation, errors, and repeated calls.

A type does not always need to be marked `nonisolated`. Use `nonisolated` only when the type or member must not use the target's default actor.

### Migration Steps

1. Move to Swift 6.2 one target at a time.
2. Turn on strict concurrency checks.
3. Pick the target's default actor isolation.
4. Fix shared mutable state with `MainActor` or another actor.
5. Use isolated conformances for actor-bound types.
6. Keep normal async work on the caller's actor.
7. Add `@concurrent` only to proven slow CPU work.
8. Check that sent inputs and results are `Sendable`.
9. Build and test all app, test, and package targets.
10. Run Thread Sanitizer tests when useful.

### Edge Cases

#### Long Work Can Still Block the UI

An async function on `MainActor` can freeze the UI if it does long work without a pause. Move that CPU work to an `@concurrent` function.

#### `@concurrent` Is Not a Lock

It does not protect global variables, static variables, caches, or class fields. Use an actor, a global actor, or another safe design.

#### `Sendable` Does Not Protect Mutable State

A value marked `Sendable` must truly be safe to move between actors. Do not add unchecked conformance only to silence the compiler.

#### Cancellation Must Be Checked

Long loops should stop when the task is cancelled.

```
@concurrent
func processItems(_ items: [Item]) async throws -> [Result] {
    var output: [Result] = []

    for item in items {
        try Task.checkCancellation()
        output.append(process(item))
    }

    return output
}
```

#### Actor Reentry Can Change State

An actor method may pause at `await`. Other work can then enter the actor and change its state. Check key state again after an `await` when needed.

#### Old APIs May Need a Bridge

Some older APIs use callbacks or `DispatchQueue`. Wrap them with async tools when needed, but keep the actor rule clear. Do not remove a queue until you know what state it protects.

#### Tests May Use Different Isolation

A test target may not use the app target's default actor. Add `@MainActor` to a test or test type when it reads main actor state.

### Good Rules

- Start with simple actor-bound code.
- Keep UI state on `MainActor`.
- Use actors for shared mutable state.
- Use `@concurrent` only for slow CPU work.
- Measure before moving work.
- Make sent values `Sendable`.
- Check cancellation in long tasks.
- Migrate one target at a time.
- Treat compiler race errors as real design warnings.

### Avoid These Patterns

- Adding `@concurrent` to every async function
- Using `nonisolated` only to hide an error
- Adding `@unchecked Sendable` without a full safety review
- Sharing a mutable class across concurrent calls
- Assuming `async` means background work
- Assuming `@concurrent` makes state thread-safe
- Using `Task.detached` when normal actor rules will work
- Keeping old queue code without knowing what it protects
- Reading main actor state from nonisolated code without `await`
- Relying on target defaults in code shared by many targets

Source: [SKILL.md on GitHub](https://github.com/agenticluke/swift-approachable-concurrency-plus/blob/ed5b77ac75947ed10a10e6bb8c9c2055d4c5faef/skill/SKILL.md)

## Third-party checks

No third-party reports yet.

## Provenance

[Signed by skilld at ed5b77a.](https://github.com/agenticluke/swift-approachable-concurrency-plus/commit/ed5b77ac75947ed10a10e6bb8c9c2055d4c5faef "ed5b77ac75947ed10a10e6bb8c9c2055d4c5faef") This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 weeks ago.

Activeupdated 2 weeks ago

## README badge

![README badge for agenticluke/swift-approachable-concurrency-plus](https://skilld.dev/b/agenticluke/swift-approachable-concurrency-plus?theme=light&label=0)

## Related skills

-
-
-
-
-
-