Get the FREE Ultimate OpenClaw Setup Guide →

swift-persistence

npx machina-cli add skill jeremieb/swift-unit-test-instructions/swift-persistence --openclaw
Files (1)
SKILL.md
4.9 KB

Swift Persistence

Instructions

Step 1: Choose the framework

Ask if not already specified:

SwiftData or CoreData?

SwiftDataCoreData
Min iOSiOS 17+iOS 8+
Syntax@Model macro, Swift-nativeNSManagedObject, Objective-C roots
Recommended forNew projects on iOS 17+Legacy projects, complex migration needs

Then load the matching reference:

  • SwiftData → references/swiftdata.md
  • CoreData → references/coredata.md

Step 2: Define the models

Ask: what entities are needed? For each entity:

  • Name (singular, UpperCamelCase: Task, UserProfile, Transaction)
  • Properties and their types
  • Relationships to other entities (one-to-one, one-to-many, many-to-many)
  • Any computed properties needed

Step 3: Generate the model layer

Models go in Core/Models/ — they are plain data types, framework-agnostic where possible.

Persistence infrastructure goes in Services/Storage/:

  • PersistenceController.swift — container setup, in-memory preview/test variant
  • [Entity]Repository.swift — CRUD operations for each entity
  • [Entity]RepositoryProtocol.swift — protocol for testability

Rule: ViewModels inject a repository protocol. They never touch ModelContext or NSManagedObjectContext directly.

Services/Storage/
├── PersistenceController.swift
├── TaskRepository.swift
└── TaskRepositoryProtocol.swift

Step 4: Generate the repository protocol

Always define a protocol first so ViewModels can be tested with a mock:

// Services/Storage/TaskRepositoryProtocol.swift
protocol TaskRepositoryProtocol {
    func fetchAll() throws -> [Task]
    func save(_ task: Task) throws
    func delete(_ task: Task) throws
}

Step 5: Wire into the ViewModel

ViewModels receive the repository via init:

@MainActor
@Observable
final class TaskListViewModel {
    private(set) var tasks: [Task] = []
    private let repository: TaskRepositoryProtocol

    init(repository: TaskRepositoryProtocol = TaskRepository()) {
        self.repository = repository
    }

    func loadTasks() {
        tasks = (try? repository.fetchAll()) ?? []
    }
}

Step 6: Generate mock for tests

final class MockTaskRepository: TaskRepositoryProtocol {
    var stubbedTasks: [Task] = []
    var saveCallCount = 0
    var deleteCallCount = 0

    func fetchAll() throws -> [Task] { stubbedTasks }
    func save(_ task: Task) throws { saveCallCount += 1 }
    func delete(_ task: Task) throws { deleteCallCount += 1 }
}

Step 7: Quality checklist

  • ModelContext / NSManagedObjectContext is never used in a ViewModel or View
  • Repository protocol exists for every entity accessed by a ViewModel
  • In-memory container/store exists for tests and previews
  • Background context used for heavy writes (batch imports, migrations)
  • Models in Core/Models/ have no direct framework import (SwiftData or CoreData) in the ViewModel layer
  • Migrations are handled explicitly — no silent schema changes

Examples

Example 1: SwiftData task app

User says: "Add SwiftData to persist tasks with title, completion status, and due date"

Actions:

  1. Load references/swiftdata.md
  2. Generate Task model with @Model
  3. Generate PersistenceController with .inMemory preview variant
  4. Generate TaskRepository + TaskRepositoryProtocol
  5. Update TaskListViewModel to inject repository
  6. Generate MockTaskRepository for tests

Example 2: CoreData in existing UIKit app

User says: "I need to add CoreData to my existing UIKit project to cache user profiles"

Actions:

  1. Load references/coredata.md
  2. Generate UserProfile NSManagedObject subclass
  3. Generate PersistenceController with in-memory option
  4. Generate UserProfileRepository + protocol
  5. Wire into existing ProfileViewModel

Troubleshooting

SwiftData: "Context is not available"ModelContext is being accessed outside a SwiftData-aware environment. Ensure the ModelContainer is set up at the App level and injected via .modelContainer() modifier. Never create contexts manually in ViewModels.

CoreData: merge conflicts — You are writing on the main context from a background thread. Use performBackgroundTask or a dedicated background context. Always call context.save() on the correct thread.

Tests failing because model can't be found — The in-memory store is not configured. Use PersistenceController.preview (SwiftData) or an in-memory NSPersistentContainer in your test setup.

Source

git clone https://github.com/jeremieb/swift-unit-test-instructions/blob/main/skills/swift-persistence/SKILL.mdView on GitHub

Overview

Build a framework-agnostic MVVM persistence layer using SwiftData or CoreData. It defines plain models, repository protocols, and in-memory test variants to keep logic testable. This approach separates concerns and supports both modern SwiftData and legacy CoreData workflows.

How This Skill Works

Models live in Core/Models and persistence logic resides in Services/Storage with a repository-based interface. ViewModels depend on a repository protocol, enabling mock implementations for tests and a concrete repository for production. Framework selection (SwiftData or CoreData) is determined upfront and the corresponding references are loaded to drive code generation.

When to Use It

  • Add CoreData to an app or migrate an existing data model
  • Set up SwiftData models using @Model in MVVM
  • Persist data via a repository abstraction rather than directly in the context
  • Create in-memory/test variant for previews and tests
  • Define and wire a repository protocol to ViewModels for testability

Quick Start

  1. Step 1: Pick SwiftData or CoreData and load the matching reference
  2. Step 2: Define entities in Core/Models and create a per-entity repository (e.g., TaskRepository.swift and TaskRepositoryProtocol.swift)
  3. Step 3: Wire the repository into a ViewModel, add a Mock repository for tests, and verify with in-memory storage

Best Practices

  • ModelContext / NSManagedObjectContext is never used directly in a ViewModel
  • Create a repository protocol for every entity accessed by a ViewModel
  • Provide an in-memory container or store for tests and previews
  • Use a background context for heavy writes (batch imports, migrations)
  • Migrations are explicit — avoid silent schema changes

Example Use Cases

  • SwiftData task app: define Task with @Model, in-memory PersistenceController, TaskRepository, and MockTaskRepository for tests
  • CoreData in UIKit app: add Task as NSManagedObject-derived entity, wire TaskRepository, and use mocks for unit tests
  • MVVM data-sync app: replace direct managed object contexts with a repository-backed ViewModel
  • Preview-driven UI: rely on in-memory storage to power SwiftUI previews without a real database
  • Unit testing persistence: inject a MockTaskRepository implementing TaskRepositoryProtocol for deterministic tests

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers