Unleashing the real power of Core Data with the elegance and safety of Swift
CoreStore
Unleashing the real power of Core Data with the elegance and safety of Swift.
- Swift 5.4: iOS 11+ / macOS 10.13+ / watchOS 4.0+ / tvOS 11.0+
- Previously supported Swift versions: Swift 3.2, Swift 4.2, Swift 5.0, Swift 5.1, Swift 5.3
TL;DR (a.k.a. sample codes)
Pure-Swift models:
class Person: CoreStoreObject {
@Field.Stored("name")
var name: String = ""
@Field.Relationship("pets", inverse: \Dog.$master)
var pets: Set<Dog>
}
(Classic NSManagedObject
s also supported)
Setting-up with progressive migration support:
dataStack = DataStack(
xcodeModelName: "MyStore",
migrationChain: ["MyStore", "MyStoreV2", "MyStoreV3"]
)
Adding a store:
dataStack.addStorage(
SQLiteStore(fileName: "MyStore.sqlite"),
completion: { (result) -> Void in
// ...
}
)
Starting transactions:
dataStack.perform(
asynchronous: { (transaction) -> Void in
let person = transaction.create(Into<Person>())
person.name = "John Smith"
person.age = 42
},
completion: { (result) -> Void in
switch result {
case .success: print("success!")
case .failure(let error): print(error)
}
}
)
Fetching objects (simple):
let people = try dataStack.fetchAll(From<Person>())
Fetching objects (complex):
let people = try dataStack.fetchAll(
From<Person>()
.where(\.age > 30),
.orderBy(.ascending(\.name), .descending(.\age)),
.tweak({ $0.includesPendingChanges = false })
)
Querying values:
let maxAge = try dataStack.queryValue(
From<Person>()
.select(Int.self, .maximum(\.age))
)
But really, there's a reason I wrote this huge README. Read up on the details!
Check out the Demo app project for sample codes as well!
Why use CoreStore?
CoreStore was (and is) heavily shaped by real-world needs of developing data-dependent apps. It enforces safe and convenient Core Data usage while letting you take advantage of the industry's encouraged best practices.
Features
- ?SwiftUI and Combine API utilities.
ListPublisher
s andObjectPublisher
s now have their@ListState
and@ObjectState
SwiftUI property wrappers. CombinePublisher
s are also available through theListPublisher.reactive
,ObjectPublisher.reactive
, andDataStack.reactive
namespaces. - Backwards-portable DiffableDataSources implementation!
UITableViews
andUICollectionViews
now have a new ally:ListPublisher
s provide diffable snapshots that make reloading animations very easy and very safe. Say goodbye toUITableViews
andUICollectionViews
reload errors! - ?Tight design around Swift’s code elegance and type safety. CoreStore fully utilizes Swift's community-driven language features.
- ?Safer concurrency architecture. CoreStore makes it hard to fall into common concurrency mistakes. The main
NSManagedObjectContext
is strictly read-only, while all updates are done through serial transactions. (See Saving and processing transactions) - ?Clean fetching and querying API. Fetching objects is easy, but querying for raw aggregates (
min
,max
, etc.) and raw property values is now just as convenient. (See Fetching and querying) - ?Type-safe, easy to configure observers. You don't have to deal with the burden of setting up
NSFetchedResultsController
s and KVO. As an added bonus, list and object observable types all support multiple observers. This means you can have multiple view controllers efficiently share a single resource! (See Observing changes and notifications) - ?Efficient importing utilities. Map your entities once with their corresponding import source (JSON for example), and importing from transactions becomes elegant. Uniquing is also done with an efficient find-and-replace algorithm. (See Importing data)
- ?Say goodbye to .xcdatamodeld files! While CoreStore supports
NSManagedObject
s, it offersCoreStoreObject
whose subclasses can declare type-safe properties all in Swift code without the need to maintain separate resource files for the models. As bonus, these special properties support custom types, and can be used to create type-safe keypaths and queries. (See Type-safeCoreStoreObject
s) - ?Progressive migrations. No need to think how to migrate from all previous model versions to your latest model. Just tell the
DataStack
the sequence of version strings (MigrationChain
s) and CoreStore will automatically use progressive migrations when needed. (See Migrations) - Easier custom migrations. Say goodbye to .xcmappingmodel files; CoreStore can now infer entity mappings when possible, while still allowing an easy way to write custom mappings. (See Migrations)
- ?Plug-in your own logging framework. Although a default logger is built-in, all logging, asserting, and error reporting can be funneled to
CoreStoreLogger
protocol implementations. (See Logging and error reporting) - ⛓Heavy support for multiple persistent stores per data stack. CoreStore lets you manage separate stores in a single
DataStack
, just the way .xcdatamodeld configurations are designed to. CoreStore will also manage one stack by default, but you can create and manage as many as you need. (See Setting up) - ?Free to name entities and their class names independently. CoreStore gets around a restriction with other Core Data wrappers where the entity name should be the same as the
NSManagedObject
subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you can assign independent names for the entities and their class names. - ?Full Documentation. No magic here; all public classes, functions, properties, etc. have detailed Apple Docs. This README also introduces a lot of concepts and explains a lot of CoreStore's behavior.
- ℹ️Informative (and pretty) logs. All CoreStore and Core Data-related types now have very informative and pretty print outputs! (See Logging and error reporting)
- ?Objective-C support! Is your project transitioning from Objective-C to Swift but still can't quite fully convert some huge classes to Swift yet? CoreStore adjusts to the ever-increasing Swift adoption. While still written in pure Swift, all CoreStore types have their corresponding Objective-C-visible "bridging classes". (See Objective-C support)
- ?More extensive Unit Tests. Extending CoreStore is safe without having to worry about breaking old behavior.
Have ideas that may benefit other Core Data users? Feature Requests are welcome!
Architecture
For maximum safety and performance, CoreStore will enforce coding patterns and practices it was designed for. (Don't worry, it's not as scary as it sounds.) But it is advisable to understand the "magic" of CoreStore before you use it in your apps.
If you are already familiar with the inner workings of CoreData, here is a mapping of CoreStore
abstractions:
Core Data | CoreStore |
---|---|
NSPersistentContainer (.xcdatamodeld file) |
DataStack |
NSPersistentStoreDescription ("Configuration"s in the .xcdatamodeld file) |
StorageInterface implementations( InMemoryStore , SQLiteStore ) |
NSManagedObjectContext |
BaseDataTransaction subclasses( SynchronousDataTransaction , AsynchronousDataTransaction , UnsafeDataTransaction ) |
A lot of Core Data wrapper libraries set up their NSManagedObjectContext
s this way:
Nesting saves from child context to the root context ensures maximum data integrity between contexts without blocking the main queue. But in reality, merging contexts is still by far faster than saving contexts. CoreStore's DataStack
takes the best of both worlds by treating the main NSManagedObjectContext
as a read-only context (or "viewContext"), and only allows changes to be made within transactions on the child context:
This allows for a butter-smooth main thread, while still taking advantage of safe nested contexts.