|
| 1 | +# ``CoreDataEvolution`` |
| 2 | + |
| 3 | +Revolutionizing Core Data with SwiftData-inspired Concurrent Operations |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +CoreDataEvolution is a library aimed at modernizing Core Data by incorporating the elegance and safety of SwiftData-style concurrency. This library is designed to simplify and enhance Core Data's handling of multithreading, drawing inspiration from SwiftData's `@ModelActor` feature, enabling efficient, safe, and scalable operations. |
| 8 | + |
| 9 | +## Motivation |
| 10 | + |
| 11 | +SwiftData introduced modern concurrency features like `@ModelActor`, making it easier to handle concurrent data access with safety guaranteed by the compiler. However, SwiftData's platform requirements and limited maturity in certain areas have deterred many developers from adopting it. CoreDataEvolution bridges the gap, bringing SwiftData's advanced design into the Core Data world for developers who are still reliant on Core Data. |
| 12 | + |
| 13 | +## Key Features |
| 14 | + |
| 15 | +### Custom Executors for Core Data Actors |
| 16 | + |
| 17 | +Using Swift 5.9's new `SerialExecutor` and `ExecutorJob` protocols, CoreDataEvolution provides custom executors that ensure all operations on managed objects are performed on the appropriate thread associated with their managed object context. |
| 18 | + |
| 19 | +### @NSModelActor Macro |
| 20 | + |
| 21 | +The `@NSModelActor` macro simplifies Core Data concurrency, mirroring SwiftData's `@ModelActor` macro. It generates the necessary boilerplate code to manage a Core Data stack within an actor, ensuring safe and efficient access to managed objects. |
| 22 | + |
| 23 | +### NSMainModelActor Macro |
| 24 | + |
| 25 | +`NSMainModelActor` provides the same functionality as `NSModelActor`, but is used to declare a class that runs on the main thread. |
| 26 | + |
| 27 | +### Elegant Actor-based Concurrency |
| 28 | + |
| 29 | +CoreDataEvolution allows you to create actors with custom executors tied to Core Data contexts, ensuring that all operations within the actor are executed serially on the context's thread. |
| 30 | + |
| 31 | +## Basic Usage |
| 32 | + |
| 33 | +Here's how you can use CoreDataEvolution to manage concurrent Core Data operations with an actor: |
| 34 | + |
| 35 | +```swift |
| 36 | +import CoreDataEvolution |
| 37 | + |
| 38 | +@NSModelActor |
| 39 | +actor DataHandler { |
| 40 | + func updateItem(identifier: NSManagedObjectID, timestamp: Date) throws { |
| 41 | + guard let item = self[identifier, as: Item.self] else { |
| 42 | + throw MyError.objectNotExist |
| 43 | + } |
| 44 | + item.timestamp = timestamp |
| 45 | + try modelContext.save() |
| 46 | + } |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +In this example, the `@NSModelActor` macro simplifies the setup, automatically creating the required executor and Core Data stack inside the actor. Developers can then focus on their business logic without worrying about concurrency pitfalls. |
| 51 | + |
| 52 | +## Advanced Usage |
| 53 | + |
| 54 | +### Custom Initialization |
| 55 | + |
| 56 | +You can disable the automatic generation of the constructor by using `disableGenerateInit`: |
| 57 | + |
| 58 | +```swift |
| 59 | +@NSModelActor(disableGenerateInit: true) |
| 60 | +public actor DataHandler { |
| 61 | + let viewName: String |
| 62 | + |
| 63 | + func createNewItem(_ timestamp: Date = .now, showThread: Bool = false) throws -> NSManagedObjectID { |
| 64 | + let item = Item(context: modelContext) |
| 65 | + item.timestamp = timestamp |
| 66 | + try modelContext.save() |
| 67 | + return item.objectID |
| 68 | + } |
| 69 | + |
| 70 | + init(container: NSPersistentContainer, viewName: String) { |
| 71 | + modelContainer = container |
| 72 | + self.viewName = viewName |
| 73 | + let context = container.newBackgroundContext() |
| 74 | + context.name = viewName |
| 75 | + modelExecutor = .init(context: context) |
| 76 | + } |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +### Main Thread Operations |
| 81 | + |
| 82 | +NSMainModelActor provides the same functionality as NSModelActor, but for operations that need to run on the main thread: |
| 83 | + |
| 84 | +```swift |
| 85 | +@MainActor |
| 86 | +@NSMainModelActor |
| 87 | +final class DataHandler { |
| 88 | + func updateItem(identifier: NSManagedObjectID, timestamp: Date) throws { |
| 89 | + guard let item = self[identifier, as: Item.self] else { |
| 90 | + throw MyError.objectNotExist |
| 91 | + } |
| 92 | + item.timestamp = timestamp |
| 93 | + try modelContext.save() |
| 94 | + } |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +## Installation |
| 99 | + |
| 100 | +Add CoreDataEvolution to your project using Swift Package Manager: |
| 101 | + |
| 102 | +```swift |
| 103 | +dependencies: [ |
| 104 | + .package(url: "https://github.com/fatbobman/CoreDataEvolution.git", .upToNextMajor(from: "0.3.0")) |
| 105 | +] |
| 106 | +``` |
| 107 | + |
| 108 | +Then import the module: |
| 109 | + |
| 110 | +```swift |
| 111 | +import CoreDataEvolution |
| 112 | +``` |
| 113 | + |
| 114 | +## System Requirements |
| 115 | + |
| 116 | +- iOS 17.0+ / macOS 14.0+ / watchOS 10.0+ / visionOS 1.0+ / tvOS 17.0+ |
| 117 | +- Swift 6.0 |
| 118 | + |
| 119 | +> Important: Due to system limitations, custom executors and `SerialExecutor` are only available on iOS 17/macOS 14 and later. |
| 120 | +
|
| 121 | +## Topics |
| 122 | + |
| 123 | +### Actors |
| 124 | + |
| 125 | +- ``NSModelActor`` |
| 126 | +- ``NSMainModelActor`` |
| 127 | + |
| 128 | +### Executors |
| 129 | + |
| 130 | +- Custom executors for Core Data contexts |
| 131 | +- Thread-safe operations |
| 132 | + |
| 133 | +### Migration |
| 134 | + |
| 135 | +- Migrating from traditional Core Data patterns |
| 136 | +- SwiftData compatibility considerations |
| 137 | + |
0 commit comments