Token导航 LogoToken导航

Agent Skill · 开发

swiftdata-persistence(快速数据持久化)

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

swiftdata-persistence 是开发类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为只读;许可证信息还不明确,第三方扫描已通过,按安装量在开发类的 21,746 个 Skill 里排第 20,391。

总安装 222周安装 9GitHub Stars 1下载 70核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

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

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

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/bluewaves-creations/bluewaves-skills --skill swiftdata-persistence

这个 Skill 是做什么的

开发类 Skill 面向写代码、改代码和排查问题,一般会读写仓库文件,部分还会执行构建或测试命令。

swiftdata-persistence 用于辅助数据整理、表格处理、CSV/Excel 分析、指标计算和图表准备。适合让 Agent 清洗字段、汇总数据、发现异常、生成统计口径或把分析结果转成可读说明。使用时需要确认数据来源、字段含义和时间范围,避免把样本数据当全量事实。

涉及敏感数据、导出文件或批量写回时,应先确认权限和脱敏边界。通过 npx skills add 命令从指定 GitHub 仓库安装并使用。

来源仓库内路径:skills/swiftdata-persistence

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/bluewaves-creations/bluewaves-skills --skill s…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它标注为只读,先用只读任务试一次,确认行为符合预期再扩大使用范围。

常见问题

6 条

swiftdata-persistence 怎么安装?

在终端执行 npx skills add https://github.com/bluewaves-creations/bluewaves-skills --skill swiftdata-p…。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

swiftdata-persistence 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

swiftdata-persistence 需要什么权限?

权限级别是只读。该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

swiftdata-persistence 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

swiftdata-persistence 用的人多吗?

公开数据是累计安装 222 次,近一周安装 9 次,下载 70 次,GitHub 1 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

swiftdata-persistence 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。
  • 标注为只读权限,试用阶段不容易误改本地文件。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理开发类任务、又不想每次都手写提示词的人。
  • 只想先试试效果、不希望它改动本地文件的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

开发类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
skill-vetter

技能审查员 · OpenClaw

8,779,018986操作浏览器MIT-0通过
ontology

本体论 · OpenClaw

6,636,960576只读MIT-0通过
entra-app-registration

内部应用程序注册 · Codex、Claude

6,631,872764接触敏感数据未明确通过
entra-app-registration

内部应用程序注册 · Codex、Claude

3,757,680201接触敏感数据未明确通过
nano-banana-pro

纳米香蕉专业版 · OpenClaw

3,526,898364接触敏感数据MIT-0有提醒
swiftdata-persistence

快速数据持久化 · 本页

2221只读未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

SwiftData Persistence

Comprehensive guide to SwiftData framework, the @Model macro, reactive queries, relationships, and native iCloud synchronization for iOS 26 development.

Prerequisites

  • iOS 17+ for SwiftData (iOS 26 recommended)
  • Xcode 26+

@Model Macro Basics

Defining a Model

import SwiftData

@Model
class Note {
    var title: String
    var content: String
    var createdAt: Date
    var isPinned: Bool

    init(title: String, content: String = "") {
        self.title = title
        self.content = content
        self.createdAt = Date()
        self.isPinned = false
    }
}

What @Model Provides

The @Model macro automatically:

  • Makes the class persistable
  • Tracks property changes
  • Enables SwiftUI observation
  • Generates schema metadata

Model Requirements

@Model
class Item {
    // All stored properties must be:
    // - Codable types (String, Int, Date, Data, etc.)
    // - Other @Model types (relationships)
    // - Arrays/optionals of the above

    var name: String           // ✓ Codable
    var count: Int             // ✓ Codable
    var timestamp: Date        // ✓ Codable
    var data: Data             // ✓ Codable
    var tags: [String]         // ✓ Array of Codable
    var metadata: [String: String]  // ✓ Dictionary of Codable
    var related: RelatedItem?  // ✓ Optional @Model relationship

    // Computed properties are NOT persisted
    var displayName: String {
        name.uppercased()
    }

    init(name: String) {
        self.name = name
        self.count = 0
        self.timestamp = Date()
        self.data = Data()
        self.tags = []
    }
}

Model Attributes

@Attribute Macro

@Model
class User {
    // Unique constraint (NOT compatible with iCloud sync)
    @Attribute(.unique)
    var email: String

    // Spotlight indexing
    @Attribute(.spotlight)
    var name: String

    // External storage for large data
    @Attribute(.externalStorage)
    var profileImage: Data?

    // Encryption (device-only, not synced to iCloud)
    @Attribute(.encrypt)
    var sensitiveData: String?

    // Preserve value when nil assigned
    @Attribute(.preserveValueOnDeletion)
    var archiveReason: String?

    // Ephemeral (not persisted)
    @Attribute(.ephemeral)
    var temporaryState: String?

