Skip to content
Back to skills

353 Concurrency A912254c

ASecurity

| Practice | Description | |----------|-------------| | @ModelActor for background work | Proper thread isolation for SwiftData | | PersistentIdentifier for cross-actor | Model objects are NOT Sendable | | Sendable DTOs for data exchange | Create separate types for transfer | | Task.detached for background | Ensures actor runs off main thread | | Explicit `save()` in Task | Autosave may not execute in time | | @Query for display, @ModelActor for mutations | Separate concerns |

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
code-qualitygoswift

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 353-concurrency_a912254c --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 353 Concurrency A912254c?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 353 Concurrency A912254c
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-353-concurrency-a912254c/badge)](https://www.skillsdirectory.com/skills/tools-only-353-concurrency-a912254c)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# SwiftData Concurrency

## Best Practices

| Practice | Description |
|----------|-------------|
| @ModelActor for background work | Proper thread isolation for SwiftData |
| PersistentIdentifier for cross-actor | Model objects are NOT Sendable |
| Sendable DTOs for data exchange | Create separate types for transfer |
| Task.detached for background | Ensures actor runs off main thread |
| Explicit `save()` in Task | Autosave may not execute in time |
| @Query for display, @ModelActor for mutations | Separate concerns |

## @ModelActor Pattern

```swift
@ModelActor
actor DataHandler {
    func importItems(_ data: [ImportData]) throws {
        for item in data {
            modelContext.insert(Item(name: item.name))
        }
        try modelContext.save()
    }

    func updateItem(id: PersistentIdentifier, name: String) throws {
        guard let item = self[id, as: Item.self] else { return }
        item.name = name
        try modelContext.save()
    }
}
```

## Sendable DTO Pattern

```swift
struct ItemDTO: Sendable, Identifiable {
    let id: PersistentIdentifier
    let name: String
    let timestamp: Date
}

@ModelActor
actor DataService {
    func fetchItems() throws -> [ItemDTO] {
        try modelContext.fetch(FetchDescriptor<Item>())
            .map { ItemDTO(id: $0.persistentModelID, name: $0.name, timestamp: $0.timestamp) }
    }
}
```

## Background Actor Creation

```swift
// GOOD: Ensures background execution
Task.detached {
    let handler = DataHandler(modelContainer: container)
    try await handler.importLargeDataset(data)
}

// Factory method alternative
@ModelActor
actor DataService {
    static nonisolated func createBackground(container: ModelContainer) async -> DataService {
        await Task.detached { DataService(modelContainer: container) }.value
    }
}
```

## Critical Anti-Patterns

### Passing Model Objects Between Actors

```swift
// BAD: Model objects are not Sendable
func getItem(id: UUID) throws -> Item {  // Crash or undefined behavior
    try modelContext.fetch(...).first!
}
let item = try await dataHandler.getItem(id: someId)
item.name = "New"  // Wrong thread!

// GOOD: Return DTO or use identifier
func getItemDTO(id: UUID) throws -> ItemDTO { ... }
func updateItem(id: PersistentIdentifier, name: String) throws { ... }
```

### Creating Actor on Main Thread

```swift
// BAD: Actor runs on main thread
@MainActor
func setup() {
    let handler = DataHandler(modelContainer: container)
}

// GOOD: Create off main actor
func setup() async {
    await Task.detached {
        let handler = DataHandler(modelContainer: container)
    }.value
}
```

### Single Actor Bottleneck

```swift
// BAD: All operations serialize
Task { try await sharedHandler.heavyImport1() }
Task { try await sharedHandler.heavyImport2() }  // Waits for import1!

// GOOD: Separate actors for independent work
Task.detached {
    let handler1 = DataHandler(modelContainer: container)
    try await handler1.heavyImport1()
}
Task.detached {
    let handler2 = DataHandler(modelContainer: container)
    try await handler2.heavyImport2()
}
```

### Modifying After Actor Boundary

```swift
// BAD: Retaining models from background
let items = try await backgroundActor.fetchAllItems()  // [Item]
items[0].name = "Changed"  // CRASH - wrong context!

// GOOD: Use identifiers to load locally
let identifier = try await backgroundActor.getItemIdentifier()
await MainActor.run {
    let item = mainContext.model(for: identifier) as? Item
    item?.name = "Changed"
}
```

### Missing @MainActor on Observable

```swift
// BAD: UI updates may happen off main thread
@Observable
class ViewModel {
    var items: [Item] = []
    func load() async {
        items = await dataService.fetchItems()  // May update from background
    }
}

// GOOD: Explicit main actor isolation
@Observable @MainActor
class ViewModel {
    var items: [Item] = []
}
```

## Review Questions

- [ ] Is @ModelActor used for heavy data operations?
- [ ] Are model objects passed between actors? (They shouldn't be)
- [ ] Is Task.detached used for background actor creation?
- [ ] Is explicit `save()` called in Task contexts?
- [ ] Are ViewModels marked @Observable @MainActor?
- [ ] Are Sendable DTOs used for cross-actor data?
- [ ] Could a single actor become a bottleneck?
- [ ] Are PersistentIdentifiers used for cross-context references?

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.