Token导航 LogoToken导航TokenDH.com
开发需要联网github未标认证来源可访问clear审计通过

axiom-swiftdata公理 SwiftData

Agent Skill

用于辅助数据整理、表格处理、CSV/Excel 分析、指标计算和图表准备。它适合让 Agent 清洗字段、汇总数据、发现异常、生成统计口径或把分析结果转成可读说明。使用时需要确认数据来源、字段含义和时间范围,避免把样本数据当全量事实;涉及敏感数据、导出文件或批量写回时,应先确认权限和脱敏边界。

总安装

6,914

周安装

297

GitHub Stars

873

下载量

2,424
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

3

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:axiom-swiftdata(公理 SwiftData)
来源仓库:https://github.com/charleswiltgen/axiom
仓库路径:skills/axiom-swiftdata
安装命令:
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftdata
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftdata

简介

用于 SwiftData 数据建模和查询,支持 @Model 类和声明式查询。

  • 适用于简单 CRUD 操作、SwiftUI 自动更新和 CloudKit 同步。
  • 提供关系建模、@Query 属性和数据一致性维护机制。
  • 安装前建议确认权限范围和维护状态,避免触发联网或文件读写操作。
  • axiom-swiftdata 属于开发类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

SwiftData

Overview

Apple's native persistence framework using @Model classes and declarative queries. Built on Core Data, designed for SwiftUI.

Core principle Reference types (class) + @Model macro + declarative @Query for reactive SwiftUI integration.

Requires iOS 17+, Swift 5.9+ Target iOS 26+ (this skill focuses on latest features) License Proprietary (Apple)

When to Use SwiftData

Choose SwiftData when you need

  • ✅ Native Apple integration with SwiftUI
  • ✅ Simple CRUD operations
  • ✅ Automatic UI updates with @Query
  • ✅ CloudKit sync (iOS 17+)
  • ✅ Reference types (classes) with relationships

Use SQLiteData instead when

  • Need value types (structs)
  • CloudKit record sharing (not just sync)
  • Large datasets (50k+ records) with specific performance needs

Use GRDB when

  • Complex raw SQL required
  • Fine-grained migration control needed

For migrations See the axiom-swiftdata-migration skill for custom schema migrations with VersionedSchema and SchemaMigrationPlan. For migration debugging, see axiom-swiftdata-migration-diag.

Example Prompts

These are real questions developers ask that this skill is designed to answer:

Basic Operations

1. "I have a notes app with folders. I need to filter notes by folder and sort by last modified. How do I set up the @Query?"

→ The skill shows how to use @Query with predicates, sorting, and automatic view updates

2. "When a user deletes a task list, all tasks should auto-delete too. How do I set up the relationship?"

→ The skill explains @Relationship with deleteRule:.cascade and inverse relationships

3. "I have a relationship between User → Messages → Attachments. How do I prevent orphaned data when deleting?"

→ The skill shows cascading deletes, inverse relationships, and safe deletion patterns

CloudKit & Sync

4. "My chat app syncs messages to other devices via CloudKit. Sometimes messages conflict. How do I handle sync conflicts?"

→ The skill covers CloudKit integration, conflict resolution strategies (last-write-wins, custom resolution), and sync patterns

5. "I'm adding CloudKit sync to my app, but I get 'Property must have a default value' error. What's wrong?"

→ The skill explains CloudKit constraints: all properties must be optional or have defaults, explains why (network timing), and shows fixes

6. "I want to show users when their data is syncing to iCloud and what happens when they're offline."

→ The skill shows monitoring sync status with notifications, detecting network connectivity, and offline-aware UI patterns

7. "I need to share a playlist with other users. How do I implement CloudKit record sharing?"

→ The skill covers CloudKit record sharing patterns (iOS 26+) with owner/permission tracking and sharing metadata

Performance & Optimization

8. "I need to query 50,000 messages but only display 20 at a time. How do I paginate efficiently?"

→ The skill covers performance patterns, batch fetching, limiting queries, and preventing memory bloat with chunked imports

9. "My app loads 100 tasks with relationships, and displaying them is slow. I think it's N+1 queries."

