CoreStore

Version Platform License

Simple, elegant, and smart Core Data programming with Swift (Swift, iOS 8+)

Features

  • Supports multiple persistent stores per data stack, just the way .xcdatamodeld files are supposed to. CoreStore will also manage one data stack by default, but you can create and manage as many as you need.
  • Ability to plug-in your own logging framework (or any of your favorite 3rd-party logger)
  • Gets around a limitation 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 .xcdatamodeld file, so you are free to name them independently.
  • Observe a list of NSManagedObject's using ManagedObjectListController, a clean wrapper for NSFetchedResultsController. Another controller, ManagedObjectController, lets you observe changes for a single object without using KVO. Both controllers can have multiple observers as well, so there is no extra overhead when sharing the same data source for multiple screens.
  • Makes it hard to fall into common concurrency mistakes. All NSManagedObjectContext tasks are encapsulated into safer, higher-level abstractions without sacrificing flexibility and customizability.
  • Provides convenient API for common use cases.
  • Clean API designed around Swifts code elegance and type safety.

TL;DR sample codes

Quick-setup:

CoreStore.addSQLiteStore("MyStore.sqlite")

Simple transactions:

CoreStore.beginAsynchronous { (transaction) -> Void in
    let object = transaction.create(MyEntity)
    object.entityID = 1
    object.name = "test entity"

    transaction.commit { (result) -> Void in
        switch result {
            case .Success(let hasChanges): println("success!")
            case .Failure(let error): println(error)
        }
    }
}

Easy fetching:

let objects = CoreStore.fetchAll(From(MyEntity))
let objects = CoreStore.fetchAll(
    From(MyEntity),
    Where("entityID", isEqualTo: 1),
    OrderBy(.Ascending("entityID"), .Descending("name")),
    Tweak { (fetchRequest) -> Void in
        fetchRequest.includesPendingChanges = true
    }
)

Simple queries:

let count = CoreStore.queryValue(
    From(MyEntity),
    Select<Int>(.Count("entityID"))
)

Quick jumps

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
NSManagedObjectModel / NSPersistentStoreCoordinator
(.xcdatamodeld file)
DataStack
NSPersistentStore
("Configuration"s in the .xcdatamodeld file)
DataStack configuration
(multiple sqlite / in-memory stores per stack)
NSManagedObjectContext BaseDataTransaction subclasses
(SynchronousDataTransaction, AsynchronousDataTransaction, DetachedDataTransaction)

Popular libraries RestKit and MagicalRecord set up their NSManagedObjectContexts this way:

nested contexts

This ensures maximum data integrity between contexts without blocking the main queue. But as Florian Kugler's investigation found out, merging contexts is still by far faster than saving nested contexts. CoreStore's DataStack takes the best of both worlds by treating the main NSManagedObjectContext as a read-only context, and only allows changes to be made within transactions:

nested contexts and merge hybrid

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

Setting up

The simplest way to initialize CoreStore is to add a default store to the default stack:

CoreStore.defaultStack.addSQLiteStore()

This one-liner does the following:

  • Triggers the lazy-initialization of CoreStore.defaultStack with a default DataStack
  • Sets up the stack's NSPersistentStoreCoordinator, the root saving NSManagedObjectContext, and the read-only main NSManagedObjectContext
  • Adds an automigrating SQLite store in the "Application Support" directory with the file name "[App bundle name].sqlite"
  • Creates and returns the NSPersistentStore instance on success, or an NSError on failure

For most cases, this configuration is usable as it is. But for more hardcore settings, refer to this extensive example:

let dataStack = DataStack(modelName: "MyModel") // loads from the "MyModel.xcdatamodeld" file

switch dataStack.addInMemoryStore(configuration: "Config1") { // creates an in-memory store with entities from the "Config1" configuration in the .xcdatamodeld file
case .Success(let persistentStore): // persistentStore is an NSPersistentStore instance
    println("Successfully created an in-memory store: \(persistentStore)"
case .Failure(let error): // error is an NSError instance
    println("Failed creating an in-memory store with error: \(error.description)"
}

switch dataStack.addSQLiteStore(
    fileURL: sqliteFileURL, // set the target file URL for the sqlite file
    configuration: "Config2", // use entities from the "Config2" configuration in the .xcdatamodeld file
    automigrating: true, // automatically run lightweight migrations or entity policy migrations when needed
    resetStoreOnMigrationFailure: true) { // delete and recreate the sqlite file when migration conflicts occur (useful when debugging)
case .Success(let persistentStore): // persistentStore is an NSPersistentStore instance
    println("Successfully created an sqlite store: \(persistentStore)"
case .Failure(let error): // error is an NSError instance
    println("Failed creating an sqlite store with error: \(error.description)"
}

CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier access later on

Note that you dont need to do the CoreStore.defaultStack = dataStack line. You can just as well hold a stack like below and call all methods directly from the DataStack instance:

class MyViewController: UIViewController {
    let dataStack = DataStack(modelName: "MyModel")
    override func viewDidLoad() {
        super.viewDidLoad()
        self.dataStack.addSQLiteStore()
    }
    func methodToBeCalledLaterOn() {
        let objects = self.dataStack.fetchAll(From(MyEntity))
        println(objects)
    }
}

The difference is when you set the stack as the CoreStore.defaultStack, you can call the stack's methods directly from CoreStore itself:


class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        CoreStore.addSQLiteStore()
    }
    func methodToBeCalledLaterOn() {
        let objects = CoreStore.fetchAll(From(MyEntity))
        println(objects)
    }
}

Check out the CoreStore.swift and DataStack.swift files if you want to explore the inner workings of the data stack.

Saving and processing transactions

(implemented; README pending)

Fetching and querying

(implemented; README pending)

Logging and error handling

(implemented; README pending)

Observing changes and notifications

(implemented; README pending)

Importing data

(currently implementing)

Description
No description provided
Readme MIT 16 MiB
Latest
2024-10-31 08:37:57 +01:00
Languages
Swift 99.9%