    // Custom original name for migration
    @Attribute(originalName: "userName")
    var displayName: String

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

@Transient Macro

@Model
class Document {
    var title: String
    var content: String

    // Not persisted, recalculated
    @Transient
    var wordCount: Int = 0

    init(title: String, content: String) {
        self.title = title
        self.content = content
        self.wordCount = content.split(separator: " ").count
    }
}

Relationships

One-to-Many Relationship

@Model
class Folder {
    var name: String

    // One folder has many notes
    @Relationship(deleteRule: .cascade)
    var notes: [Note] = []

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

@Model
class Note {
    var title: String
    var content: String

    // Many notes belong to one folder (inverse)
    var folder: Folder?

    init(title: String, content: String = "", folder: Folder? = nil) {
        self.title = title
        self.content = content
        self.folder = folder
    }
}

Many-to-Many Relationship

@Model
class Note {
    var title: String

    // Note can have many tags
    @Relationship(inverse: \Tag.notes)
    var tags: [Tag] = []

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

@Model
class Tag {
    var name: String

    // Tag can be on many notes
    var notes: [Note] = []

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

Delete Rules

@Relationship(deleteRule: .cascade)   // Delete related objects
@Relationship(deleteRule: .nullify)   // Set relationship to nil (default)
@Relationship(deleteRule: .deny)      // Prevent deletion if related exist
@Relationship(deleteRule: .noAction)  // Do nothing

iCloud-Compatible Relationships

Important: For iCloud sync, all relationships MUST be optional:

@Model
class Note {
    var title: String

    // REQUIRED for iCloud: Optional relationships
    var folder: Folder?
    var tags: [Tag]?  // Optional array

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

ModelContainer Configuration

Basic Setup

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: [Note.self, Folder.self, Tag.self])
    }
}

Custom Configuration

@main
struct MyApp: App {
    let container: ModelContainer

    init() {
        let schema = Schema([Note.self, Folder.self, Tag.self])

        let config = ModelConfiguration(
            schema: schema,
            isStoredInMemoryOnly: false,
            allowsSave: true
        )

        do {
            container = try ModelContainer(for: schema, configurations: config)
        } catch {
            fatalError("Failed to configure SwiftData: \(error)")
        }
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(container)
    }
}

Multiple Configurations

let userConfig = ModelConfiguration(
    "UserData",
    schema: Schema([User.self]),
    url: userDataURL
)

let cacheConfig = ModelConfiguration(
    "Cache",
    schema: Schema([CachedItem.self]),
    isStoredInMemoryOnly: true
)

let container = try ModelContainer(
    for: Schema([User.self, CachedItem.self]),
    configurations: userConfig, cacheConfig
)

Native iCloud Sync

Enabling iCloud Sync (One Line!)

SwiftData includes native iCloud sync - no CloudKit code required:

@main
struct MyApp: App {
    let container: ModelContainer

    init() {
        let schema = Schema([Note.self, Tag.self])

        let config = ModelConfiguration(
            schema: schema,
            cloudKitDatabase: .automatic  // That's it!
        )

        do {
            container = try ModelContainer(for: schema, configurations: config)
        } catch {
            fatalError("Failed to configure SwiftData: \(error)")
        }
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(container)
    }
}

cloudKitDatabase Options

// Automatic iCloud sync (recommended)
cloudKitDatabase: .automatic

// Specific CloudKit container
cloudKitDatabase: .private("iCloud.com.yourcompany.yourapp")

// No iCloud sync (local only)
cloudKitDatabase: .none

Xcode Setup for iCloud

  1. Select your target in Xcode
  2. Go to "Signing & Capabilities"
  3. Click "+ Capability"
  4. Add "iCloud"
  5. Check "CloudKit"
  6. Select or create a CloudKit container
  7. Add "Background Modes" capability
  8. Check "Remote notifications"

iCloud-Compatible Model Requirements

Critical rules for iCloud sync:

@Model
class Note {
    // ✓ Default values for non-optional properties
    var title: String = ""
    var content: String = ""
    var createdAt: Date = Date()

    // ✓ Optional relationships
    var folder: Folder?
    var tags: [Tag]?

    // ✗ NO unique constraints (not supported by CloudKit)
    // @Attribute(.unique) var id: String  // DON'T DO THIS

    // ✗ NO deny delete rules
    // @Relationship(deleteRule: .deny)    // DON'T DO THIS

    init(title: String = "", content: String = "") {
        self.title = title
        self.content = content
        self.createdAt = Date()
    }
}

Schema Migration for iCloud

After shipping to production:

// DO:
// - Add new optional properties with defaults
// - Add new optional relationships

// DON'T:
// - Delete properties (data loss)
// - Rename properties (treated as delete + add)
// - Change property types
// - Add required properties without defaults

Initialize CloudKit Schema

Before first production release:

#if DEBUG
// Run once to create CloudKit schema
try container.mainContext.initializeCloudKitSchema()
#endif

@Query Macro

Basic Query

struct NotesListView: View {
    @Query var notes: [Note]

