Swift Modern Architecture Skill · git:20251022.4c2260c · 2025-10-22 · sha256 e9a563193eafc4cb

Swift Modern Architecture Skill git:20251022.4c2260cA

Immutable. This exact content is served forever at /api/v1/blob/e9a563193eafc4cb.

---
name: swift-modern-architecture-skill
description: Guide for building iOS apps using Swift 6, iOS 18+, SwiftUI, SwiftData, and modern concurrency patterns. Use when writing Swift/iOS code, designing app architecture, or modernizing legacy patterns. Prevents outdated patterns like Core Data, ObservableObject, DispatchQueue, and NavigationView.
---

# Swift Modern Architecture Skill

Build iOS apps using Swift 6 and iOS 18+ best practices. This skill ensures code uses modern patterns: SwiftData (not Core Data), Observation framework (not Combine), Swift concurrency (not GCD), and current SwiftUI APIs.

## Core Principles

### 1. Swift 6 Concurrency First
Always use Swift concurrency (`async/await`, `actor`, `@MainActor`) instead of GCD or completion handlers. Use structured concurrency (`TaskGroup`, `async let`) over unstructured tasks.

### 2. Observation Framework Over Combine
Use `@Observable` macro for state management instead of `ObservableObject` with `@Published`. The Observation framework is more efficient and has cleaner syntax.

### 3. SwiftData Over Core Data
For new projects, always use SwiftData with `@Model` and `@Query`. SwiftData provides simpler APIs while maintaining Core Data's power.

### 4. Modern SwiftUI APIs
Use `NavigationStack` (not `NavigationView`), `@Entry` for environment values, `.task` modifier for async work, and built-in components like `ContentUnavailableView`.

### 5. Type Safety
Use enums instead of strings for identifiers, typed throws for specific errors, and proper `Sendable` conformance for thread safety.

### 6. Value Types When Possible
Prefer structs and enums over classes unless reference semantics are required. Use `actor` for thread-safe shared mutable state.

## When to Use This Skill

Activate this skill when:
- Writing Swift or iOS application code
- Designing application architecture
- Reviewing or modernizing existing Swift code
- Setting up SwiftUI views, view models, or data models
- Implementing networking, persistence, or business logic
- Working with async operations or concurrency

## Architecture Pattern: MVVM with Observation

### View Model Structure
```swift
@Observable
final class ViewModel {
    // Private dependencies
    private let service: ServiceProtocol
    
    // Public readable state
    private(set) var data: [Item] = []
    private(set) var isLoading = false
    private(set) var error: Error?
    
    // User input state (use @Bindable in view)
    var searchText = ""
    var selectedFilter: Filter = .all
    
    init(service: ServiceProtocol) {
        self.service = service
    }
    
    // Public actions
    func loadData() async {
        isLoading = true
        defer { isLoading = false }
        
        do {
            data = try await service.fetchData()
        } catch {
            self.error = error
        }
    }
}
```

### View Structure
```swift
struct ContentView: View {
    @Bindable var viewModel: ViewModel
    
    var body: some View {
        content
            .task { await viewModel.loadData() }
    }
    
    @ViewBuilder
    private var content: some View {
        if viewModel.isLoading {
            ProgressView()
        } else {
            List(viewModel.data) { item in
                ItemRow(item: item)
            }
        }
    }
}
```

## SwiftData Quick Reference

### Model Definition
```swift
import SwiftData

@Model
final class Item {
    var name: String
    var createdAt: Date
    @Relationship(deleteRule: .cascade) var children: [ChildItem]
    
    init(name: String) {
        self.name = name
        self.createdAt = Date()
        self.children = []
    }
}
```

### Querying Data
```swift
// In SwiftUI view
@Query(sort: \Item.createdAt, order: .reverse) 
private var items: [Item]

// With filter
@Query(filter: #Predicate<Item> { $0.isComplete }) 
private var completedItems: [Item]

// With dynamic predicate
@Query private var items: [Item]

init(searchText: String) {
    let predicate = #Predicate<Item> { item in
        searchText.isEmpty || item.name.contains(searchText)
    }
    _items = Query(filter: predicate)
}
```

### Model Context Operations
```swift
@Environment(\.modelContext) private var modelContext

func addItem() {
    let item = Item(name: "New")
    modelContext.insert(item)
    try? modelContext.save()
}

func deleteItem(_ item: Item) {
    modelContext.delete(item)
    try? modelContext.save()
}
```