→ The skill shows how to identify N+1 problems without prefetching, provides prefetching pattern, and shows 100x performance improvement

10. "I'm importing 1 million records from an API. What's the best way to batch them without running out of memory?"

→ The skill shows chunk-based importing with periodic saves, memory cleanup patterns, and batch operation optimization

11. "Which properties should I add indexes to? I'm worried about over-indexing slowing down writes."

→ The skill explains index optimization patterns: when to index (frequently filtered/sorted properties), when to avoid (rarely used, frequently changing), maintenance costs

Migration from Legacy Frameworks

12. "We're migrating from Realm/Core Data to SwiftData"

→ See the comparison table in Migration section below, then follow realm-to-swiftdata-migration or axiom-swiftdata-migration for detailed guides


@Model Definitions

Basic Model

import 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 patterns

  • Use final class, not struct (omit final if you need subclasses — see Class Inheritance below)
  • Use @Attribute(.unique) for primary key-like behavior (not supported with CloudKit sync — see CloudKit Constraints below)
  • Provide explicit init (SwiftData doesn't synthesize)
  • Optional properties (String?) are nullable
  • Use @Attribute(.preserveValueOnDeletion) on properties whose values should survive even after the object is deleted (useful for analytics, audit trails)

Relationships

@Model
final class Track {
    @Attribute(.unique) var id: String
    var title: String

    @Relationship(deleteRule: .cascade, inverse: \Album.tracks)
    var album: Album?

    init(id: String, title: String, album: Album? = nil) {
        self.id = id
        self.title = title
        self.album = album
    }
}

@Model
final class Album {
    @Attribute(.unique) var id: String
    var title: String

    @Relationship(deleteRule: .cascade)
    var tracks: [Track] = []

    init(id: String, title: String) {
        self.id = id
        self.title = title
    }
}

Many-to-Many Self-Referential Relationships

@MainActor  // Required for Swift 6 strict concurrency
@Model
final class User {
    @Attribute(.unique) var id: String
    var name: String

    // Users following this user (inverse relationship)
    @Relationship(deleteRule: .nullify, inverse: \User.following)
    var followers: [User] = []

    // Users this user is following
    @Relationship(deleteRule: .nullify)
    var following: [User] = []

    init(id: String, name: String) {
        self.id = id
        self.name = name
    }
}

CRITICAL: SwiftData automatically manages BOTH sides when you modify ONE side.

Correct — Only modify ONE side

// user1 follows user2 (modifying ONE side)
user1.following.append(user2)
try modelContext.save()

// SwiftData AUTOMATICALLY updates user2.followers
// Don't manually append to both sides - causes duplicates!

Wrong — Don't manually update both sides

user1.following.append(user2)
user2.followers.append(user1)  // Redundant! Creates duplicates in CloudKit sync

Unfollowing (remove from ONE side only)

user1.following.removeAll { $0.id == user2.id }
try modelContext.save()
// user2.followers automatically updated

Verifying relationship integrity (for debugging)

// Check if relationship is truly bidirectional
let user1FollowsUser2 = user1.following.contains { $0.id == user2.id }
let user2FollowedByUser1 = user2.followers.contains { $0.id == user1.id }

// These MUST always match after save()
assert(user1FollowsUser2 == user2FollowedByUser1, "Relationship corrupted!")

CloudKit Sync Recovery (if relationships become corrupted)

// If CloudKit sync creates duplicate/orphaned relationships:

// 1. Backup current state
let backup = user.following.map { $0.id }

// 2. Clear relationships
user.following.removeAll()
user.followers.removeAll()
try modelContext.save()

// 3. Rebuild from source of truth (e.g., API)
for followingId in backup {
    if let followingUser = fetchUser(id: followingId) {
        user.following.append(followingUser)
    }
}
try modelContext.save()

// 4. Force CloudKit resync (in ModelConfiguration)
// Re-create ModelContainer to force full sync after corruption recovery

Delete rules

  • .cascade - Delete related objects
  • .nullify - Set relationship to nil
  • .deny - Prevent deletion if relationship exists
  • .noAction - Leave relationship as-is (careful!)

Class Inheritance

SwiftData supports class inheritance for hierarchical models. Use when you have a clear IS-A relationship (e.g., BusinessTrip IS-A Trip) and need both broad queries (all trips) and type-specific queries.

Base and Subclass Pattern

Apply @Model to both base class and subclasses. Omit final on the base class.

@Model class Trip {
    @Attribute(.preserveValueOnDeletion)
    var name: String
    var destination: String
    var startDate: Date
    var endDate: Date

    @Relationship(deleteRule: .cascade, inverse: \Accommodation.trip)
    var accommodation: Accommodation?

    init(name: String, destination: String, startDate: Date, endDate: Date) {
        self.name = name
        self.destination = destination
        self.startDate = startDate
        self.endDate = endDate
    }
}

@Model class BusinessTrip: Trip {
    var purpose: String
    var expenseCode: String

    @Relationship(deleteRule: .cascade, inverse: \BusinessMeal.trip)
    var businessMeals: [BusinessMeal] = []

    init(name: String, destination: String, startDate: Date, endDate: Date,
         purpose: String, expenseCode: String) {
        self.purpose = purpose
        self.expenseCode = expenseCode
        super.init(name: name, destination: destination, startDate: startDate, endDate: endDate)
    }
}

Type-Based Queries with #Predicate

Query all base class instances (includes subclasses), or filter by type:

// All trips (includes BusinessTrip, PersonalTrip, etc.)
@Query(sort: \Trip.startDate) var allTrips: [Trip]

// Only business trips — use `is` in #Predicate
@Query(filter: #Predicate<Trip> { $0 is BusinessTrip }) var businessTrips: [Trip]

// Filter on subclass-specific properties — use `as?` cast
let vacationPredicate = #Predicate<Trip> {
    if let personal = $0 as? PersonalTrip {
        return personal.reason == .vacation
    }
    return false
}
@Query(filter: vacationPredicate) var vacationTrips: [Trip]

Polymorphic Relationships

Relationships typed to the base class can hold mixed subclass instances:

@Model class TravelPlanner {
    var name: String

    @Relationship(deleteRule: .cascade)
    var upcomingTrips: [Trip] = []  // Can contain BusinessTrip and PersonalTrip

    init(name: String) { self.name = name }
}

Cast to access subclass-specific properties:

for trip in planner.upcomingTrips {
    if let business = trip as? BusinessTrip {
        print(business.expenseCode)
    }
}

When to Use Inheritance vs Alternatives

SignalUse InheritanceUse Enum/Flag Instead
Subclasses share many base propertiesYes
Need type-based queries across all modelsYes
Subclasses have their own relationshipsYes
Only 1-2 distinguishing propertiesYes
Query only on specialized propertiesYes
Protocol conformance sufficesYes

Keep hierarchies shallow (1-2 levels). Deep chains complicate schema migrations and queries.

ModelContainer Setup

SwiftUI App

import SwiftUI
import SwiftData

@main
struct MusicApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: [Track.self, Album.self])
    }
}

