Swiftdata Patterns

v1.0.0

SwiftData best practices, batch queries, N+1 avoidance, and model relationships for macOS/iOS apps

0· 137·0 current·0 all-time

Install

OpenClaw Prompt Flow

Install with OpenClaw

Best for remote or guided setup. Copy the exact prompt, then paste it into OpenClaw for soponcd/swiftdata-patterns.

Previewing Install & Setup.
Prompt PreviewInstall & Setup
Install the skill "Swiftdata Patterns" (soponcd/swiftdata-patterns) from ClawHub.
Skill page: https://clawhub.ai/soponcd/swiftdata-patterns
Keep the work scoped to this skill only.
After install, inspect the skill metadata and help me finish setup.
Use only the metadata you can verify from ClawHub; do not invent missing requirements.
Ask before making any broader environment changes.

Command Line

CLI Commands

Use the direct CLI path if you want to install manually and keep every step visible.

OpenClaw CLI

Bare skill slug

openclaw skills install swiftdata-patterns

ClawHub CLI

Package manager switcher

npx clawhub@latest install swiftdata-patterns
Security Scan
VirusTotalVirusTotal
Benign
View report →
OpenClawOpenClaw
Benign
high confidence
Purpose & Capability
The name and description (SwiftData best practices, batch queries, N+1 avoidance, model relationships) match the provided SKILL.md content. There are no unrelated required binaries, env vars, or install steps.
Instruction Scope
SKILL.md contains only code examples and guidance for SwiftData usage within iOS/macOS apps (Model definitions, FetchDescriptor patterns, in-memory testing, App Group file-store examples). It does not instruct the agent to read host system files, export secrets, call external endpoints, or perform actions outside of showing code snippets and recommendations.
Install Mechanism
No install spec or code files are included; this is instruction-only, so nothing is downloaded or written to disk by the skill itself.
Credentials
The skill declares no required environment variables, credentials, or config paths. Example code references app-specific paths (App Group ID placeholder) which are normal for app development and not agent-level secrets.
Persistence & Privilege
always is false and the skill does not request persistent system presence or modify other skills or agent-wide settings. Autonomous invocation is allowed by default (not a red flag here) and is proportional for an assistant skill.
Assessment
This is a documentation-only skill (code examples) for SwiftData patterns and appears internally consistent. It's safe to install from a permissions/credential perspective. As always: review code snippets before copying them into production (placeholders like group.your.app.id must be replaced), and be cautious if you later enable any agent feature that can execute code or access your device file system—those runtime permissions are outside this skill and could change risk.

Like a lobster shell, security has layers — review code before you run it.

Runtime requirements

🗄️ Clawdis
latestvk97060h2meayah44k6v2e3nyc183j7vn
137downloads
0stars
1versions
Updated 1mo ago
v1.0.0
MIT-0

SwiftData Patterns

Expert-level SwiftData patterns for macOS/iOS applications. Optimized for performance, relationships, and production readiness.

When to Use

Use this skill when:

  • Designing SwiftData models
  • Writing SwiftData queries
  • Optimizing batch operations
  • Setting up model relationships
  • Handling persistence layer architecture
  • Avoiding N+1 query problems

Core Principles

1. Model Design

@Model
final class YourModel {
    @Attribute(.unique) var id: UUID

    // Use external storage for large data
    @Attribute(.externalStorage) var largeData: Data?

    // Relationships with cascade delete
    @Relationship(deleteRule: .cascade)
    var children: [ChildModel]?

    init(id: UUID = UUID()) {
        self.id = id
    }
}

2. FetchDescriptor Best Practices

// Use predicate for filtering
let descriptor = FetchDescriptor<YourModel>(
    predicate: #Predicate { $0.isActive && $0.createdAt >= startDate },
    sortBy: [SortDescriptor(\.createdAt, order: .reverse)]
)

// Batch fetch by IDs (N+1 avoidance)
func fetchModels(by ids: [UUID]) -> [YourModel] {
    guard !ids.isEmpty else { return [] }

    let descriptor = FetchDescriptor<YourModel>(
        predicate: #Predicate { ids.contains($0.id) }
    )
    return (try? context.fetch(descriptor)) ?? []
}

3. In-Memory Testing Pattern

@MainActor
final class ModelTests: XCTestCase {
    var container: ModelContainer!
    var context: ModelContext!

    override func setUp() async throws {
        try await super.setUp()
        let config = ModelConfiguration(isStoredInMemoryOnly: true)
        container = try ModelContainer(for: YourModel.self, configurations: config)
        context = container.mainContext
    }

    override func tearDown() async throws {
        try await super.tearDown()
        container = nil
        context = nil
    }
}

4. Service Layer Pattern

@MainActor
final class DataService {
    nonisolated let container: ModelContainer
    let context: ModelContext

    init(inMemory: Bool = false) throws {
        let configuration = ModelConfiguration(isStoredInMemoryOnly: inMemory)
        container = try ModelContainer(for: YourModel.self, configurations: configuration)
        context = ModelContext(container)
        context.autosaveEnabled = false  // Manual save control
    }