## API Client Pattern

Create an `actor` for thread-safe API operations:

```swift
actor APIClient {
    private let session: URLSession
    private let decoder: JSONDecoder
    
    init(session: URLSession = .shared) {
        self.session = session
        self.decoder = JSONDecoder()
    }
    
    func fetch<T: Decodable>(_ endpoint: Endpoint) async throws -> T {
        let (data, response) = try await session.data(for: endpoint.urlRequest)
        
        guard let httpResponse = response as? HTTPURLResponse,
              (200...299).contains(httpResponse.statusCode) else {
            throw APIError.invalidResponse
        }
        
        return try decoder.decode(T.self, from: data)
    }
}
```

## Navigation Pattern

Use type-safe navigation with `NavigationStack`:

```swift
struct AppView: View {
    @State private var path = NavigationPath()
    
    var body: some View {
        NavigationStack(path: $path) {
            RootView()
                .navigationDestination(for: Item.self) { item in
                    ItemDetailView(item: item)
                }
                .navigationDestination(for: User.self) { user in
                    UserProfileView(user: user)
                }
        }
    }
}
```

## Testing with Swift Testing

Use the modern Swift Testing framework instead of XCTest:

```swift
import Testing

@Test("View model loads data successfully")
func dataLoading() async throws {
    let viewModel = ViewModel(service: MockService())
    await viewModel.loadData()
    #expect(viewModel.data.isEmpty == false)
}

@Test("Validation fails with invalid input", arguments: [
    "invalid-email",
    "missing@",
    "@domain.com"
])
func emailValidation(invalidEmail: String) throws {
    #expect(throws: ValidationError.self) {
        try validateEmail(invalidEmail)
    }
}
```

## Common Modernization Checks

Before writing code, verify you're using:
- ✅ `@Observable` NOT `ObservableObject`
- ✅ `@Query` NOT `@FetchRequest`
- ✅ `NavigationStack` NOT `NavigationView`
- ✅ `async/await` NOT completion handlers
- ✅ `@MainActor` NOT `DispatchQueue.main.async`
- ✅ `actor` NOT serial `DispatchQueue`
- ✅ `SwiftData.ModelContext` NOT `NSManagedObjectContext`
- ✅ Swift Testing `@Test` NOT XCTest
- ✅ Typed `throws(ErrorType)` when appropriate

## Bundled Resources

### References
Load when you need detailed guidance:

- **modern-patterns.md** - Comprehensive patterns for Swift 6/iOS 18+
  - Load when: Implementing any feature, especially concurrency, data persistence, or API calls
  
- **anti-patterns.md** - What NOT to do and why
  - Load when: Reviewing code, modernizing legacy patterns, or unsure about approach
  
- **examples.md** - Complete working implementations
  - Load when: Starting new features (Todo app, Weather app, Auth flow examples)

### Usage Pattern
1. Read the relevant reference file before implementing complex features
2. Check anti-patterns when reviewing existing code
3. Reference complete examples when starting new app components

## Quick Decision Tree

**Need state management?**
→ Use `@Observable` for view models
→ Use `@State` for simple view-local state
→ Use `@Environment` for dependency injection

**Need data persistence?**
→ Use SwiftData with `@Model` and `@Query`
→ Never use Core Data for new code

**Need async operations?**
→ Use `async/await` and structured concurrency
→ Mark UI-bound code with `@MainActor`
→ Use `actor` for thread-safe shared state

**Need navigation?**
→ Use `NavigationStack` with `NavigationPath`
→ Type-safe destinations with `.navigationDestination(for:)`

**Need API calls?**
→ Create an `actor` with `async throws` methods
→ Use `URLSession.data(from:)` with async/await

## Error Prevention

This skill actively prevents these outdated patterns:
- Core Data (`NSManagedObject`, `@FetchRequest`)
- Combine (`ObservableObject`, `@Published`, `.sink`)
- GCD (`DispatchQueue`, `DispatchGroup`)
- Old SwiftUI (`NavigationView`, `NavigationLink(destination:)`)
- Manual threading (`Thread`, `NSOperationQueue`)
- Completion handlers when `async/await` is available
- XCTest when Swift Testing is more appropriate

When encountering these patterns in existing code, suggest modern alternatives from the references.