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 NSManagedObjects 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. ListPublishers and ObjectPublishers now have their @ListState and @ObjectState SwiftUI property wrappers. Combine Publisher s are also available through the ListPublisher.reactive, ObjectPublisher.reactive, and DataStack.reactive namespaces.
  • Backwards-portable DiffableDataSources implementation! UITableViews and UICollectionViews now have a new ally: ListPublishers provide diffable snapshots that make reloading animations very easy and very safe. Say goodbye to UITableViews and UICollectionViews 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 NSFetchedResultsControllers 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 NSManagedObjects, it offers CoreStoreObject 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-safe CoreStoreObjects)
  • ?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 (MigrationChains) 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 NSManagedObjectContexts this way:

nested contexts

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:

nested contexts and merge hybrid

This allows for a butter-smooth main thread, while still taking advantage of safe nested contexts.

GitHub

https://github.com/JohnEstropia/CoreStore