Custom Configuration

let schema = Schema([Track.self, Album.self])

let config = ModelConfiguration(
    schema: schema,
    url: URL(fileURLWithPath: "/path/to/database.sqlite"),
    cloudKitDatabase: .private("iCloud.com.example.app")
)

let container = try ModelContainer(
    for: schema,
    configurations: config
)

In-Memory (Tests)

let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try ModelContainer(
    for: schema,
    configurations: config
)

Queries in SwiftUI

Basic @Query

import SwiftUI
import SwiftData

struct TracksView: View {
    @Query var tracks: [Track]

    var body: some View {
        List(tracks) { track in
            Text(track.title)
        }
    }
}

Automatic updates View refreshes when data changes.

Filtered, Sorted, Combined

// Filtered
@Query(filter: #Predicate<Track> { $0.genre == "Rock" }) var rockTracks: [Track]

// Sorted (single)
@Query(sort: \.title, order: .forward) var tracks: [Track]

// Sorted (multiple descriptors)
@Query(sort: [SortDescriptor(\.artist), SortDescriptor(\.title)]) var tracks: [Track]

// Combined filter + sort
@Query(filter: #Predicate<Track> { $0.duration > 180 }, sort: \.title) var longTracks: [Track]

ModelContext Operations

Accessing ModelContext

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    // ...
}

CRUD Operations

// Insert
let track = Track(id: "1", title: "Song", artist: "Artist", duration: 240)
modelContext.insert(track)

// Fetch
let descriptor = FetchDescriptor<Track>(
    predicate: #Predicate { $0.genre == "Rock" },
    sortBy: [SortDescriptor(\.title)]
)
let rockTracks = try modelContext.fetch(descriptor)

// Update — just modify properties, SwiftData tracks changes
track.title = "Updated Title"

// Delete
modelContext.delete(track)

// Batch delete
try modelContext.delete(model: Track.self, where: #Predicate { $0.genre == "Classical" })

// Save (optional — auto-saves on view disappear)
try modelContext.save()

Predicates

Basic Comparisons

#Predicate<Track> { $0.duration > 180 }
#Predicate<Track> { $0.artist == "Artist Name" }
#Predicate<Track> { $0.genre != nil }

Compound Predicates

#Predicate<Track> { track in
    track.genre == "Rock" && track.duration > 180
}

#Predicate<Track> { track in
    track.artist == "Artist" || track.artist == "Other Artist"
}