    func save() throws {
        try context.save()
    }
}

Performance Patterns

Batch Insert with Chunking

extension ModelContext {
    func safeBatchInsert<T: PersistentModel>(
        _ objects: [T],
        batchSize: Int = 100
    ) throws {
        for (index, object) in objects.enumerated() {
            insert(object)
            if index % batchSize == 0 {
                try save()
            }
        }
        try save()
    }
}

Avoid N+1 Queries

Bad - N+1 problem:

for reminder in reminders {
    let task = service.findIdentityMap(by: reminder.id)  // N queries!
    process(task)
}

Good - Batch fetch:

let ids = reminders.map { $0.id }
let tasks = service.fetchIdentityMaps(by: ids)  // 1 query!

for (index, reminder) in reminders.enumerated() {
    let task = tasks.first { $0.ekIdentifier == reminder.id }
    process(task)
}

Shared Fetch Descriptors

@MainActor
final class DataService {
    // Nonisolated for thread-safe descriptor access
    nonisolated func descriptorForActiveItems() -> FetchDescriptor<YourModel> {
        FetchDescriptor<YourModel>(
            predicate: #Predicate { $0.isActive },
            sortBy: [SortDescriptor(\.createdAt, order: .reverse)]
        )
    }

    // Use in @Observable ViewModels
    func fetchActiveItems() -> [YourModel] {
        try? context.fetch(descriptorForActiveItems()) ?? []
    }
}

Model Relationships

Bidirectional Links

@Model
final class Note {
    @Attribute(.unique) var id: UUID

    // Forward links
    @Relationship(inverse: \Note.backlinks)
    var forwardLinks: [Note]?

    // Backward links (auto-maintained)
    var backlinks: [Note]?

    init(id: UUID = UUID()) {
        self.id = id
    }
}

Cascade Delete

@Model
final class Parent {
    @Attribute(.unique) var id: UUID

    @Relationship(deleteRule: .cascade)  // Auto-delete children
    var children: [Child]?
}

@Model
final class Child {
    @Attribute(.unique) var id: UUID
    var parent: Parent?
}

Configuration Best Practices

App Group Support

private static func createConfiguration(inMemory: Bool) throws -> ModelConfiguration {
    if inMemory {
        return ModelConfiguration(isStoredInMemoryOnly: true)
    }

    let appGroupID = "group.your.app.id"
    guard let containerURL = FileManager.default.containerURL(
        forSecurityApplicationGroupIdentifier: appGroupID
    ) else {
        // Fallback to sandbox
        return createSandboxConfiguration()
    }

    let dataURL = containerURL.appendingPathComponent("App_Data")
    try? FileManager.default.createDirectory(at: dataURL, withIntermediateDirectories: true)
    let storeURL = dataURL.appendingPathComponent("App.sqlite")

    return ModelConfiguration(url: storeURL, cloudKitDatabase: .automatic)
}

Testing Guidelines

Given-When-Then Pattern

func testBatchFetchPerformance() async throws {
    // Given: Create test data
    let ids = (0..<100).map { _ in
        let model = service.createModel()
        try? context.save()
        return model.id
    }

    // When: Batch fetch
    let start = Date()
    let results = service.fetchModels(by: ids)
    let duration = Date().timeIntervalSince(start)

    // Then: Verify
    XCTAssertEqual(results.count, 100)
    XCTAssertLessThan(duration, 0.5, "Batch fetch should be fast")
}

Predicate Testing

func testPredicateFiltering() async throws {
    // Given
    let activeModel = service.createModel(isActive: true)
    let inactiveModel = service.createModel(isActive: false)
    try? context.save()

    // When
    let descriptor = FetchDescriptor<YourModel>(
        predicate: #Predicate { $0.isActive }
    )
    let results = try context.fetch(descriptor)

    // Then
    XCTAssertEqual(results.count, 1)
    XCTAssertEqual(results.first?.id, activeModel.id)
}

Best Practices

PracticeReason
Use @MainActor on servicesSwiftData context is main-thread bound
External storage for large dataPrevents database bloat
Batch fetch for relationshipsAvoids N+1 queries
Manual autosave controlPrevents unwanted intermediate saves
In-memory config for testsIsolated test state
Nonisolated fetch descriptorsThread-safe descriptor access

Common Pitfalls

PitfallConsequencePrevention
N+1 queriesSlow sync performanceUse batch fetch(by: [ID])
Forgetting @MainActorRuntime crashesAll SwiftData services must be isolated
Large data inlineDatabase bloatUse @Attribute(.externalStorage)
Auto-save conflictsUnexpected state changesSet autosaveEnabled = false
Missing cascade deleteOrphaned recordsUse deleteRule: .cascade

Running SwiftData in Tests

# Test with SwiftData
xcodebuild test -scheme YourApp \
  -destination 'platform=macOS' \
  -only-testing:'YourAppTests/ModelTests/testBatchFetch'

Comments

Loading comments...