DataThespian is a thread-safe SwiftData implementation that uses the power of ModelActors to provide an optimized and type-safe database interface. It offers a clean API for common database operations while maintaining concurrency safety and preventing common SwiftData pitfalls.
Key features:
- Thread-safe database operations using ModelActor
- Type-safe query interface with Selectors
- SwiftUI integration via Environment
- Support for monitoring database changes
- Collection synchronization utilities
Apple Platforms
- Xcode 16.0 or later
- Swift 6.0 or later
- iOS 17 / watchOS 10.0 / tvOS 17 / macOS 14 or later deployment targets
Linux
- Ubuntu 20.04 or later
- Swift 6.0 or later
To integrate DataThespian into your app using SPM, specify it in your Package.swift file:
let package = Package(
...
dependencies: [
.package(url: "https://github.com/brightdigit/DataThespian.git", from: "1.0.0")
],
targets: [
.target(
name: "YourApps",
dependencies: [
.product(name: "DataThespian", package: "DataThespian"), ...
]),
...
]
)
When working with SwiftData, it's crucial to use a single ModelContext
throughout your app. There are two ways to create your database:
// Create a database using the built-in ModelActorDatabase
let database = ModelActorDatabase(modelContainer: container)
You can also create your own database type by implementing the Database
protocol:
@ModelActor
actor CustomDatabase: Database {
}
To avoid issues with multiple ModelContexts being created each time SwiftUI redraws views, use SharedDatabase
to ensure a single shared context:
public struct SharedDatabase {
public static let shared: SharedDatabase = .init()
public let schemas: [any PersistentModel.Type]
public let modelContainer: ModelContainer
public let database: any Database
private init(
schemas: [any PersistentModel.Type] = .all,
modelContainer: ModelContainer? = nil,
database: (any Database)? = nil
) {
self.schemas = schemas
let modelContainer = modelContainer ?? .forTypes(schemas)
self.modelContainer = modelContainer
self.database = database ?? ModelActorDatabase(modelContainer: modelContainer)
}
}
Then set up the database in your SwiftUI app:
var body: some Scene {
WindowGroup {
RootView()
}
.database(SharedDatabase.shared.database)
/* If you need @Query support
.modelContainer(SharedDatabase.shared.modelContainer)
*/
}
Access the database in your views using the environment:
@Environment(\.database) private var database
DataThespian provides a type-safe way to query your data:
// Fetch a single item
let item = try await database.get(for: .predicate(#Predicate<Item> {
$0.name == "Test"
}))
// Fetch multiple items with sorting
let items = await database.fetch(for: .descriptor(
predicate: #Predicate<Item> { $0.isActive },
sortBy: [SortDescriptor(\Item.timestamp, order: .reverse)]
))
// Insert new item
let timestamp = Date()
let newItem = await database.insert {
Item(name: "Test", timestamp: timestamp)
}
// Save changes
try await database.save()
// Re-query after save using a unique field
let savedItem = try await database.getOptional(for: .predicate(#Predicate<Item> {
$0.timestamp == timestamp
}))
To learn more, check out the full documentation.
This code is distributed under the MIT license. See the LICENSE file for more info.