String Matching

// Contains
#Predicate<Track> { track in
    track.title.contains("Love")
}

// Case-insensitive contains
#Predicate<Track> { track in
    track.title.localizedStandardContains("love")
}

// Starts with
#Predicate<Track> { track in
    track.artist.hasPrefix("The ")
}

Relationship Predicates

#Predicate<Track> { track in
    track.album?.title == "Album Name"
}

#Predicate<Album> { album in
    album.tracks.count > 10
}

Swift 6 Concurrency

@MainActor Isolation

import SwiftData

@MainActor
@Model
final class Track {
    var id: String
    var title: String

    init(id: String, title: String) {
        self.id = id
        self.title = title
    }
}

Why SwiftData models are not Sendable. Use @MainActor to ensure safe access from SwiftUI.

Background Context

import SwiftData

actor DataImporter {
    let modelContainer: ModelContainer

    init(container: ModelContainer) {
        self.modelContainer = container
    }

    func importTracks(_ tracks: [TrackData]) async throws {
        // Create background context
        let context = ModelContext(modelContainer)

        for track in tracks {
            let model = Track(
                id: track.id,
                title: track.title,
                artist: track.artist,
                duration: track.duration
            )
            context.insert(model)
        }

        try context.save()
    }
}

Pattern Use ModelContext(modelContainer) for background operations, not @Environment(\.modelContext) which is main-actor bound.

Calling from SwiftUI

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext

    var body: some View {
        Button("Import") {
            Task {
                let importer = DataImporter(container: modelContext.container)
                try await importer.importTracks(data)
            }
        }
    }
}

CloudKit Integration

Enable CloudKit Sync

let schema = Schema([Track.self])

let config = ModelConfiguration(
    schema: schema,
    cloudKitDatabase: .private("iCloud.com.example.MusicApp")
)

let container = try ModelContainer(
    for: schema,
    configurations: config
)

Capabilities Required

  1. Enable iCloud in Xcode (Signing & Capabilities)
  2. Select CloudKit
  3. Add iCloud container: iCloud.com.example.MusicApp

Note SwiftData CloudKit sync is automatic - no manual conflict resolution needed.

CloudKit Constraints (CRITICAL)

When using CloudKit sync, ALL properties must be optional or have default values

@Model
final class Track {
    var id: String = UUID().uuidString  // ✅ Has default (don't use .unique — CloudKit can't enforce it)
    var title: String = ""  // ✅ Has default
    var duration: TimeInterval = 0  // ✅ Has default
    var genre: String? = nil  // ✅ Optional

    // ❌ These don't work with CloudKit:
    // var requiredField: String  // No default, not optional
}

Why CloudKit only syncs to private zones, and network delays mean new records may not have all fields populated yet.

Relationship Constraint All relationships must be optional

