ModelActorX

0.4.0

ModelActorX is a Swift library that provides custom macros ModelActorX and MainModelActorX to enhance and extend the functionality of SwiftData's ModelActor.
fatbobman/ModelActorX

What's New

0.4.0

2024-11-01T06:50:18Z

Using ModelActorX with MainActor

@ModelActorX also provides a constructor declared with @MainActor. When you use this constructor to generate an actor, it will directly utilize the mainContext (view context), and the entire actor will run on the main thread. The key difference from @MainModelActorX is that the type remains an actor. This means that existing code built upon ModelActor does not require modification—the calls will still retain await.

This approach might be an ideal temporary solution before iOS 18 addresses the responsiveness issues related to updates using @ModelActor.

@ModelActorX
actor DataHandler {}

Task{ @MainActor in
   let handler = DataHandler(mainContext: container.mainContext) // Use the view context for construction
   await handler.updateItem(id: id) // Even on the main thread, you can still use `await`
}

ModelActorX

Swift 6 License: MIT

ModelActorX is a Swift library that provides custom macros ModelActorX and MainModelActorX to enhance and extend the functionality of SwiftData's ModelActor. These macros offer additional flexibility by allowing developers to control the generation of initializers and to declare additional variables within actors and classes.


Don't miss out on the latest updates and excellent articles about Swift, SwiftUI, Core Data, and SwiftData. Subscribe to Fatbobman's Swift Weekly and receive weekly insights and valuable content directly to your inbox.


Features

  • ModelActorX Macro: Similar to SwiftData's ModelActor, with an added disableGenerateInit parameter to control initializer generation.
  • MainModelActorX Macro: Generates a class running on the MainActor, ideal for main-thread operations, and uses ModelContainer's mainContext.
  • Custom Initializers: Ability to declare additional variables and pass them through custom initializers when disableGenerateInit is set to true.
  • Seamless Integration: Designed to work seamlessly with SwiftData and existing Swift projects.

Installation

Swift Package Manager

Add ModelActorX to your project using Swift Package Manager. In your Package.swift file, add:

dependencies: [
    .package(url: "https://github.com/fatbobman/ModelActorX.git", from: "0.1.0")
]

Alternatively, in Xcode:

  1. Go to File > Add Packages...
  2. Enter the repository URL: https://github.com/fatbobman/ModelActorX.git
  3. Follow the prompts to add the package to your project.

Usage

ModelActorX

The ModelActorX macro is used to define an actor with functionality similar to SwiftData's ModelActor. The key difference is the disableGenerateInit parameter, which, when set to true, prevents the automatic generation of an initializer. This allows you to declare additional variables and provide a custom initializer.

Basic Usage

@ModelActorX
actor DataHandler {
    func newItem(date: Date) throws -> PersistentIdentifier {
        let item = Item(timestamp: date)
        modelContext.insert(item)
        try modelContext.save()
        return item.persistentModelID
    }

    func getTimestampFromItemID(_ itemID: PersistentIdentifier) -> Date? {
        return self[itemID, as: Item.self]?.timestamp
    }
}

Custom Initializer

@ModelActorX(disableGenerateInit: true)
actor DataHandler1 {
    let date: Date

    func newItem() throws -> PersistentIdentifier {
        let item = Item(timestamp: date)
        modelContext.insert(item)
        try modelContext.save()
        return item.persistentModelID
    }

    func getTimestampFromItemID(_ itemID: PersistentIdentifier) -> Date? {
        return self[itemID, as: Item.self]?.timestamp
    }

    init(container: ModelContainer, date: Date) {
        self.date = date
        modelContainer = container
        let modelContext = ModelContext(modelContainer)
        modelExecutor = DefaultSerialModelExecutor(modelContext: modelContext)
    }
}

MainModelActorX

The MainModelActorX macro is used to generate a class that runs on the MainActor. This is particularly useful for UI updates or any operations that need to be performed on the main thread. The generated class uses the mainContext from ModelContainer.

Basic Usage

@MainActor
@MainModelActorX
final class MainDataHandler {
    func newItem(date: Date) throws -> PersistentIdentifier {
        let item = Item(timestamp: date)
        modelContext.insert(item)
        try modelContext.save()
        return item.persistentModelID
    }

    func getTimestampFromItemID(_ itemID: PersistentIdentifier) -> Date? {
        return self[itemID, as: Item.self]?.timestamp
    }
}

Custom Initializer

@MainActor
@MainModelActorX(disableGenerateInit: true)
final class MainDataHandler1 {
    let date: Date

    func newItem() throws -> PersistentIdentifier {
        let item = Item(timestamp: date)
        modelContext.insert(item)
        try modelContext.save()
        return item.persistentModelID
    }

    func getTimestampFromItemID(_ itemID: PersistentIdentifier) -> Date? {
        return self[itemID, as: Item.self]?.timestamp
    }

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

Using ModelActorX with MainActor

@ModelActorX also provides a constructor declared with @MainActor. When you use this constructor to generate an actor, it will directly utilize the mainContext (view context), and the entire actor will run on the main thread. The key difference from @MainModelActorX is that the type remains an actor. This means that existing code built upon ModelActor does not require modification—the calls will still retain await.

This approach might be an ideal temporary solution before iOS 18 addresses the responsiveness issues related to updates using @ModelActor.

@ModelActorX
actor DataHandler {}

Task{ @MainActor in
   let handler = DataHandler(mainContext: container.mainContext) // Use the view context for construction
   await handler.updateItem(id: id) // Even on the main thread, you can still use `await`
}

Testing Examples

ModelActorXTests

struct ModelActorXTests {
    @Test func example1() async throws {
        let container = createContainer()
        let handler = DataHandler(modelContainer: container)
        let now = Date.now
        let id = try await handler.newItem(date: now)
        let date = await handler.getTimestampFromItemID(id)
        #expect(date == now)
    }

    @Test func example2() async throws {
        let container = createContainer()
        let now = Date.now
        let handler = DataHandler1(container: container, date: now)
        let id = try await handler.newItem()
        let date = await handler.getTimestampFromItemID(id)
        #expect(date == now)
    }
}

MainModelActorXTests

@MainActor
struct MainModelActorXTests {
    @Test
    func test1() async throws {
        let container = createContainer()
        let handler = MainDataHandler(modelContainer: container)
        let now = Date.now
        let id = try handler.newItem(date: now)
        let date = handler.getTimestampFromItemID(id)
        #expect(date == now)
    }

    @Test
    func test2() async throws {
        let container = createContainer()
        let now = Date.now
        let handler = MainDataHandler1(container: container, date: now)
        let id = try handler.newItem()
        let date = handler.getTimestampFromItemID(id)
        #expect(date == now)
    }
}

Parameters

ModelActorX Macro

  • disableGenerateInit: Bool (optional): Controls whether an initializer is automatically generated. Default is false.

MainModelActorX Macro

  • disableGenerateInit: Bool (optional): Controls whether an initializer is automatically generated. Default is false.

Requirements

  • Swift 6
  • iOS 17.0 / macOS 14 / tvOS 17 / watchOS 10 or later

License

ModelActorX is released under the MIT License. See LICENSE for details.

Acknowledgements

  • Inspired by SwiftData's ModelActor functionality.
  • Core Data Version: CoreDataEvolution
  • Thanks to the Swift community for continuous support and contributions.

Contributing

Contributions are welcome! If you have ideas for improvements or find bugs, please open an issue or submit a pull request.

Buy Me A Coffee

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

Last updated: Tue May 13 2025 22:36:57 GMT-0900 (Hawaii-Aleutian Daylight Time)