axiom-realm-migration-ref
Use when migrating from Realm to SwiftData - comprehensive migration guide covering pattern equivalents, threading model conversion, schema migration strategies, CloudKit sync transition, and real-world scenarios
Install
mkdir -p .claude/skills/axiom-realm-migration-ref && curl -L -o skill.zip "https://mcp.directory/api/skills/download/7224" && unzip -o skill.zip -d .claude/skills/axiom-realm-migration-ref && rm skill.zipInstalls to .claude/skills/axiom-realm-migration-ref
About this skill
Realm to SwiftData Migration — Reference Guide
Purpose: Complete migration path from Realm to SwiftData Swift Version: Swift 5.9+ (Swift 6 with strict concurrency recommended) iOS Version: iOS 17+ (iOS 26+ recommended) Context: Realm Device Sync sunset Sept 30, 2025. This guide is essential for Realm users migrating before deadline.
Critical Timeline
Realm Device Sync DEPRECATION DEADLINE = September 30, 2025
If your app uses Realm Sync:
- ⚠️ You MUST migrate by September 30, 2025
- ✅ SwiftData is the recommended replacement
- ⏰ Time remaining: Depends on current date, but migrations take 2-8 weeks for production apps
This guide provides everything needed for successful migration.
Migration Strategy Overview
Phase 1 (Week 1-2): Preparation & Planning
├─ Audit current Realm usage
├─ Understand model relationships
├─ Plan data migration path
└─ Set up test environment
Phase 2 (Week 2-3): Development
├─ Create SwiftData models from Realm schemas
├─ Implement data migration logic
├─ Convert threading model to async/await
└─ Test with real data
Phase 3 (Week 3-4): Migration
├─ Migrate existing app users' data
├─ Run in parallel (Realm + SwiftData)
├─ Verify CloudKit sync works
└─ Monitor for issues
Phase 4 (Week 4+): Production
├─ Deploy update with parallel persistence
├─ Gradual cutover from Realm to SwiftData
├─ Deprecate Realm code
└─ Monitor CloudKit sync health
Part 1: Pattern Equivalents
Model Definition Conversion
Realm → SwiftData: Basic Model
// REALM
class RealmTrack: Object {
@Persisted(primaryKey: true) var id: String
@Persisted var title: String
@Persisted var artist: String
@Persisted var duration: TimeInterval
@Persisted var genre: String?
}
// SWIFTDATA
@Model
final class Track {
@Attribute(.unique) var id: String
var title: String
var artist: String
var duration: TimeInterval
var genre: String?
init(id: String, title: String, artist: String, duration: TimeInterval, genre: String? = nil) {
self.id = id
self.title = title
self.artist = artist
self.duration = duration
self.genre = genre
}
}
Key differences:
- Realm:
@Persisted(primaryKey: true)→ SwiftData:@Attribute(.unique) - Realm: Implicit init → SwiftData: Explicit init required
- Realm:
Objectbase class → SwiftData:@Modelmacro onfinal class
Realm → SwiftData: Relationships
// REALM: One-to-Many
class RealmAlbum: Object {
@Persisted(primaryKey: true) var id: String
@Persisted var title: String
@Persisted var tracks: RealmSwiftCollection<RealmTrack>
}
// SWIFTDATA: One-to-Many
@Model
final class Album {
@Attribute(.unique) var id: String
var title: String
@Relationship(deleteRule: .cascade, inverse: \Track.album)
var tracks: [Track] = []
}
@Model
final class Track {
@Attribute(.unique) var id: String
var title: String
var album: Album? // Inverse automatically maintained
}
Key differences:
- Realm: Explicit
RealmSwiftCollectiontype → SwiftData: Native[Track]array - Realm: Manual relationship management → SwiftData: Inverse relationships automatic
- Realm: No delete rules → SwiftData:
deleteRule: .cascade / .nullify / .deny
Realm → SwiftData: Indexes
// REALM
class RealmTrack: Object {
@Persisted(primaryKey: true) var id: String
@Persisted(indexed: true) var genre: String
@Persisted(indexed: true) var releaseDate: Date
}
// SWIFTDATA
@Model
final class Track {
@Attribute(.unique) var id: String
@Attribute(.indexed) var genre: String = ""
@Attribute(.indexed) var releaseDate: Date = Date()
}
Part 2: Threading Model Conversion
Realm Threading → Swift Concurrency
Realm: Manual Thread Handling
class RealmDataManager {
func fetchTracksOnBackground() {
DispatchQueue.global().async {
let realm = try! Realm() // Must get Realm on each thread
let tracks = realm.objects(RealmTrack.self)
DispatchQueue.main.async {
self.updateUI(tracks: Array(tracks))
}
}
}
func saveTrackOnBackground(_ track: RealmTrack) {
DispatchQueue.global().async {
let realm = try! Realm()
try! realm.write {
realm.add(track)
}
}
}
}
Problems:
- Manual DispatchQueue threading error-prone
- Easy to access objects on wrong thread
- No compile-time guarantees
SwiftData: Actor-Based Concurrency
actor SwiftDataManager {
let modelContainer: ModelContainer
func fetchTracks() async -> [Track] {
let context = ModelContext(modelContainer)
let descriptor = FetchDescriptor<Track>()
return (try? context.fetch(descriptor)) ?? []
}
func saveTrack(_ track: Track) async {
let context = ModelContext(modelContainer)
context.insert(track)
try? context.save()
}
}
// Usage (automatic thread handling)
@MainActor
class ViewController: UIViewController {
@State private var tracks: [Track] = []
private let manager: SwiftDataManager
func loadTracks() async {
tracks = await manager.fetchTracks()
}
}
Advantages:
- No manual DispatchQueue
- Compile-time thread safety
- Automatic actor isolation
- Swift 6 strict concurrency compatible
Common Threading Patterns
| Realm Pattern | SwiftData Pattern |
|---|---|
DispatchQueue.global().async | async/await in background actor |
realm.write { } | context.insert() + context.save() |
| Manual thread-local Realm instances | Shared ModelContainer + background ModelContext |
Thread.isMainThread checks | @MainActor annotations |
Part 3: Schema Migration Strategies
Simple Schema Migration (Direct Conversion)
For apps with simple schemas (< 5 tables, < 10 fields), direct migration is straightforward:
actor SchemaImporter {
let realmPath: String
let modelContainer: ModelContainer
func migrateFromRealm() async throws {
// 1. Open Realm database
let realmConfig = Realm.Configuration(fileURL: URL(fileURLWithPath: realmPath))
let realm = try await Realm(configuration: realmConfig)
// 2. Create SwiftData context
let context = ModelContext(modelContainer)
// 3. Migrate each model type
try migrateAllTracks(from: realm, to: context)
try migrateAllAlbums(from: realm, to: context)
try migrateAllPlaylists(from: realm, to: context)
// 4. Save all at once
try context.save()
print("Migration complete!")
}
private func migrateAllTracks(from realm: Realm, to context: ModelContext) throws {
let realmTracks = realm.objects(RealmTrack.self)
for realmTrack in realmTracks {
let sdTrack = Track(
id: realmTrack.id,
title: realmTrack.title,
artist: realmTrack.artist,
duration: realmTrack.duration,
genre: realmTrack.genre
)
context.insert(sdTrack)
}
}
private func migrateAllAlbums(from realm: Realm, to context: ModelContext) throws {
let realmAlbums = realm.objects(RealmAlbum.self)
for realmAlbum in realmAlbums {
let sdAlbum = Album(
id: realmAlbum.id,
title: realmAlbum.title
)
context.insert(sdAlbum)
// Connect relationships after creating all records
for realmTrack in realmAlbum.tracks {
if let sdTrack = findTrack(id: realmTrack.id, in: context) {
sdAlbum.tracks.append(sdTrack)
}
}
}
}
private func findTrack(id: String, in context: ModelContext) -> Track? {
let descriptor = FetchDescriptor<Track>(
predicate: #Predicate { $0.id == id }
)
return try? context.fetch(descriptor).first
}
}
Complex Schema Migration (Transformation Layer)
For apps with complex schemas, many computed properties, or data transformations:
// Step 1: Define transformation layer
struct TrackDTO {
let realmTrack: RealmTrack
var id: String { realmTrack.id }
var title: String { realmTrack.title }
var cleanTitle: String { realmTrack.title.trimmingCharacters(in: .whitespaces) }
var durationFormatted: String {
let minutes = Int(realmTrack.duration) / 60
let seconds = Int(realmTrack.duration) % 60
return String(format: "%d:%02d", minutes, seconds)
}
}
// Step 2: Migrate through transformation layer
actor ComplexMigrator {
let modelContainer: ModelContainer
func migrateWithTransformation(from realm: Realm) throws {
let context = ModelContext(modelContainer)
let realmTracks = realm.objects(RealmTrack.self)
for realmTrack in realmTracks {
let dto = TrackDTO(realmTrack: realmTrack)
// Transform data during migration
let sdTrack = Track(
id: dto.id,
title: dto.cleanTitle, // Cleaned version
artist: realmTrack.artist,
duration: realmTrack.duration
)
context.insert(sdTrack)
}
try context.save()
}
}
Part 4: CloudKit Sync Transition
Realm Sync → SwiftData CloudKit
Realm Sync (now deprecated) provided automatic sync. SwiftData uses CloudKit directly:
// REALM SYNC: Automatic but deprecated
let config = Realm.Configuration(
syncConfiguration: SyncConfiguration(user: app.currentUser!)
)
// SWIFTDATA: CloudKit (recommended replacement)
let schema = Schema([Track.self, Album.self])
let config = ModelConfiguration(
schema: schema,
cloudKitDatabase: .privat
---
*Content truncated.*
More by CharlesWiltgen
View all skills by CharlesWiltgen →You might also like
flutter-development
aj-geddes
Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.
drawio-diagrams-enhanced
jgtolentino
Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.
ui-ux-pro-max
nextlevelbuilder
"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."
godot
bfollington
This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.
nano-banana-pro
garg-aayush
Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.
fastapi-templates
wshobson
Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.
Related MCP Servers
Browse all serversRtfmbro is an MCP server for config management tools—get real-time, version-specific docs from GitHub for Python, Node.j
Keycloak Admin plugin enables seamless user and realm management for automated identity and access control solutions.
RSS Feed Parser is a powerful rss feed generator and rss link generator with RSSHub integration, perfect for creating cu
Analyze and decompile Java class files online with our Java decompiler software, featuring JD decompiler and JD GUI inte
Access and interact with Jira and Linear tickets directly in conversations—no context switching to Jira ticketing softwa
Integrate with Keycloak for user creation, role assignment, group, and client management across realms using Keycloak id
Stay ahead of the MCP ecosystem
Get weekly updates on new skills and servers.