@Model
final class Track {
    @Relationship(deleteRule: .cascade, inverse: \Album.tracks)
    var album: Album?  // ✅ Must be optional for CloudKit
}

Sync Status, Conflicts, Offline Handling

SwiftData CloudKit sync uses last-write-wins by default. For sync status monitoring, custom conflict resolution, and offline-aware UI patterns, see axiom-cloud-sync. For CKShare-based record sharing, see axiom-cloudkit-ref.

Resolving "Property must be optional or have default value" Error

Problem You get this error when trying to use CloudKit sync:

Property 'title' must be optional or have a default value for CloudKit synchronization

Solution

// ❌ Wrong - required property
@Model
final class Track {
    var title: String
}

// ✅ Correct - has default
@Model
final class Track {
    var title: String = ""
}

// ✅ Also correct - optional
@Model
final class Track {
    var title: String?
}

Testing CloudKit Sync (Without iCloud)

let schema = Schema([Track.self])

// Test configuration (no CloudKit sync)
let testConfig = ModelConfiguration(isStoredInMemoryOnly: true)

let container = try ModelContainer(for: schema, configurations: testConfig)

For real CloudKit testing

  1. Sign in to iCloud on test device
  2. Enable CloudKit in Capabilities
  3. Use real device (simulator CloudKit is unreliable)
  4. Check iCloud status in Settings → [Your Name] → iCloud

iOS 26+ Features

Enhanced Relationship Handling

@Model
final class Track {
    @Relationship(
        deleteRule: .cascade,
        inverse: \Album.tracks,
        minimum: 0,
        maximum: 1  // Track belongs to at most one album
    ) var album: Album?
}

Transient Properties

@Model
final class Track {
    var id: String
    var duration: TimeInterval

    @Transient
    var formattedDuration: String {
        let minutes = Int(duration) / 60
        let seconds = Int(duration) % 60
        return String(format: "%d:%02d", minutes, seconds)
    }
}

Transient Computed property, not persisted.

History Tracking

// Enable history tracking
let config = ModelConfiguration(
    schema: schema,
    cloudKitDatabase: .private("iCloud.com.example.app"),
    allowsSave: true,
    isHistoryEnabled: true  // iOS 26+
)

Performance Patterns

Batch Fetching

let descriptor = FetchDescriptor<Track>(
    sortBy: [SortDescriptor(\.title)]
)
descriptor.fetchLimit = 100  // Paginate results

let tracks = try modelContext.fetch(descriptor)

Prefetch Relationships (Prevent N+1 Queries)

let descriptor = FetchDescriptor<Track>()
descriptor.relationshipKeyPathsForPrefetching = [\.album]  // Eager load album

let tracks = try modelContext.fetch(descriptor)
// No N+1 queries - albums already loaded

CRITICAL Without prefetching, accessing track.album.title in a loop triggers individual queries for EACH track:

// ❌ SLOW: N+1 queries (1 fetch tracks + 100 fetch albums)
let tracks = try modelContext.fetch(FetchDescriptor<Track>())
for track in tracks {
    print(track.album?.title)  // 100 separate queries!
}

// ✅ FAST: 2 queries total (1 fetch tracks + 1 fetch all albums)
let descriptor = FetchDescriptor<Track>()
descriptor.relationshipKeyPathsForPrefetching = [\.album]
let tracks = try modelContext.fetch(descriptor)
for track in tracks {
    print(track.album?.title)  // Already loaded
}

Faulting (Lazy Loading)

SwiftData uses faulting (lazy loading) by default:

let track = tracks.first
// Album is a fault - not loaded yet

let albumTitle = track.album?.title
// Album loaded on access (separate query)

Use faulting strategically

  • ✅ Good when you access relationships in only 10-20% of cases
  • ✅ Good for large relationship graphs you partially use
  • ❌ Bad when you access relationships in loops → use prefetching instead

Batch Operations (Performance for Large Datasets)

// ❌ SLOW: 1000 individual saves
for track in largeDataset {
    track.genre = "Updated"
    try modelContext.save()  // Expensive - 1000 times
}

