---
name: ios-coredata-architect
description: "Core Data schemas, NSPersistentCloudKitContainer, SwiftData migrations — Apple persistence done right. Designs Core Data / SwiftData models with proper relationships, indexes, and lightweight migrations. Handles the Core Data → SwiftData migration path."
license: MIT
---

# Core Data & SwiftData Architect

A focused assistant for Apple's persistence stack. It designs Core Data and SwiftData models that hold up in production — sane relationships, the right indexes, safe migrations, and CloudKit sync that actually round-trips — and it knows the sharp edges of moving an existing Core Data app onto SwiftData. Aimed at iOS/macOS developers who are past "hello world" and need the schema decisions to be correct before they ship.

## When to use it

- You're starting a new app and want a SwiftData `@Model` graph (or `.xcdatamodeld`) designed correctly the first time.
- A lightweight migration is silently failing and you need to know whether you've crossed into a mapping-model situation.
- You're adopting `NSPersistentCloudKitContainer` and hitting "CloudKit integration requires…" errors at launch.
- You want to migrate a Core Data codebase to SwiftData without losing the existing on-disk store.
- You need a fetch to stop being slow and suspect a missing index or an N+1 relationship fault.

## What it does

- Builds SwiftData models with `@Model`, `@Attribute(.unique)`, `@Relationship(deleteRule:inverse:)`, and `@Index`, plus the matching Core Data entity/attribute/relationship setup and Spotlight/derived attributes where useful.
- Sets up `ModelContainer` / `ModelConfiguration` and the Core Data equivalent (`NSPersistentContainer`, `NSManagedObjectContext`, background contexts via `performBackgroundTask`).
- Configures `NSPersistentCloudKitContainer` correctly: optional attributes with defaults, no unique constraints (CloudKit forbids them), `NSPersistentHistoryTrackingKey`, and remote-change notifications.
- Plans migrations — lightweight inference, `VersionedSchema` + `SchemaMigrationPlan` (custom/lightweight stages) for SwiftData, and mapping models / `NSMigrationManager` when inference can't bridge the change.

## Example

```
> Design a SwiftData model for Notebook -> Note (one-to-many),
  CloudKit-synced, with full-text-ish search on Note.title.

@Model final class Notebook {
  var name: String = ""
  // CloudKit: no .unique, inverse required, cascade delete
  @Relationship(deleteRule: .cascade, inverse: \Note.notebook)
  var notes: [Note] = []
}

@Model final class Note {
  #Index<Note>([\.title])
  var title: String = ""
  var notebook: Notebook?
}
```

## Tips & gotchas

- For CloudKit, every attribute must be optional or have a default, and `@Attribute(.unique)` is unavailable — model uniqueness in app logic instead.
- Lightweight migration silently bails when you rename without a renaming identifier; set `@Attribute(originalName:)` (SwiftData) or hash-modifier renaming IDs (Core Data).
- Enable `NSPersistentHistoryTrackingKey` before going multi-process or CloudKit, not after — retrofitting history tracking onto a live store is painful.
- SwiftData and Core Data can share one store; keep the `NSManagedObjectModel` and `@Model` graph byte-compatible during a phased migration.

## FAQ

**Can I migrate to SwiftData without resetting users' data?** Yes. Point `ModelConfiguration` at the existing store URL and keep entity/attribute names identical; SwiftData reads the Core Data store in place.

**Why does CloudKit sync drop some records?** Usually a non-optional attribute with no default or a unique constraint — both are rejected by the CloudKit schema. The skill audits the model for these.

**Lightweight vs. mapping model — how do I know?** If you only add/remove attributes or optional relationships, inference handles it. Renames, type changes, or splitting entities need a `SchemaMigrationPlan` stage or an `NSMappingModel`.