    var body: some View {
        List(notes) { note in
            Text(note.title)
        }
    }
}

Sorted Query

// Single sort
@Query(sort: \Note.createdAt, order: .reverse)
var notes: [Note]

// Multiple sorts
@Query(sort: [
    SortDescriptor(\Note.isPinned, order: .reverse),
    SortDescriptor(\Note.createdAt, order: .reverse)
])
var notes: [Note]

Filtered Query

// Static predicate
@Query(filter: #Predicate<Note> { note in
    note.isPinned == true
})
var pinnedNotes: [Note]

// Complex predicate
@Query(filter: #Predicate<Note> { note in
    note.title.contains("Swift") && !note.content.isEmpty
})
var swiftNotes: [Note]

Dynamic Filtering

struct SearchableNotesView: View {
    @State private var searchText = ""

    var body: some View {
        FilteredNotesView(searchText: searchText)
            .searchable(text: $searchText)
    }
}

struct FilteredNotesView: View {
    @Query var notes: [Note]

    init(searchText: String) {
        let predicate = #Predicate<Note> { note in
            searchText.isEmpty || note.title.localizedStandardContains(searchText)
        }
        _notes = Query(filter: predicate, sort: \.createdAt, order: .reverse)
    }

    var body: some View {
        List(notes) { note in
            Text(note.title)
        }
    }
}

Query with Limit

@Query(sort: \Note.createdAt, order: .reverse)
var recentNotes: [Note]

// In view, limit manually
List(recentNotes.prefix(10)) { note in
    Text(note.title)
}

Query Animations

@Query(sort: \Note.title, animation: .default)
var notes: [Note]

ModelContext Operations

Accessing Context

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

    // ...
}

Creating Objects

func createNote() {
    let note = Note(title: "New Note")
    modelContext.insert(note)
    // Auto-saved on SwiftUI lifecycle events
}

Explicit Save

func saveChanges() {
    do {
        try modelContext.save()
    } catch {
        print("Save failed: \(error)")
    }
}

Deleting Objects

func deleteNote(_ note: Note) {
    modelContext.delete(note)
}

func deleteNotes(at offsets: IndexSet) {
    for index in offsets {
        modelContext.delete(notes[index])
    }
}

Fetching with Descriptor

func fetchRecentNotes() throws -> [Note] {
    let descriptor = FetchDescriptor<Note>(
        predicate: #Predicate { $0.isPinned },
        sortBy: [SortDescriptor(\.createdAt, order: .reverse)]
    )
    return try modelContext.fetch(descriptor)
}

// With limit
func fetchTopNotes(limit: Int) throws -> [Note] {
    var descriptor = FetchDescriptor<Note>(
        sortBy: [SortDescriptor(\.createdAt, order: .reverse)]
    )
    descriptor.fetchLimit = limit
    return try modelContext.fetch(descriptor)
}

Batch Operations

// Delete all matching predicate
try modelContext.delete(model: Note.self, where: #Predicate { note in
    note.createdAt < cutoffDate
})

// Enumerate for batch processing
let descriptor = FetchDescriptor<Note>()
try modelContext.enumerate(descriptor) { note in
    note.processedAt = Date()
}

#Predicate Macro

Basic Predicates

// Equality
#Predicate<Note> { $0.isPinned == true }

// Comparison
#Predicate<Note> { $0.createdAt > someDate }

// String contains
#Predicate<Note> { $0.title.contains("Swift") }

// Case-insensitive contains
#Predicate<Note> { $0.title.localizedStandardContains(searchText) }

Compound Predicates

// AND
#Predicate<Note> { note in
    note.isPinned && note.title.contains("Important")
}

// OR
#Predicate<Note> { note in
    note.isPinned || note.folder?.name == "Favorites"
}

// NOT
#Predicate<Note> { note in
    !note.content.isEmpty
}

Optional Handling

#Predicate<Note> { note in
    note.folder?.name == "Work"
}

// Check for nil
#Predicate<Note> { note in
    note.folder != nil
}

Array Predicates

// Array contains
#Predicate<Note> { note in
    note.tags?.contains(where: { $0.name == "Important" }) ?? false
}

// Array is empty
#Predicate<Note> { note in
    note.tags?.isEmpty ?? true
}

Model Inheritance (iOS 26)

Base and Derived Models

@Model
class MediaItem {
    var title: String
    var createdAt: Date

    init(title: String) {
        self.title = title
        self.createdAt = Date()
    }
}

@Model
final class Photo: MediaItem {
    var imageData: Data?
    var resolution: String?

    init(title: String, imageData: Data?) {
        super.init(title: title)
        self.imageData = imageData
    }
}