// ✅ FAST: Single save operation
for track in largeDataset {
    track.genre = "Updated"
}
try modelContext.save()  // Once for entire batch

Index Optimization (iOS 26+)

Create indexes on frequently queried properties:

@Model
final class Track {
    @Attribute(.unique) var id: String = UUID().uuidString

    @Attribute(.indexed)  // ✅ Add index
    var genre: String = ""

    @Attribute(.indexed)
    var releaseDate: Date = Date()

    var title: String = ""
    var duration: TimeInterval = 0
}

// Now these queries are faster:
@Query(filter: #Predicate { $0.genre == "Rock" }) var rockTracks: [Track]
@Query(filter: #Predicate { $0.releaseDate > Date() }) var upcomingTracks: [Track]

When to add indexes

  • ✅ Properties used in @Query filters frequently
  • ✅ Properties used in sort operations
  • ✅ Properties used in relationships
  • ❌ NOT properties that are rarely filtered
  • ❌ NOT properties that change frequently (maintenance cost)

Memory Optimization: Fetch Chunks

For very large datasets (100k+ records), fetch in chunks:

actor DataImporter {
    let modelContainer: ModelContainer

    func importLargeDataset(_ items: [Item]) async throws {
        let chunkSize = 1000
        let context = ModelContext(modelContainer)

        for chunk in items.chunked(into: chunkSize) {
            for item in chunk {
                let track = Track(
                    id: item.id,
                    title: item.title,
                    artist: item.artist,
                    duration: item.duration
                )
                context.insert(track)
            }

            try context.save()  // Save after each chunk

            // Prevent memory bloat
            context.delete(model: Track.self, where: #Predicate { _ in true })
        }
    }
}

extension Array {
    func chunked(into size: Int) -> [[Element]] {
        stride(from: 0, to: count, by: size).map {
            Array(self[$0..<Swift.min($0 + size, count)])
        }
    }
}

Avoiding Retain Cycles in CloudKit Sync

When using CloudKit, avoid capturing self in closures:

// ❌ Retain cycle with CloudKit sync
actor TrackManager {
    func startSync() {
        Task {
            for await notification in NotificationCenter.default
                .notifications(named: NSNotification.Name("CloudKitSyncDidComplete")) {
                self.refreshUI()  // Potential retain cycle
            }
        }
    }
}

// ✅ Proper weak capture
actor TrackManager {
    func startSync() {
        Task { [weak self] in
            guard let self else { return }
            for await notification in NotificationCenter.default
                .notifications(named: NSNotification.Name("CloudKitSyncDidComplete")) {
                await self.refreshUI()
            }
        }
    }
}

Common Patterns

Search

struct SearchableTracksView: View {
    @Query var tracks: [Track]
    @State private var searchText = ""

    var filteredTracks: [Track] {
        if searchText.isEmpty {
            return tracks
        }
        return tracks.filter { track in
            track.title.localizedStandardContains(searchText) ||
            track.artist.localizedStandardContains(searchText)
        }
    }

    var body: some View {
        List(filteredTracks) { track in
            Text(track.title)
        }
        .searchable(text: $searchText)
    }
}

Custom Sort

struct TracksView: View {
    @Query var tracks: [Track]
    @State private var sortOrder: SortOrder = .title

    enum SortOrder {
        case title, artist, duration
    }

    var sortedTracks: [Track] {
        switch sortOrder {
        case .title:
            return tracks.sorted { $0.title < $1.title }
        case .artist:
            return tracks.sorted { $0.artist < $1.artist }
        case .duration:
            return tracks.sorted { $0.duration < $1.duration }
        }
    }
}

Undo/Redo

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Environment(\.undoManager) private var undoManager

    func deleteTrack(_ track: Track) {
        modelContext.delete(track)

        // Undo is automatic with modelContext
        // Use Cmd+Z to undo
    }
}

Migration from Realm & Core Data

Key Differences at a Glance

ConceptRealmCore DataSwiftData
Model definitionObject subclass + @PersistedNSManagedObject + @NSManagedfinal class + @Model
Primary key@Persisted(primaryKey:)Entity inspector@Attribute(.unique)
ThreadingManual per-thread Realm instancescontext.perform {} blocksActor isolation + ModelContext(container)
RelationshipsRealmSwiftCollection<T>Entity editor + @NSManaged@Relationship with automatic inverses
Background workDispatchQueue + thread-local RealmnewBackgroundContext()actor + ModelContext(modelContainer)
Batch deleteLoop + realm.delete()NSBatchDeleteRequestcontext.delete(model:where:)
CloudKit syncRealm Sync (deprecated Sept 2025)NSPersistentCloudKitContainerModelConfiguration(cloudKitDatabase:)

Detailed Migration Guides

  • realm-to-swiftdata-migration — Complete Realm migration: pattern equivalents, thread safety conversion, relationship migration, CloudKit sync transition, timeline planning
  • axiom-swiftdata-migration — SwiftData schema evolution: VersionedSchema, SchemaMigrationPlan, lightweight vs custom migrations
  • axiom-database-migration — Safe additive migration patterns applicable to any persistence framework

Testing

Test Setup

import XCTest
import SwiftData
@testable import MusicApp

final class TrackTests: XCTestCase {
    var modelContext: ModelContext!

    override func setUp() async throws {
        let schema = Schema([Track.self])
        let config = ModelConfiguration(isStoredInMemoryOnly: true)
        let container = try ModelContainer(for: schema, configurations: config)
        modelContext = ModelContext(container)
    }

    func testInsertTrack() throws {
        let track = Track(id: "1", title: "Test", artist: "Artist", duration: 240)
        modelContext.insert(track)

        let descriptor = FetchDescriptor<Track>()
        let tracks = try modelContext.fetch(descriptor)

        XCTAssertEqual(tracks.count, 1)
        XCTAssertEqual(tracks.first?.title, "Test")
    }
}

Comparison: SwiftData vs SQLiteData

FeatureSwiftDataSQLiteData
TypeReference (class)Value (struct)
Macro@Model@Table
Queries@Query in SwiftUI@FetchAll / @FetchOne
Relationships@Relationship macroExplicit foreign keys
CloudKitAutomatic syncManual SyncEngine + sharing
BackendCore DataGRDB + SQLite
Learning CurveEasy (native)Moderate
PerformanceGoodExcellent (raw SQL)

tvOS

SwiftData on tvOS has no persistent local storage. tvOS has no Document directory, and Application Support maps to Caches — the system deletes files under storage pressure. A local-only SwiftData store will lose all data.

You must use CloudKit sync (cloudKitDatabase:.private(...)) for tvOS SwiftData apps. Without iCloud, user data does not survive between app launches. See axiom-tvos for full tvOS storage constraints.


Resources

Docs: /swiftdata, /swiftdata/adopting-inheritance-in-swiftdata

Skills: axiom-swiftdata-migration, axiom-swiftdata-migration-diag, axiom-database-migration, axiom-sqlitedata, axiom-grdb, axiom-swift-concurrency

Common Mistakes

❌ Forgetting explicit init

@Model
final class Track {
    var id: String
    var title: String
    // No init - won't compile
}

Fix Always provide init for @Model classes

❌ Using structs

@Model
struct Track { }  // Won't work - must be class

Fix Use final class not struct

❌ Background operations on main context

@Environment(\.modelContext) var context  // Main actor only

Task {
    // ❌ Crash - crossing actor boundaries
    context.insert(track)
}

Fix Use ModelContext(modelContainer) for background work

❌ Not saving when needed

modelContext.insert(track)
// Might not persist immediately

Fix Call try modelContext.save() for immediate persistence


Created 2025-11-28 Targets iOS 17+ (focus on iOS 26+ features) Framework SwiftData (Apple) Swift 5.9+ (Swift 6 concurrency patterns)

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

04

需要参考平台分布和安装热度时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Claude Code

26.76%
按下载量换算649

Codex

25.12%
按下载量换算609

OpenCode

16.42%
按下载量换算398

Antigravity

11.51%
按下载量换算279

Cursor

7.13%
按下载量换算173

Gemini CLI

3.65%
按下载量换算88

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

来源信息

继续浏览同类 Skills