SwiftData Pro

repository·main·Indexed 18 days ago

https://github.com/twostraws/swiftdata-agent-skill

An agent skill for AI coding assistants (Claude Code, Codex, Cursor, Gemini) designed to improve SwiftData code generation. It provides guidance on @Model, @Query, predicates, migrations, iCloud sync, and class inheritance for iOS 26+, as well as best practices for ModelContext saving, actor boundaries, and indexing.

Tokens
4.5K
Snippets
13
Records
23
Agent score
63%

What's inside swiftdata-pro

  1. Use the swiftdata-pro agent skill

    main

    The swiftdata-pro skill is designed to write, review, and improve SwiftData code. It focuses on correctness, modern API usage (Swift 6.2+), and adherence to project conventions.

    When to use it

    Use this skill when you need an agent to:

    • Write new SwiftData models, views, or logic.
    • Review existing SwiftData implementations for bugs or anti-patterns.
    • Improve code to leverage modern features like iOS 18+ indexing or iOS 26+ class inheritance.

    Core Principles

    • SwiftData First: The skill prefers SwiftData over Core Data. Core Data is only suggested if a feature is impossible in SwiftData.
    • Modern Concurrency: Targets Swift 6.2 or later using modern Swift concurrency patterns.
    • No Unasked Dependencies: Does not introduce third-party frameworks without explicit permission.
    • No Nitpicking: Reports only genuine problems and avoids inventing non-issues.
  2. Configure `@Relationship` and avoid circular references

    main

    When defining relationships between models:

    • Avoid Circularity: Place the @Relationship macro on only one side of the relationship. Using it on both sides causes a circular reference error.
    • Specify Inverses: SwiftData often misidentifies inverse relationships. Always be explicit by specifying the exact inverse relationship within the @Relationship macro.
    • Define Delete Rules: Always specify an explicit deleteRule. The default is .nullify (which sets references to nil), but .cascade is common for parent-child relationships. Using .nullify on non-optional properties can cause crashes or orphaned objects.
    // Example of explicit relationship with a delete rule
    @Relationship(deleteRule: .cascade, inverse: \.items) 
    var items: [Item]
  3. Usage of `@Query` and `fetchCount()`

    main

    @Query

    @Query is designed specifically for use inside SwiftUI views. Do not attempt to use it in other contexts (like services or actors) as it will not operate correctly.

    ModelContext.fetchCount()

    If you only need to know how many items match a specific criteria, use fetchCount(_:) with a FetchDescriptor.

    • Warning: Unlike @Query, fetchCount() does not live-update when the underlying data changes unless an external trigger (like a @Query update) occurs.
  4. Manage SwiftData persistence and saving

    main

    SwiftData's autosave behavior is infrequent and unpredictable. To ensure data correctness, always use explicit calls to save() on your ModelContext. You do not need to check modelContext.hasChanges before calling save(); you can call it directly.

    Important Note on Identifiers: Persistent identifiers are temporary (starting with a lowercase "t") until the object is saved for the first time. You must call save() before relying on a model's permanent ID.

    // Explicitly save to ensure correctness
    try modelContext.save()
  5. Define `@Transient` and `@Attribute` properties

    main

    @Transient Properties

    Properties marked with @Transient are not persisted to the store. They must have a default value and will reset to that default whenever the object is fetched.

    • Best Practice: Use @Transient only for values that are expensive to compute. For values derived from other stored properties, use a computed property instead.

    @Attribute(.externalStorage)

    This attribute is a suggestion to SwiftData for properties of type Data. SwiftData will decide whether to actually use external storage based on its own internal logic.

  6. Install SwiftData Pro via npx

    main

    You can install the swiftdata-pro skill into AI coding assistants like Claude Code, Codex, Cursor, and Gemini using npx. During installation, you can choose to install the skill for a specific project or make it available globally across all projects.

    npx skills add https://github.com/twostraws/swiftdata-agent-skill --skill swiftdata-pro
  7. Filter SwiftData models using subclasses

    main

    You can use @Query and #Predicate to filter data based on model inheritance:

    • Querying specific subclasses: Use the subclass type directly to fetch only those instances: @Query private var tutorials: [Tutorial].
    • Querying the base class: Fetching the parent class automatically includes all child class instances: @Query private var articles: [Article].
    • Filtering for multiple specific subclasses: Use the is keyword within a #Predicate to include specific children but exclude the parent: filter: #Predicate<Article> { $0 is Tutorial || $0 is News }.
    • Filtering by subclass properties: You can perform typecasting inside a #Predicate to access properties unique to a subclass.

    Important: When querying the parent class (even with a filter), the resulting array elements are of the parent type. You must use Swift typecasting (as?) to access child-specific properties or methods in your view or logic.

    // Load only tutorials
    @Query private var tutorials: [Tutorial]
    
    // Load all articles (including tutorials and news)
    @Query private var articles: [Article]
    
    // Load specific child classes using 'is'
    @Query(filter: #Predicate<Article> { 
        $0 is Tutorial || $0 is News 
    }) private var tutorialsAndNews: [Article]
    
    // Filter based on child-class properties
    @Query(filter: #Predicate<Article> { article in
        if let tutorial = article as? Tutorial {
            tutorial.difficulty < 3
        } else if let news = article as? News {
            news.topic == "General"
        } else {
            false
        }
    }) private var frontPageArticles: [Article]
  8. Add indexes to SwiftData models

    main

    To speed up queries in SwiftData (iOS 18+), you can add indexes to your @Model classes using the #Index<T> macro.

    Usage Patterns

    1. Single Property Indexes: Use this when you frequently query by a specific field.
    2. Compound/Grouped Indexes: Use this when you frequently query using a combination of properties. You can mix single properties and property groups within the same macro call.

    Performance Considerations

    Indexes improve read performance for queries but introduce a small performance cost for write operations. Avoid using indexes for data that is updated frequently but rarely read (e.g., logging data).

    // Single property indexes
    @Model class Article {
        #Index<Article>([\.type], [\.author])
    
        var type: String
        var author: String
        var publishDate: Date
    
        init(type: String, author: String, publishDate: Date) {
            self.type = type
            self.author = author
            self.publishDate = publishDate
        }
    }
    
    // Mixing single properties and groups of properties
    #Index<Article>([\.type], [\.type, \.author])
  9. Trigger SwiftData Pro in Claude Code and Codex

    main

    The swiftdata-pro skill can be invoked using specific commands or natural language instructions.

    Claude Code: Use the /swiftdata-pro command. Codex: Use the $swiftdata-pro command.

    You can append specific instructions to these commands to perform targeted reviews, such as checking for latest API usage or index placement.

    # Claude Code
    /swiftdata-pro Check my code for latest API usage
    
    # Codex
    $swiftdata-pro Check where indexes should be added to my SwiftData models
    
    # Natural Language (Any supported agent)
    Use the SwiftData Pro skill to enable iCloud support in this project.