@Model
final class Video: MediaItem {
    var duration: TimeInterval
    var thumbnailData: Data?

    init(title: String, duration: TimeInterval) {
        super.init(title: title)
        self.duration = duration
    }
}

Polymorphic Queries

// Query all media items (photos and videos)
@Query var allMedia: [MediaItem]

// Query only photos
@Query var photos: [Photo]

Background Operations

Background Context

func importData() async {
    let container = modelContainer

    await Task.detached {
        let context = ModelContext(container)

        // Perform operations
        for item in largeDataSet {
            let note = Note(title: item.title)
            context.insert(note)
        }

        try? context.save()
    }.value
}

Actor Isolation

@ModelActor
actor DataImporter {
    func importNotes(from data: [ImportData]) throws {
        for item in data {
            let note = Note(title: item.title, content: item.content)
            modelContext.insert(note)
        }
        try modelContext.save()
    }
}

// Usage
let importer = DataImporter(modelContainer: container)
try await importer.importNotes(from: importData)

Migration

Lightweight Migration (Automatic)

SwiftData handles lightweight migrations automatically:

  • Adding new properties with defaults
  • Removing properties
  • Adding optional relationships

Custom Migration

enum MySchemaV1: VersionedSchema {
    static var versionIdentifier = Schema.Version(1, 0, 0)

    static var models: [any PersistentModel.Type] {
        [Note.self]
    }

    @Model
    class Note {
        var title: String
        var content: String

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

enum MySchemaV2: VersionedSchema {
    static var versionIdentifier = Schema.Version(2, 0, 0)

    static var models: [any PersistentModel.Type] {
        [Note.self]
    }

    @Model
    class Note {
        var title: String
        var content: String
        var createdAt: Date  // New property

        init(title: String, content: String) {
            self.title = title
            self.content = content
            self.createdAt = Date()
        }
    }
}

enum MyMigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] {
        [MySchemaV1.self, MySchemaV2.self]
    }

    static var stages: [MigrationStage] {
        [migrateV1toV2]
    }

    static let migrateV1toV2 = MigrationStage.lightweight(
        fromVersion: MySchemaV1.self,
        toVersion: MySchemaV2.self
    )
}

// Use in container
let container = try ModelContainer(
    for: Note.self,
    migrationPlan: MyMigrationPlan.self
)

Testing

In-Memory Testing

import Testing
import SwiftData

@Test
func testNoteCreation() throws {
    let config = ModelConfiguration(isStoredInMemoryOnly: true)
    let container = try ModelContainer(for: Note.self, configurations: config)
    let context = ModelContext(container)

    let note = Note(title: "Test", content: "Content")
    context.insert(note)

    let descriptor = FetchDescriptor<Note>()
    let notes = try context.fetch(descriptor)

    #expect(notes.count == 1)
    #expect(notes.first?.title == "Test")
}

SwiftUI Preview with Sample Data

@MainActor
let previewContainer: ModelContainer = {
    let config = ModelConfiguration(isStoredInMemoryOnly: true)
    let container = try! ModelContainer(for: Note.self, configurations: config)

    // Insert sample data
    let context = container.mainContext
    let sampleNotes = [
        Note(title: "First Note", content: "Content 1"),
        Note(title: "Second Note", content: "Content 2")
    ]
    sampleNotes.forEach { context.insert($0) }

    return container
}()

#Preview {
    NotesListView()
        .modelContainer(previewContainer)
}

Best Practices

1. Design for iCloud from the Start

// GOOD: iCloud-compatible model
@Model
class Note {
    var title: String = ""
    var content: String = ""
    var folder: Folder?      // Optional relationship
    var tags: [Tag]?         // Optional array

    init(title: String = "") {
        self.title = title
    }
}

// AVOID: iCloud-incompatible
@Model
class Note {
    @Attribute(.unique) var id: String  // Not supported
    var folder: Folder                   // Non-optional relationship
}

2. Use @Query for Reactive Data

// GOOD: Reactive updates
@Query(sort: \Note.createdAt)
var notes: [Note]

// AVOID: Manual fetching in views
@State private var notes: [Note] = []
func loadNotes() {
    notes = try? context.fetch(FetchDescriptor<Note>())
}

3. Explicit Save for Critical Data

func saveImportantChange() {
    modelContext.insert(criticalData)
    do {
        try modelContext.save()
    } catch {
        // Handle error appropriately
    }
}

4. Use Background Contexts for Heavy Work

func importLargeDataset() async {
    await Task.detached {
        let context = ModelContext(container)
        // Heavy operations
        try? context.save()
    }.value
}

Official Resources

平台分布

Codex

38.78%
按下载量换算27

Claude

31.56%
按下载量换算22

Cursor

16.89%
按下载量换算12

Gemini CLI

8.67%
按下载量换算6

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

只读

该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills