From daab317c37d3d3dadde130c144aecfd61d78b566 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sat, 27 Feb 2016 17:11:45 +0900 Subject: [PATCH] update --- .gitignore | 3 + _config.yml | 21 + _includes/footer.html | 38 + _includes/head.html | 12 + _includes/header.html | 27 + _includes/icon-github.html | 1 + _includes/icon-github.svg | 1 + _includes/icon-twitter.html | 1 + _includes/icon-twitter.svg | 1 + _layouts/default.html | 20 + _layouts/page.html | 14 + _layouts/post.html | 15 + _posts/2016-02-27-welcome-to-jekyll.markdown | 1228 +++++++++++++++ _sass/_base.scss | 206 +++ _sass/_layout.scss | 242 +++ _sass/_syntax-highlighting.scss | 71 + about.md | 15 + css/main.scss | 53 + feed.xml | 30 + index.html | 1465 +----------------- params.json | 1 - stylesheets/github-light.css | 116 -- stylesheets/normalize.css | 424 ----- stylesheets/stylesheet.css | 245 --- 24 files changed, 2016 insertions(+), 2234 deletions(-) create mode 100644 .gitignore create mode 100644 _config.yml create mode 100644 _includes/footer.html create mode 100644 _includes/head.html create mode 100644 _includes/header.html create mode 100644 _includes/icon-github.html create mode 100644 _includes/icon-github.svg create mode 100644 _includes/icon-twitter.html create mode 100644 _includes/icon-twitter.svg create mode 100644 _layouts/default.html create mode 100644 _layouts/page.html create mode 100644 _layouts/post.html create mode 100644 _posts/2016-02-27-welcome-to-jekyll.markdown create mode 100644 _sass/_base.scss create mode 100644 _sass/_layout.scss create mode 100644 _sass/_syntax-highlighting.scss create mode 100644 about.md create mode 100644 css/main.scss create mode 100644 feed.xml delete mode 100644 params.json delete mode 100644 stylesheets/github-light.css delete mode 100644 stylesheets/normalize.css delete mode 100644 stylesheets/stylesheet.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45c1505 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +_site +.sass-cache +.jekyll-metadata diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..1c5e5bc --- /dev/null +++ b/_config.yml @@ -0,0 +1,21 @@ +# Welcome to Jekyll! +# +# This config file is meant for settings that affect your whole blog, values +# which you are expected to set up once and rarely need to edit after that. +# For technical reasons, this file is *NOT* reloaded automatically when you use +# 'jekyll serve'. If you change this file, please restart the server process. + +# Site settings +title: Your awesome title +email: your-email@domain.com +description: > # this means to ignore newlines until "baseurl:" + Write an awesome description for your new site here. You can edit this + line in _config.yml. It will appear in your document head meta (for + Google search results) and in your feed.xml site description. +baseurl: "" # the subpath of your site, e.g. /blog +url: "http://yourdomain.com" # the base hostname & protocol for your site +twitter_username: jekyllrb +github_username: jekyll + +# Build settings +markdown: kramdown diff --git a/_includes/footer.html b/_includes/footer.html new file mode 100644 index 0000000..72239f1 --- /dev/null +++ b/_includes/footer.html @@ -0,0 +1,38 @@ + diff --git a/_includes/head.html b/_includes/head.html new file mode 100644 index 0000000..1598d6f --- /dev/null +++ b/_includes/head.html @@ -0,0 +1,12 @@ + + + + + + {% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %} + + + + + + diff --git a/_includes/header.html b/_includes/header.html new file mode 100644 index 0000000..b3f86db --- /dev/null +++ b/_includes/header.html @@ -0,0 +1,27 @@ + diff --git a/_includes/icon-github.html b/_includes/icon-github.html new file mode 100644 index 0000000..e501a16 --- /dev/null +++ b/_includes/icon-github.html @@ -0,0 +1 @@ +{% include icon-github.svg %}{{ include.username }} diff --git a/_includes/icon-github.svg b/_includes/icon-github.svg new file mode 100644 index 0000000..4422c4f --- /dev/null +++ b/_includes/icon-github.svg @@ -0,0 +1 @@ + diff --git a/_includes/icon-twitter.html b/_includes/icon-twitter.html new file mode 100644 index 0000000..e623dbd --- /dev/null +++ b/_includes/icon-twitter.html @@ -0,0 +1 @@ +{{ include.username }} diff --git a/_includes/icon-twitter.svg b/_includes/icon-twitter.svg new file mode 100644 index 0000000..dcf660e --- /dev/null +++ b/_includes/icon-twitter.svg @@ -0,0 +1 @@ + diff --git a/_layouts/default.html b/_layouts/default.html new file mode 100644 index 0000000..e4ab96f --- /dev/null +++ b/_layouts/default.html @@ -0,0 +1,20 @@ + + + + {% include head.html %} + + + + {% include header.html %} + +
+
+ {{ content }} +
+
+ + {% include footer.html %} + + + + diff --git a/_layouts/page.html b/_layouts/page.html new file mode 100644 index 0000000..ce233ad --- /dev/null +++ b/_layouts/page.html @@ -0,0 +1,14 @@ +--- +layout: default +--- +
+ +
+

{{ page.title }}

+
+ +
+ {{ content }} +
+ +
diff --git a/_layouts/post.html b/_layouts/post.html new file mode 100644 index 0000000..3a0fb52 --- /dev/null +++ b/_layouts/post.html @@ -0,0 +1,15 @@ +--- +layout: default +--- +
+ +
+

{{ page.title }}

+ +
+ +
+ {{ content }} +
+ +
diff --git a/_posts/2016-02-27-welcome-to-jekyll.markdown b/_posts/2016-02-27-welcome-to-jekyll.markdown new file mode 100644 index 0000000..4e9f6fc --- /dev/null +++ b/_posts/2016-02-27-welcome-to-jekyll.markdown @@ -0,0 +1,1228 @@ +--- +layout: page +title: "CoreStore" +categories: CoreStore +--- +![corestore](https://cloud.githubusercontent.com/assets/3029684/13368133/305f37d0-dd2b-11e5-8ae6-26e48050b8f6.png) + +[![Build Status](https://img.shields.io/travis/JohnEstropia/CoreStore/master.svg)](https://travis-ci.org/JohnEstropia/CoreStore) +[![Version](https://img.shields.io/cocoapods/v/CoreStore.svg?style=flat)](http://cocoadocs.org/docsets/CoreStore) +[![Platform](https://img.shields.io/cocoapods/p/CoreStore.svg?style=flat)](http://cocoadocs.org/docsets/CoreStore) +[![License](https://img.shields.io/cocoapods/l/CoreStore.svg?style=flat)](https://raw.githubusercontent.com/JohnEstropia/CoreStore/master/LICENSE) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) + +Unleashing the real power of Core Data with the elegance and safety of Swift + +* Swift 2.2 (Xcode 7.3) +* iOS 8+ / OSX 10.10+ / watchOS 2.0+ / tvOS 9.0+ + +[Click here for a wiki version of this README](https://github.com/JohnEstropia/CoreStore/wiki) + + + +## What CoreStore does better: + +- **Heavily supports multiple persistent stores per data stack**, just the way *.xcdatamodeld* files are designed to. CoreStore will also manage one data stack by default, but you can create and manage as many as you need. +- **Progressive Migrations!** Just tell the data stack the sequence of model versions and CoreStore will automatically use progressive migrations if needed on stores added to that stack. +- Ability to **plug-in your own logging framework** +- 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 managed object model file, so you are **free to name entities and their class names independently**. +- Provides type-safe, easy to configure **observers to replace `NSFetchedResultsController` and KVO** +- Exposes **API not just for fetching, but also for querying aggregates and property values** +- Makes it hard to fall into common concurrency mistakes. All `NSManagedObjectContext` tasks are encapsulated into **safer, higher-level abstractions** without sacrificing flexibility and customizability. +- Exposes clean and convenient API designed around **Swift’s code elegance and type safety**. +- **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. +- **Efficient importing utilities!** + +**[Vote for the next feature!](http://goo.gl/RIiHMP)** + + + +## Contents + +- [TL;DR (a.k.a. sample codes)](#tldr-aka-sample-codes) +- [Architecture](#architecture) +- CoreStore Tutorials (All of these have demos in the **CoreStoreDemo** app project!) + - [Setting up](#setting-up) + - [Migrations](#migrations) + - [Progressive migrations](#progressive-migrations) + - [Forecasting migrations](#forecasting-migrations) + - [Saving and processing transactions](#saving-and-processing-transactions) + - [Transaction types](#transaction-types) + - [Asynchronous transactions](#asynchronous-transactions) + - [Synchronous transactions](#synchronous-transactions) + - [Unsafe transactions](#unsafe-transactions) + - [Creating objects](#creating-objects) + - [Updating objects](#updating-objects) + - [Deleting objects](#deleting-objects) + - [Passing objects safely](#passing-objects-safely) + - [Importing data](#importing-data) + - [Fetching and querying](#fetching-and-querying) + - [`From` clause](#from-clause) + - [Fetching](#fetching) + - [`Where` clause](#where-clause) + - [`OrderBy` clause](#orderby-clause) + - [`Tweak` clause](#tweak-clause) + - [Querying](#querying) + - [`Select` clause](#selectt-clause) + - [`GroupBy` clause](#groupby-clause) + - [Logging and error handling](#logging-and-error-handling) + - [Observing changes and notifications](#observing-changes-and-notifications) (unavailable on OSX) + - [Observe a single object](#observe-a-single-object) + - [Observe a list of objects](#observe-a-list-of-objects) +- [Roadmap](#roadmap) +- [Installation](#installation) +- [Changesets](#changesets) + - [Upgrading from v0.2.0 to 1.0.0](#upgrading-from-v020-to-100) + + + +## TL;DR (a.k.a. sample codes) + +Setting-up with progressive migration support: +```swift +CoreStore.defaultStack = DataStack( + modelName: "MyStore", + migrationChain: ["MyStore", "MyStoreV2", "MyStoreV3"] +) +``` + +Adding a store: +```swift +try CoreStore.addSQLiteStore( + fileName: "MyStore.sqlite", + completion: { (result) -> Void in + // ... + } +) +``` + +Starting transactions: +```swift +CoreStore.beginAsynchronous { (transaction) -> Void in + let person = transaction.create(Into(MyPersonEntity)) + person.name = "John Smith" + person.age = 42 + + transaction.commit { (result) -> Void in + switch result { + case .Success(let hasChanges): print("success!") + case .Failure(let error): print(error) + } + } +} +``` + +Fetching objects: +```swift +let people = CoreStore.fetchAll(From(MyPersonEntity)) +``` +```swift +let people = CoreStore.fetchAll( + From(MyPersonEntity), + Where("age > 30"), + OrderBy(.Ascending("name"), .Descending("age")), + Tweak { (fetchRequest) -> Void in + fetchRequest.includesPendingChanges = false + } +) +``` + +Querying values: +```swift +let maxAge = CoreStore.queryValue( + From(MyPersonEntity), + Select(.Maximum("age")) +) +``` + +But really, there's a reason I wrote this huge README. Read up on the details! + +Check out the **CoreStoreDemo** app project for sample codes as well! + + + +## 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`, `UnsafeDataTransaction`) | + +Popular libraries [RestKit](https://github.com/RestKit/RestKit) and [MagicalRecord](https://github.com/magicalpanda/MagicalRecord) set up their `NSManagedObjectContext`s this way: + +nested contexts + +Nesting context saves from child context to the root context 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* 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. + + + +## Setting up +The simplest way to initialize CoreStore is to add a default store to the default stack: +```swift +try CoreStore.addSQLiteStoreAndWait() +``` +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 SQLite store in the *"Application Support/"* directory (or the *"Caches/"* directory on tvOS) 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: +```swift +let dataStack = DataStack( + modelName: "MyModel", // loads from the "MyModel.xcdatamodeld" file + migrationChain: ["MyStore", "MyStoreV2", "MyStoreV3"] // model versions for progressive migrations +) + +do { + // creates an in-memory store with entities from the "Config1" configuration in the .xcdatamodeld file + let persistentStore = try dataStack.addInMemoryStoreAndWait(configuration: "Config1") // persistentStore is an NSPersistentStore instance + print("Successfully created an in-memory store: \(persistentStore)" +} +catch { + print("Failed creating an in-memory store with error: \(error as NSError)" +} + +do { + try 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 + resetStoreOnModelMismatch: true, + completion: { (result) -> Void in + switch result { + case .Success(let persistentStore): + print("Successfully added sqlite store: \(persistentStore)" + case .Failure(let error): + print("Failed adding sqlite store with error: \(error)" + } + } + ) +} +catch { + print("Failed adding sqlite store with error: \(error as NSError)" +} + +CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier access later on +``` + +(If you have never heard of "Configurations", you'll find them in your *.xcdatamodeld* file) +xcode configurations screenshot + +In our sample code above, note that you don't need to do the `CoreStore.defaultStack = dataStack` line. You can just as well hold a reference to the `DataStack` like below and call all its instance methods directly: +```swift +class MyViewController: UIViewController { + let dataStack = DataStack(modelName: "MyModel") + override func viewDidLoad() { + super.viewDidLoad() + do { + try self.dataStack.addSQLiteStoreAndWait() + } + catch { // ... + } + } + func methodToBeCalledLaterOn() { + let objects = self.dataStack.fetchAll(From(MyEntity)) + print(objects) + } +} +``` +The difference is when you set the stack as the `CoreStore.defaultStack`, you can call the stack's methods directly from `CoreStore` itself: +```swift +class MyViewController: UIViewController { + override func viewDidLoad() { + super.viewDidLoad() + do { + try CoreStore.addSQLiteStoreAndWait() + } + catch { // ... + } + } + func methodToBeCalledLaterOn() { + let objects = CoreStore.fetchAll(From(MyEntity)) + print(objects) + } +} +``` + + +## Migrations +So far we have only seen `addSQLiteStoreAndWait(...)` used to initialize our persistent store. As the method name's "AndWait" suffix suggests, this method blocks so it should not do long tasks such as store migrations (in fact CoreStore won't even attempt to, and any model mismatch will be reported as an error). If migrations are expected, the asynchronous variant `addSQLiteStore(... completion:)` method should be used instead: +```swift +do { + let progress: NSProgress? = try dataStack.addSQLiteStore( + fileName: "MyStore.sqlite", + configuration: "Config2", + completion: { (result) -> Void in + switch result { + case .Success(let persistentStore): + print("Successfully added sqlite store: \(persistentStore)") + case .Failure(let error): + print("Failed adding sqlite store with error: \(error)") + } + } + ) +} +catch { + print("Failed adding sqlite store with error: \(error as NSError)" +} +``` +The `completion` block reports a `PersistentStoreResult` that indicates success or failure. + +`addSQLiteStore(...)` throws an error if the store at the specified URL conflicts with an existing store in the `DataStack`, or if an existing sqlite file could not be read. If an error is thrown, the `completion` block will not be executed. + +Notice that this method also returns an optional `NSProgress`. If `nil`, no migrations are needed, thus progress reporting is unnecessary as well. If not `nil`, you can use this to track migration progress by using standard KVO on the "fractionCompleted" key, or by using a closure-based utility exposed in *NSProgress+Convenience.swift*: +```swift +progress?.setProgressHandler { [weak self] (progress) -> Void in + self?.progressView?.setProgress(Float(progress.fractionCompleted), animated: true) + self?.percentLabel?.text = progress.localizedDescription // "50% completed" + self?.stepLabel?.text = progress.localizedAdditionalDescription // "0 of 2" +} +``` +This closure is executed on the main thread so UIKit calls can be done safely. + + +### Progressive migrations +By default, CoreStore uses Core Data's default automatic migration mechanism. In other words, CoreStore will try to migrate the existing persistent store to the *.xcdatamodeld* file's current model version. If no mapping model is found from the store's version to the data model's version, CoreStore gives up and reports an error. + +The `DataStack` lets you specify hints on how to break a migration into several sub-migrations using a `MigrationChain`. This is typically passed to the `DataStack` initializer and will be applied to all stores added to the `DataStack` with `addSQLiteStore(...)` and its variants: +```swift +let dataStack = DataStack(migrationChain: + ["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4"]) +``` +The most common usage is to pass in the *.xcdatamodeld* version names in increasing order as above. + +For more complex migration paths, you can also pass in a version tree that maps the key-values to the source-destination versions: +```swift +let dataStack = DataStack(migrationChain: [ + "MyAppModel": "MyAppModelV3", + "MyAppModelV2": "MyAppModelV4", + "MyAppModelV3": "MyAppModelV4" +]) +``` +This allows for different migration paths depending on the starting version. The example above resolves to the following paths: +- MyAppModel-MyAppModelV3-MyAppModelV4 +- MyAppModelV2-MyAppModelV4 +- MyAppModelV3-MyAppModelV4 + +Initializing with empty values (either `nil`, `[]`, or `[:]`) instructs the `DataStack` to disable progressive migrations and revert to the default migration behavior (i.e. use the .xcdatamodel's current version as the final version): +```swift +let dataStack = DataStack(migrationChain: nil) +``` + +The `MigrationChain` is validated when passed to the `DataStack` and unless it is empty, will raise an assertion if any of the following conditions are met: +- a version appears twice in an array +- a version appears twice as a key in a dictionary literal +- a loop is found in any of the paths + +One important thing to remember is that **if a `MigrationChain` is specified, the *.xcdatamodeld*'s "Current Version" will be bypassed** and the `MigrationChain`'s leafmost version will be the `DataStack`'s base model version. + + +### Forecasting migrations + +Sometimes migrations are huge and you may want prior information so your app could display a loading screen, or to display a confirmation dialog to the user. For this, CoreStore provides a `requiredMigrationsForSQLiteStore(...)` method you can use to inspect a persistent store before you actually call `addSQLiteStore(...)`: +```swift +do { + let migrationTypes: [MigrationType] = CoreStore.requiredMigrationsForSQLiteStore(fileName: "MyStore.sqlite") + if migrationTypes.count > 1 + || (migrationTypes.filter { $0.isHeavyweightMigration }.count) > 0 { + // ... Show special waiting screen + } + else if migrationTypes.count > 0 { + // ... Show simple activity indicator + } + else { + // ... Do nothing + } + + CoreStore.addSQLiteStore(/* ... */) +} +catch { + // ... +} +``` +`requiredMigrationsForSQLiteStore(...)` returns an array of `MigrationType`s, where each item in the array may be either of the following values: +```swift +case Lightweight(sourceVersion: String, destinationVersion: String) +case Heavyweight(sourceVersion: String, destinationVersion: String) +``` +Each `MigrationType` indicates the migration type for each step in the `MigrationChain`. Use these information as fit for your app. + + + +## Saving and processing transactions +To ensure deterministic state for objects in the read-only `NSManagedObjectContext`, CoreStore does not expose API's for updating and saving directly from the main context (or any other context for that matter.) Instead, you spawn *transactions* from `DataStack` instances: +```swift +let dataStack = self.dataStack +dataStack.beginAsynchronous { (transaction) -> Void in + // make changes + transaction.commit() +} +``` +or for the default stack, directly from `CoreStore`: +```swift +CoreStore.beginAsynchronous { (transaction) -> Void in + // make changes + transaction.commit() +} +``` +The `commit()` method saves the changes to the persistent store. If `commit()` is not called when the transaction block completes, all changes within the transaction is discarded. + +The examples above use `beginAsynchronous(...)`, but there are actually 3 types of transactions at your disposal: *asynchronous*, *synchronous*, and *unsafe*. + +### Transaction types + +#### Asynchronous transactions +are spawned from `beginAsynchronous(...)`. This method returns immediately and executes its closure from a background serial queue: +```swift +CoreStore.beginAsynchronous { (transaction) -> Void in + // make changes + transaction.commit() +} +``` +Transactions created from `beginAsynchronous(...)` are instances of `AsynchronousDataTransaction`. + +#### Synchronous transactions +are created from `beginSynchronous(...)`. While the syntax is similar to its asynchronous counterpart, `beginSynchronous(...)` waits for its transaction block to complete before returning: +```swift +CoreStore.beginSynchronous { (transaction) -> Void in + // make changes + transaction.commit() +} +``` +`transaction` above is a `SynchronousDataTransaction` instance. + +Since `beginSynchronous(...)` technically blocks two queues (the caller's queue and the transaction's background queue), it is considered less safe as it's more prone to deadlock. Take special care that the closure does not block on any other external queues. + +#### Unsafe transactions +are special in that they do not enclose updates within a closure: +```swift +let transaction = CoreStore.beginUnsafe() +// make changes +downloadJSONWithCompletion({ (json) -> Void in + + // make other changes + transaction.commit() +}) +downloadAnotherJSONWithCompletion({ (json) -> Void in + + // make some other changes + transaction.commit() +}) +``` +This allows for non-contiguous updates. Do note that this flexibility comes with a price: you are now responsible for managing concurrency for the transaction. As uncle Ben said, "with great power comes great race conditions." + +As the above example also shows, only unsafe transactions are allowed to call `commit()` multiple times; doing so with synchronous and asynchronous transactions will trigger an assert. + + +You've seen how to create transactions, but we have yet to see how to make *creates*, *updates*, and *deletes*. The 3 types of transactions above are all subclasses of `BaseDataTransaction`, which implements the methods shown below. + +### Creating objects + +The `create(...)` method accepts an `Into` clause which specifies the entity for the object you want to create: +```swift +let person = transaction.create(Into(MyPersonEntity)) +``` +While the syntax is straightforward, CoreStore does not just naively insert a new object. This single line does the following: +- Checks that the entity type exists in any of the transaction's parent persistent store +- If the entity belongs to only one persistent store, a new object is inserted into that store and returned from `create(...)` +- If the entity does not belong to any store, an assert will be triggered. **This is a programmer error and should never occur in production code.** +- If the entity belongs to multiple stores, an assert will be triggered. **This is also a programmer error and should never occur in production code.** Normally, with Core Data you can insert an object in this state but saving the `NSManagedObjectContext` will always fail. CoreStore checks this for you at creation time when it makes sense (not during save). + +If the entity exists in multiple configurations, you need to provide the configuration name for the destination persistent store: + + let person = transaction.create(Into("Config1")) + +or if the persistent store is the auto-generated "Default" configuration, specify `nil`: + + let person = transaction.create(Into(nil)) + +Note that if you do explicitly specify the configuration name, CoreStore will only try to insert the created object to that particular store and will fail if that store is not found; it will not fall back to any other configuration that the entity belongs to. + +### Updating objects + +After creating an object from the transaction, you can simply update its properties as normal: +```swift +CoreStore.beginAsynchronous { (transaction) -> Void in + let person = transaction.create(Into(MyPersonEntity)) + person.name = "John Smith" + person.age = 30 + transaction.commit() +} +``` +To update an existing object, fetch the object's instance from the transaction: +```swift +CoreStore.beginAsynchronous { (transaction) -> Void in + let person = transaction.fetchOne( + From(MyPersonEntity), + Where("name", isEqualTo: "Jane Smith") + ) + person.age = person.age + 1 + transaction.commit() +} +``` +*(For more about fetching, see [Fetching and querying](#fetching-and-querying))* + +**Do not update an instance that was not created/fetched from the transaction.** If you have a reference to the object already, use the transaction's `edit(...)` method to get an editable proxy instance for that object: +```swift +let jane: MyPersonEntity = // ... + +CoreStore.beginAsynchronous { (transaction) -> Void in + // WRONG: jane.age = jane.age + 1 + // RIGHT: + let jane = transaction.edit(jane)! // using the same variable name protects us from misusing the non-transaction instance + jane.age = jane.age + 1 + transaction.commit() +} +``` +This is also true when updating an object's relationships. Make sure that the object assigned to the relationship is also created/fetched from the transaction: +```swift +let jane: MyPersonEntity = // ... +let john: MyPersonEntity = // ... + +CoreStore.beginAsynchronous { (transaction) -> Void in + // WRONG: jane.friends = [john] + // RIGHT: + let jane = transaction.edit(jane)! + let john = transaction.edit(john)! + jane.friends = NSSet(array: [john]) + transaction.commit() +} +``` + +### Deleting objects + +Deleting an object is simpler because you can tell a transaction to delete an object directly without fetching an editable proxy (CoreStore does that for you): +```swift +let john: MyPersonEntity = // ... + +CoreStore.beginAsynchronous { (transaction) -> Void in + transaction.delete(john) + transaction.commit() +} +``` +or several objects at once: +```swift +let john: MyPersonEntity = // ... +let jane: MyPersonEntity = // ... + +CoreStore.beginAsynchronous { (transaction) -> Void in + transaction.delete(john, jane) + // transaction.delete([john, jane]) is also allowed + transaction.commit() +} +``` +If you do not have references yet to the objects to be deleted, transactions have a `deleteAll(...)` method you can pass a query to: +```swift +CoreStore.beginAsynchronous { (transaction) -> Void in + transaction.deleteAll( + From(MyPersonEntity) + Where("age > 30") + ) + transaction.commit() +} +``` + +### Passing objects safely + +Always remember that the `DataStack` and individual transactions manage different `NSManagedObjectContext`s so you cannot just use objects between them. That's why transactions have an `edit(...)` method: +```swift +let jane: MyPersonEntity = // ... + +CoreStore.beginAsynchronous { (transaction) -> Void in + let jane = transaction.edit(jane)! + jane.age = jane.age + 1 + transaction.commit() +} +``` +But `CoreStore`, `DataStack` and `BaseDataTransaction` have a very flexible `fetchExisting(...)` method that you can pass instances back and forth with: +```swift +let jane: MyPersonEntity = // ... + +CoreStore.beginAsynchronous { (transaction) -> Void in + let jane = transaction.fetchExisting(jane)! // instance for transaction + jane.age = jane.age + 1 + transaction.commit { + let jane = CoreStore.fetchExisting(jane)! // instance for DataStack + print(jane.age) + } +} +``` +`fetchExisting(...)` also works with multiple `NSManagedObject`s or with `NSManagedObjectID`s: +```swift +var peopleIDs: [NSManagedObjectID] = // ... + +CoreStore.beginAsynchronous { (transaction) -> Void in + let jane = transaction.fetchOne( + From(MyPersonEntity), + Where("name", isEqualTo: "Jane Smith") + ) + jane.friends = NSSet(array: transaction.fetchExisting(peopleIDs)!) + // ... +} +``` + + +## Importing data +Some times, if not most of the time, the data that we save to Core Data comes from external sources such as web servers or external files. Say you have a JSON dictionary, you may be extracting values as such: +```swift +let json: [String: AnyObject] = // ... +person.name = json["name"] as? NSString +person.age = json["age"] as? NSNumber +// ... +``` +If you have many attributes, you don't want to keep repeating this mapping everytime you want to import data. CoreStore lets you write the data mapping code just once, and all you have to do is call `importObject(...)` or `importUniqueObject(...)` through `BaseDataTransaction` subclasses: +```swift +CoreStore.beginAsynchronous { (transaction) -> Void in + let json: [String: AnyObject] = // ... + try! transaction.importObject( + Into(MyPersonEntity), + source: json + ) + transaction.commit() +} +``` +To support data import for an entity, implement either `ImportableObject` or `ImportableUniqueObject` on the `NSManagedObject` subclass: +- `ImportableObject`: Use this protocol if the object have no inherent uniqueness and new objects should always be added when calling `importObject(...)`. +- `ImportableUniqueObject`: Use this protocol to specify a unique ID for an object that will be used to distinguish whether a new object should be created or if an existing object should be updated when calling `importUniqueObject(...)`. + +Both protocols require implementers to specify an `ImportSource` which can be set to any type that the object can extract data from: +```swift +typealias ImportSource = NSDictionary +``` +```swift +typealias ImportSource = [String: AnyObject] +``` +```swift +typealias ImportSource = NSData +``` +You can even use external types from popular 3rd-party JSON libraries ([SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON)'s `JSON` type is a personal favorite), or just simple tuples or primitives. + +#### `ImportableObject` +`ImportableObject` is a very simple protocol: +```swift +public protocol ImportableObject: class { + typealias ImportSource + static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool + func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws +} +``` +First, set `ImportSource` to the expected type of the data source: +```swift +typealias ImportSource = [String: AnyObject] +``` +This lets us call `importObject(_:source:)` with any `[String: AnyObject]` type as the argument to `source`: +```swift +CoreStore.beginAsynchronous { (transaction) -> Void in + let json: [String: AnyObject] = // ... + try! transaction.importObject( + Into(MyPersonEntity), + source: json + ) + // ... +} +``` +The actual extraction and assignment of values should be implemented in the `didInsertFromImportSource(...)` method of the `ImportableObject` protocol: +```swift +func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws { + self.name = source["name"] as? NSString + self.age = source["age"] as? NSNumber + // ... +} +``` +Transactions also let you import multiple objects at once using the `importObjects(_:sourceArray:)` method: +```swift +CoreStore.beginAsynchronous { (transaction) -> Void in + let jsonArray: [[String: AnyObject]] = // ... + try! transaction.importObjects( + Into(MyPersonEntity), + sourceArray: jsonArray + ) + // ... +} +``` +Doing so tells the transaction to iterate through the array of import sources and calls `shouldInsertFromImportSource(...)` on the `ImportableObject` to determine which instances should be created. You can do validations and return `false` from `shouldInsertFromImportSource(...)` if you want to skip importing from a source and continue on with the other sources in the array. + +If on the other hand, your validation in one of the sources failed in such a manner that all other sources should also be cancelled, you can `throw` from within `didInsertFromImportSource(...)`: +```swift +func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws { + self.name = source["name"] as? NSString + self.age = source["age"] as? NSNumber + // ... + if self.name == nil { + throw Errors.InvalidNameError + } +} +``` +Doing so can let you abandon an invalid transaction immediately: +```swift +CoreStore.beginAsynchronous { (transaction) -> Void in + let jsonArray: [[String: AnyObject]] = // ... + do { + try transaction.importObjects( + Into(MyPersonEntity), + sourceArray: jsonArray + ) + } + catch { + return // Woops, don't save + } + transaction.commit { + // ... + } +} +``` + +#### `ImportableUniqueObject` +Typically, we don't just keep creating objects every time we import data. Usually we also need to update already existing objects. Implementing the `ImportableUniqueObject` protocol lets you specify a "unique ID" that transactions can use to search existing objects before creating new ones: +```swift +public protocol ImportableUniqueObject: ImportableObject { + typealias ImportSource + typealias UniqueIDType: NSObject + + static var uniqueIDKeyPath: String { get } + var uniqueIDValue: UniqueIDType { get set } + + static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool + static func shouldUpdateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool + static func uniqueIDFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws -> UniqueIDType? + func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws + func updateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws +} +``` +Notice that it has the same insert methods as `ImportableObject`, with additional methods for updates and for specifying the unique ID: +```swift +class var uniqueIDKeyPath: String { + return "personID" +} +var uniqueIDValue: NSNumber { + get { return self.personID } + set { self.personID = newValue } +} +class func uniqueIDFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws -> NSNumber? { + return source["id"] as? NSNumber +} +``` +For `ImportableUniqueObject`, the extraction and assignment of values should be implemented from the `updateFromImportSource(...)` method. The `didInsertFromImportSource(...)` by default calls `updateFromImportSource(...)`, but you can separate the implementation for inserts and updates if needed. + +You can then create/update an object by calling a transaction's `importUniqueObject(...)` method: +```swift +CoreStore.beginAsynchronous { (transaction) -> Void in + let json: [String: AnyObject] = // ... + try! transaction.importUniqueObject( + Into(MyPersonEntity), + source: json + ) + // ... +} +``` +or multiple objects at once with the `importUniqueObjects(...)` method: + +```swift +CoreStore.beginAsynchronous { (transaction) -> Void in + let jsonArray: [[String: AnyObject]] = // ... + try! transaction.importObjects( + Into(MyPersonEntity), + sourceArray: jsonArray + ) + // ... +} +``` +As with `ImportableObject`, you can control wether to skip importing an object by implementing +`shouldInsertFromImportSource(...)` and `shouldUpdateFromImportSource(...)`, or to cancel all objects by `throw`ing an error from the `uniqueIDFromImportSource(...)`, `didInsertFromImportSource(...)` or `updateFromImportSource(...)` methods. + + +## Fetching and Querying +Before we dive in, be aware that CoreStore distinguishes between *fetching* and *querying*: +- A *fetch* executes searches from a specific *transaction* or *data stack*. This means fetches can include pending objects (i.e. before a transaction calls on `commit()`.) Use fetches when: + - results need to be `NSManagedObject` instances + - unsaved objects should be included in the search (though fetches can be configured to exclude unsaved ones) +- A *query* pulls data straight from the persistent store. This means faster searches when computing aggregates such as *count*, *min*, *max*, etc. Use queries when: + - you need to compute aggregate functions (see below for a list of supported functions) + - results can be raw values like `NSString`s, `NSNumber`s, `Int`s, `NSDate`s, an `NSDictionary` of key-values, etc. + - only values for specified attribute keys need to be included in the results + - unsaved objects should be ignored + +#### `From` clause +The search conditions for fetches and queries are specified using *clauses*. All fetches and queries require a `From` clause that indicates the target entity type: +```swift +let people = CoreStore.fetchAll(From(MyPersonEntity)) +// CoreStore.fetchAll(From()) works as well +``` +`people` in the example above will be of type `[MyPersonEntity]`. The `From(MyPersonEntity)` clause indicates a fetch to all persistent stores that `MyPersonEntity` belong to. + +If the entity exists in multiple configurations and you need to only search from a particular configuration, indicate in the `From` clause the configuration name for the destination persistent store: +```swift +let people = CoreStore.fetchAll(From("Config1")) // ignore objects in persistent stores other than the "Config1" configuration +``` +or if the persistent store is the auto-generated "Default" configuration, specify `nil`: +```swift +let person = CoreStore.fetchAll(From(nil)) +``` +Now we know how to use a `From` clause, let's move on to fetching and querying. + +### Fetching + +There are currently 5 fetch methods you can call from `CoreStore`, from a `DataStack` instance, or from a `BaseDataTransaction` instance. All of the methods below accept the same parameters: a required `From` clause, and an optional series of `Where`, `OrderBy`, and/or `Tweak` clauses. + +- `fetchAll(...)` - returns an array of all objects that match the criteria. +- `fetchOne(...)` - returns the first object that match the criteria. +- `fetchCount(...)` - returns the number of objects that match the criteria. +- `fetchObjectIDs(...)` - returns an array of `NSManagedObjectID`s for all objects that match the criteria. +- `fetchObjectID(...)` - returns the `NSManagedObjectID`s for the first objects that match the criteria. + +Each method's purpose is straightforward, but we need to understand how to set the clauses for the fetch. + +#### `Where` clause + +The `Where` clause is CoreStore's `NSPredicate` wrapper. It specifies the search filter to use when fetching (or querying). It implements all initializers that `NSPredicate` does (except for `-predicateWithBlock:`, which Core Data does not support): +```swift +var people = CoreStore.fetchAll( + From(MyPersonEntity), + Where("%K > %d", "age", 30) // string format initializer +) +people = CoreStore.fetchAll( + From(MyPersonEntity), + Where(true) // boolean initializer +) +``` +If you do have an existing `NSPredicate` instance already, you can pass that to `Where` as well: +```swift +let predicate = NSPredicate(...) +var people = CoreStore.fetchAll( + From(MyPersonEntity), + Where(predicate) // predicate initializer +) +``` +`Where` clauses also implement the `&&`, `||`, and `!` logic operators, so you can provide logical conditions without writing too much `AND`, `OR`, and `NOT` strings: +```swift +var people = CoreStore.fetchAll( + From(MyPersonEntity), + Where("age > %d", 30) && Where("gender == %@", "M") +) +``` +If you do not provide a `Where` clause, all objects that belong to the specified `From` will be returned. + +#### `OrderBy` clause + +The `OrderBy` clause is CoreStore's `NSSortDescriptor` wrapper. Use it to specify attribute keys in which to sort the fetch (or query) results with. +```swift +var mostValuablePeople = CoreStore.fetchAll( + From(MyPersonEntity), + OrderBy(.Descending("rating"), .Ascending("surname")) +) +``` +As seen above, `OrderBy` accepts a list of `SortKey` enumeration values, which can be either `.Ascending` or `.Descending`. + +You can use the `+` and `+=` operator to append `OrderBy`s together. This is useful when sorting conditionally: +```swift +var orderBy = OrderBy(.Descending("rating")) +if sortFromYoungest { + orderBy += OrderBy(.Ascending("age")) +} +var mostValuablePeople = CoreStore.fetchAll( + From(MyPersonEntity), + orderBy +) +``` + +#### `Tweak` clause + +The `Tweak` clause lets you, uh, *tweak* the fetch (or query). `Tweak` exposes the `NSFetchRequest` in a closure where you can make changes to its properties: +```swift +var people = CoreStore.fetchAll( + From(MyPersonEntity), + Where("age > %d", 30), + OrderBy(.Ascending("surname")), + Tweak { (fetchRequest) -> Void in + fetchRequest.includesPendingChanges = false + fetchRequest.returnsObjectsAsFaults = false + fetchRequest.includesSubentities = false + } +) +``` +The clauses are evaluated the order they appear in the fetch/query, so you typically need to set `Tweak` as the last clause. +`Tweak`'s closure is executed only just before the fetch occurs, so make sure that any values captured by the closure is not prone to race conditions. + +While `Tweak` lets you micro-configure the `NSFetchRequest`, note that CoreStore already preconfigured that `NSFetchRequest` to suitable defaults. Only use `Tweak` when you know what you are doing! + +### Querying + +One of the functionalities overlooked by other Core Data wrapper libraries is raw properties fetching. If you are familiar with `NSDictionaryResultType` and `-[NSFetchedRequest propertiesToFetch]`, you probably know how painful it is to setup a query for raw values and aggregate values. CoreStore makes this easy by exposing the 2 methods below: + +- `queryValue(...)` - returns a single raw value for an attribute or for an aggregate value. If there are multiple results, `queryValue(...)` only returns the first item. +- `queryAttributes(...)` - returns an array of dictionaries containing attribute keys with their corresponding values. + +Both methods above accept the same parameters: a required `From` clause, a required `Select` clause, and an optional series of `Where`, `OrderBy`, `GroupBy`, and/or `Tweak` clauses. + +Setting up the `From`, `Where`, `OrderBy`, and `Tweak` clauses is similar to how you would when fetching. For querying, you also need to know how to use the `Select` and `GroupBy` clauses. + +#### `Select` clause + +The `Select` clause specifies the target attribute/aggregate key, as well as the expected return type: +```swift +let johnsAge = CoreStore.queryValue( + From(MyPersonEntity), + Select("age"), + Where("name == %@", "John Smith") +) +``` +The example above queries the "age" property for the first object that matches the `Where` condition. `johnsAge` will be bound to type `Int?`, as indicated by the `Select` generic type. For `queryValue(...)`, the following are allowed as the return type (and therefore as the generic type for `Select`): +- `Bool` +- `Int8` +- `Int16` +- `Int32` +- `Int64` +- `Double` +- `Float` +- `String` +- `NSNumber` +- `NSString` +- `NSDecimalNumber` +- `NSDate` +- `NSData` +- `NSManagedObjectID` +- `NSString` + +For `queryAttributes(...)`, only `NSDictionary` is valid for `Select`, thus you are allowed to omit the generic type: +```swift +let allAges = CoreStore.queryAttributes( + From(MyPersonEntity), + Select("age") +) +``` + +If you only need a value for a particular attribute, you can just specify the key name (like we did with `Select("age")`), but several aggregate functions can also be used as parameter to `Select`: +- `.Average(...)` +- `.Count(...)` +- `.Maximum(...)` +- `.Minimum(...)` +- `.Sum(...)` + +```swift +let oldestAge = CoreStore.queryValue( + From(MyPersonEntity), + Select(.Maximum("age")) +) +``` + +For `queryAttributes(...)` which returns an array of dictionaries, you can specify multiple attributes/aggregates to `Select`: +```swift +let personJSON = CoreStore.queryAttributes( + From(MyPersonEntity), + Select("name", "age") +) +``` +`personJSON` will then have the value: +```swift +[ + [ + "name": "John Smith", + "age": 30 + ], + [ + "name": "Jane Doe", + "age": 22 + ] +] +``` +You can also include an aggregate as well: +```swift +let personJSON = CoreStore.queryAttributes( + From(MyPersonEntity), + Select("name", .Count("friends")) +) +``` +which returns: +```swift +[ + [ + "name": "John Smith", + "count(friends)": 42 + ], + [ + "name": "Jane Doe", + "count(friends)": 231 + ] +] +``` +The `"count(friends)"` key name was automatically used by CoreStore, but you can specify your own key alias if you need: +```swift +let personJSON = CoreStore.queryAttributes( + From(MyPersonEntity), + Select("name", .Count("friends", As: "friendsCount")) +) +``` +which now returns: +```swift +[ + [ + "name": "John Smith", + "friendsCount": 42 + ], + [ + "name": "Jane Doe", + "friendsCount": 231 + ] +] +``` + +#### `GroupBy` clause + +The `GroupBy` clause lets you group results by a specified attribute/aggregate. This is useful only for `queryAttributes(...)` since `queryValue(...)` just returns the first value. +```swift +let personJSON = CoreStore.queryAttributes( + From(MyPersonEntity), + Select("age", .Count("age", As: "count")), + GroupBy("age") +) +``` +this returns dictionaries that shows the count for each `"age"`: +```swift +[ + [ + "age": 42, + "count": 1 + ], + [ + "age": 22, + "count": 1 + ] +] +``` + +## Logging and error handling +One unfortunate thing when using some third-party libraries is that they usually pollute the console with their own logging mechanisms. CoreStore provides its own default logging class, but you can plug-in your own favorite logger by implementing the `CoreStoreLogger` protocol. +```swift +final class MyLogger: CoreStoreLogger { + func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { + // pass to your logger + } + + func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { + // pass to your logger + } + + func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { + // pass to your logger + } +} +``` +Then pass an instance of this class to `CoreStore`: +```swift +CoreStore.logger = MyLogger() +``` +Doing so channels all logging calls to your logger. + +Note that to keep the call stack information intact, all calls to these methods are **NOT** thread-managed. Therefore you have to make sure that your logger is thread-safe or you may otherwise have to dispatch your logging implementation to a serial queue. + +## Observing changes and notifications (unavailable on OSX) +CoreStore provides type-safe wrappers for observing managed objects: + +- `ObjectMonitor`: use to monitor changes to a single `NSManagedObject` instance (instead of Key-Value Observing) +- `ListMonitor`: use to monitor changes to a list of `NSManagedObject` instances (instead of `NSFetchedResultsController`) + +### Observe a single object + +To observe an object, implement the `ObjectObserver` protocol and specify the `EntityType`: +```swift +class MyViewController: UIViewController, ObjectObserver { + func objectMonitor(monitor: ObjectMonitor, willUpdateObject object: MyPersonEntity) { + // ... + } + + func objectMonitor(monitor: ObjectMonitor, didUpdateObject object: MyPersonEntity, changedPersistentKeys: Set) { + // ... + } + + func objectMonitor(monitor: ObjectMonitor, didDeleteObject object: MyPersonEntity) { + // ... + } +} +``` +We then need to keep a `ObjectMonitor` instance and register our `ObjectObserver` as an observer: +```swift +let person: MyPersonEntity = // ... +self.monitor = CoreStore.monitorObject(person) +self.monitor.addObserver(self) +``` +The controller will then notify our observer whenever the object's attributes change. You can add multiple `ObjectObserver`s to a single `ObjectMonitor` without any problem. This means you can just share around the `ObjectMonitor` instance to different screens without problem. + +You can get `ObjectMonitor`'s object through its `object` property. If the object is deleted, the `object` property will become `nil` to prevent further access. + +While `ObjectMonitor` exposes `removeObserver(...)` as well, it only stores `weak` references of the observers and will safely unregister deallocated observers. + +### Observe a list of objects +To observe a list of objects, implement one of the `ListObserver` protocols and specify the `EntityType`: +```swift +class MyViewController: UIViewController, ListObserver { + func listMonitorWillChange(monitor: ListMonitor) { + // ... + } + + func listMonitorDidChange(monitor: ListMonitor) { + // ... + } +} +``` +Including `ListObserver`, there are 3 observer protocols you can implement depending on how detailed you need to handle a change notification: +- `ListObserver`: lets you handle these callback methods: +```swift + func listMonitorWillChange(monitor: ListMonitor) + + func listMonitorDidChange(monitor: ListMonitor) +``` +- `ListObjectObserver`: in addition to `ListObserver` methods, also lets you handle object inserts, updates, and deletes: +```swift + func listMonitor(monitor: ListMonitor, didInsertObject object: MyPersonEntity, toIndexPath indexPath: NSIndexPath) + + func listMonitor(monitor: ListMonitor, didDeleteObject object: MyPersonEntity, fromIndexPath indexPath: NSIndexPath) + + func listMonitor(monitor: ListMonitor, didUpdateObject object: MyPersonEntity, atIndexPath indexPath: NSIndexPath) + + func listMonitor(monitor: ListMonitor, didMoveObject object: MyPersonEntity, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) +``` +- `ListSectionObserver`: in addition to `ListObjectObserver` methods, also lets you handle section inserts and deletes: +```swift + func listMonitor(monitor: ListMonitor, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) + + func listMonitor(monitor: ListMonitor, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) +``` + +We then need to create a `ListMonitor` instance and register our `ListObserver` as an observer: +```swift +self.monitor = CoreStore.monitorList( + From(MyPersonEntity), + Where("age > 30"), + OrderBy(.Ascending("name")), + Tweak { (fetchRequest) -> Void in + fetchRequest.fetchBatchSize = 20 + } +) +self.monitor.addObserver(self) +``` +Similar to `ObjectMonitor`, a `ListMonitor` can also have multiple `ListObserver`s registered to a single `ListMonitor`. + +If you have noticed, the `monitorList(...)` method accepts `Where`, `OrderBy`, and `Tweak` clauses exactly like a fetch. As the list maintained by `ListMonitor` needs to have a deterministic order, at least the `From` and `OrderBy` clauses are required. + +A `ListMonitor` created from `monitorList(...)` will maintain a single-section list. You can therefore access its contents with just an index: +```swift +let firstPerson = self.monitor[0] +``` + +If the list needs to be grouped into sections, create the `ListMonitor` instance with the `monitorSectionedList(...)` method and a `SectionBy` clause: +```swift +self.monitor = CoreStore.monitorSectionedList( + From(MyPersonEntity), + SectionBy("age"), + Where("gender", isEqualTo: "M"), + OrderBy(.Ascending("age"), .Ascending("name")), + Tweak { (fetchRequest) -> Void in + fetchRequest.fetchBatchSize = 20 + } +) +``` +A list controller created this way will group the objects by the attribute key indicated by the `SectionBy` clause. One more thing to remember is that the `OrderBy` clause should sort the list in such a way that the `SectionBy` attribute would be sorted together (a requirement shared by `NSFetchedResultsController`.) + +The `SectionBy` clause can also be passed a closure to transform the section name into a displayable string: +```swift +self.monitor = CoreStore.monitorSectionedList( + From(MyPersonEntity), + SectionBy("age") { (sectionName) -> String? in + "\(sectionName) years old" + }, + OrderBy(.Ascending("age"), .Ascending("name")) +) +``` +This is useful when implementing a `UITableViewDelegate`'s section header: +```swift +func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + let sectionInfo = self.monitor.sectionInfoAtIndex(section) + // sectionInfo is an NSFetchedResultsSectionInfo instance + return sectionInfo.name +} +``` + +To access the objects of a sectioned list, use an `NSIndexPath` or a tuple: +```swift +let indexPath = NSIndexPath(forRow: 2, inSection: 1) +let person1 = self.monitor[indexPath] +let person2 = self.monitor[1, 2] +// person1 and person2 are the same object +``` + + +# Roadmap +- Support iCloud stores +- CoreSpotlight auto-indexing (experimental) + + +# Installation +- Requires: + - iOS 8 SDK and above + - Swift 2.1 (Xcode 7.2) +- Dependencies: + - [GCDKit](https://github.com/JohnEstropia/GCDKit) + +### Install with CocoaPods +``` +pod 'CoreStore' +``` +This installs CoreStore as a framework. Declare `import CoreStore` in your swift file to use the library. + +### Install with Carthage +In your `Cartfile`, add +``` +github "JohnEstropia/CoreStore" >= 1.4.4 +github "JohnEstropia/GCDKit" >= 1.1.7 +``` +and run +``` +carthage update +``` + +### Install as Git Submodule +``` +git submodule add https://github.com/JohnEstropia/CoreStore.git +``` +Drag and drop **CoreStore.xcodeproj** to your project. + +#### To install as a framework: +Drag and drop **CoreStore.xcodeproj** to your project. + +#### To include directly in your app module: +Add all *.swift* files to your project. + + + +# Changesets +### Upgrading from v0.2.0 to 1.0.0 +- Renamed some classes/protocols to shorter, more relevant, easier to remember names: +- `ManagedObjectController` to `ObjectMonitor` +- `ManagedObjectObserver` to `ObjectObserver` +- `ManagedObjectListController` to `ListMonitor` +- `ManagedObjectListChangeObserver` to `ListObserver` +- `ManagedObjectListObjectObserver` to `ListObjectObserver` +- `ManagedObjectListSectionObserver` to `ListSectionObserver` +- `SectionedBy` to `SectionBy` (match tense with `OrderBy` and `GroupBy`) +The protocols above had their methods renamed as well, to retain the natural language semantics. +- Several methods now `throw` errors insted of returning a result `enum`. +- New migration utilities! (README still pending) Check out *DataStack+Migration.swift* and *CoreStore+Migration.swift* for the new methods, as well as *DataStack.swift* for its new initializer. + + + +# Contributions +While CoreStore's design is pretty solid and the unit test and demo app work well, CoreStore is pretty much still in its early stage. With more exposure to production code usage and criticisms from the developer community, CoreStore hopes to mature as well. +Please feel free to report any issues, suggestions, or criticisms! +日本語で連絡していただいても構いません! + +## License +CoreStore is released under an MIT license. See the LICENSE file for more information + diff --git a/_sass/_base.scss b/_sass/_base.scss new file mode 100644 index 0000000..0883c3c --- /dev/null +++ b/_sass/_base.scss @@ -0,0 +1,206 @@ +/** + * Reset some basic elements + */ +body, h1, h2, h3, h4, h5, h6, +p, blockquote, pre, hr, +dl, dd, ol, ul, figure { + margin: 0; + padding: 0; +} + + + +/** + * Basic styling + */ +body { + font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family; + color: $text-color; + background-color: $background-color; + -webkit-text-size-adjust: 100%; + -webkit-font-feature-settings: "kern" 1; + -moz-font-feature-settings: "kern" 1; + -o-font-feature-settings: "kern" 1; + font-feature-settings: "kern" 1; + font-kerning: normal; +} + + + +/** + * Set `margin-bottom` to maintain vertical rhythm + */ +h1, h2, h3, h4, h5, h6, +p, blockquote, pre, +ul, ol, dl, figure, +%vertical-rhythm { + margin-bottom: $spacing-unit / 2; +} + + + +/** + * Images + */ +img { + max-width: 100%; + vertical-align: middle; +} + + + +/** + * Figures + */ +figure > img { + display: block; +} + +figcaption { + font-size: $small-font-size; +} + + + +/** + * Lists + */ +ul, ol { + margin-left: $spacing-unit; +} + +li { + > ul, + > ol { + margin-bottom: 0; + } +} + + + +/** + * Headings + */ +h1, h2, h3, h4, h5, h6 { + font-weight: $base-font-weight; +} + + + +/** + * Links + */ +a { + color: $brand-color; + text-decoration: none; + + &:visited { + color: darken($brand-color, 15%); + } + + &:hover { + color: $text-color; + text-decoration: underline; + } +} + + + +/** + * Blockquotes + */ +blockquote { + color: $grey-color; + border-left: 4px solid $grey-color-light; + padding-left: $spacing-unit / 2; + font-size: 18px; + letter-spacing: -1px; + font-style: italic; + + > :last-child { + margin-bottom: 0; + } +} + + + +/** + * Code formatting + */ +pre, +code { + font-size: 15px; + border: 1px solid $grey-color-light; + border-radius: 3px; + background-color: #eef; +} + +code { + padding: 1px 5px; +} + +pre { + padding: 8px 12px; + overflow-x: auto; + + > code { + border: 0; + padding-right: 0; + padding-left: 0; + } +} + + + +/** + * Wrapper + */ +.wrapper { + max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit} * 2)); + max-width: calc(#{$content-width} - (#{$spacing-unit} * 2)); + margin-right: auto; + margin-left: auto; + padding-right: $spacing-unit; + padding-left: $spacing-unit; + @extend %clearfix; + + @include media-query($on-laptop) { + max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit})); + max-width: calc(#{$content-width} - (#{$spacing-unit})); + padding-right: $spacing-unit / 2; + padding-left: $spacing-unit / 2; + } +} + + + +/** + * Clearfix + */ +%clearfix { + + &:after { + content: ""; + display: table; + clear: both; + } +} + + + +/** + * Icons + */ +.icon { + + > svg { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: middle; + + path { + fill: $grey-color; + } + } +} diff --git a/_sass/_layout.scss b/_sass/_layout.scss new file mode 100644 index 0000000..9cbfdde --- /dev/null +++ b/_sass/_layout.scss @@ -0,0 +1,242 @@ +/** + * Site header + */ +.site-header { + border-top: 5px solid $grey-color-dark; + border-bottom: 1px solid $grey-color-light; + min-height: 56px; + + // Positioning context for the mobile navigation icon + position: relative; +} + +.site-title { + font-size: 26px; + font-weight: 300; + line-height: 56px; + letter-spacing: -1px; + margin-bottom: 0; + float: left; + + &, + &:visited { + color: $grey-color-dark; + } +} + +.site-nav { + float: right; + line-height: 56px; + + .menu-icon { + display: none; + } + + .page-link { + color: $text-color; + line-height: $base-line-height; + + // Gaps between nav items, but not on the last one + &:not(:last-child) { + margin-right: 20px; + } + } + + @include media-query($on-palm) { + position: absolute; + top: 9px; + right: $spacing-unit / 2; + background-color: $background-color; + border: 1px solid $grey-color-light; + border-radius: 5px; + text-align: right; + + .menu-icon { + display: block; + float: right; + width: 36px; + height: 26px; + line-height: 0; + padding-top: 10px; + text-align: center; + + > svg { + width: 18px; + height: 15px; + + path { + fill: $grey-color-dark; + } + } + } + + .trigger { + clear: both; + display: none; + } + + &:hover .trigger { + display: block; + padding-bottom: 5px; + } + + .page-link { + display: block; + padding: 5px 10px; + + &:not(:last-child) { + margin-right: 0; + } + margin-left: 20px; + } + } +} + + + +/** + * Site footer + */ +.site-footer { + border-top: 1px solid $grey-color-light; + padding: $spacing-unit 0; +} + +.footer-heading { + font-size: 18px; + margin-bottom: $spacing-unit / 2; +} + +.contact-list, +.social-media-list { + list-style: none; + margin-left: 0; +} + +.footer-col-wrapper { + font-size: 15px; + color: $grey-color; + margin-left: -$spacing-unit / 2; + @extend %clearfix; +} + +.footer-col { + float: left; + margin-bottom: $spacing-unit / 2; + padding-left: $spacing-unit / 2; +} + +.footer-col-1 { + width: -webkit-calc(35% - (#{$spacing-unit} / 2)); + width: calc(35% - (#{$spacing-unit} / 2)); +} + +.footer-col-2 { + width: -webkit-calc(20% - (#{$spacing-unit} / 2)); + width: calc(20% - (#{$spacing-unit} / 2)); +} + +.footer-col-3 { + width: -webkit-calc(45% - (#{$spacing-unit} / 2)); + width: calc(45% - (#{$spacing-unit} / 2)); +} + +@include media-query($on-laptop) { + .footer-col-1, + .footer-col-2 { + width: -webkit-calc(50% - (#{$spacing-unit} / 2)); + width: calc(50% - (#{$spacing-unit} / 2)); + } + + .footer-col-3 { + width: -webkit-calc(100% - (#{$spacing-unit} / 2)); + width: calc(100% - (#{$spacing-unit} / 2)); + } +} + +@include media-query($on-palm) { + .footer-col { + float: none; + width: -webkit-calc(100% - (#{$spacing-unit} / 2)); + width: calc(100% - (#{$spacing-unit} / 2)); + } +} + + + +/** + * Page content + */ +.page-content { + padding: $spacing-unit 0; +} + +.page-heading { + font-size: 20px; +} + +.post-list { + margin-left: 0; + list-style: none; + + > li { + margin-bottom: $spacing-unit; + } +} + +.post-meta { + font-size: $small-font-size; + color: $grey-color; +} + +.post-link { + display: block; + font-size: 24px; +} + + + +/** + * Posts + */ +.post-header { + margin-bottom: $spacing-unit; +} + +.post-title { + font-size: 42px; + letter-spacing: -1px; + line-height: 1; + + @include media-query($on-laptop) { + font-size: 36px; + } +} + +.post-content { + margin-bottom: $spacing-unit; + + h2 { + font-size: 32px; + + @include media-query($on-laptop) { + font-size: 28px; + } + } + + h3 { + font-size: 26px; + + @include media-query($on-laptop) { + font-size: 22px; + } + } + + h4 { + font-size: 20px; + + @include media-query($on-laptop) { + font-size: 18px; + } + } +} diff --git a/_sass/_syntax-highlighting.scss b/_sass/_syntax-highlighting.scss new file mode 100644 index 0000000..8fac597 --- /dev/null +++ b/_sass/_syntax-highlighting.scss @@ -0,0 +1,71 @@ +/** + * Syntax highlighting styles + */ +.highlight { + background: #fff; + @extend %vertical-rhythm; + + .highlighter-rouge & { + background: #eef; + } + + .c { color: #998; font-style: italic } // Comment + .err { color: #a61717; background-color: #e3d2d2 } // Error + .k { font-weight: bold } // Keyword + .o { font-weight: bold } // Operator + .cm { color: #998; font-style: italic } // Comment.Multiline + .cp { color: #999; font-weight: bold } // Comment.Preproc + .c1 { color: #998; font-style: italic } // Comment.Single + .cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special + .gd { color: #000; background-color: #fdd } // Generic.Deleted + .gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific + .ge { font-style: italic } // Generic.Emph + .gr { color: #a00 } // Generic.Error + .gh { color: #999 } // Generic.Heading + .gi { color: #000; background-color: #dfd } // Generic.Inserted + .gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific + .go { color: #888 } // Generic.Output + .gp { color: #555 } // Generic.Prompt + .gs { font-weight: bold } // Generic.Strong + .gu { color: #aaa } // Generic.Subheading + .gt { color: #a00 } // Generic.Traceback + .kc { font-weight: bold } // Keyword.Constant + .kd { font-weight: bold } // Keyword.Declaration + .kp { font-weight: bold } // Keyword.Pseudo + .kr { font-weight: bold } // Keyword.Reserved + .kt { color: #458; font-weight: bold } // Keyword.Type + .m { color: #099 } // Literal.Number + .s { color: #d14 } // Literal.String + .na { color: #008080 } // Name.Attribute + .nb { color: #0086B3 } // Name.Builtin + .nc { color: #458; font-weight: bold } // Name.Class + .no { color: #008080 } // Name.Constant + .ni { color: #800080 } // Name.Entity + .ne { color: #900; font-weight: bold } // Name.Exception + .nf { color: #900; font-weight: bold } // Name.Function + .nn { color: #555 } // Name.Namespace + .nt { color: #000080 } // Name.Tag + .nv { color: #008080 } // Name.Variable + .ow { font-weight: bold } // Operator.Word + .w { color: #bbb } // Text.Whitespace + .mf { color: #099 } // Literal.Number.Float + .mh { color: #099 } // Literal.Number.Hex + .mi { color: #099 } // Literal.Number.Integer + .mo { color: #099 } // Literal.Number.Oct + .sb { color: #d14 } // Literal.String.Backtick + .sc { color: #d14 } // Literal.String.Char + .sd { color: #d14 } // Literal.String.Doc + .s2 { color: #d14 } // Literal.String.Double + .se { color: #d14 } // Literal.String.Escape + .sh { color: #d14 } // Literal.String.Heredoc + .si { color: #d14 } // Literal.String.Interpol + .sx { color: #d14 } // Literal.String.Other + .sr { color: #009926 } // Literal.String.Regex + .s1 { color: #d14 } // Literal.String.Single + .ss { color: #990073 } // Literal.String.Symbol + .bp { color: #999 } // Name.Builtin.Pseudo + .vc { color: #008080 } // Name.Variable.Class + .vg { color: #008080 } // Name.Variable.Global + .vi { color: #008080 } // Name.Variable.Instance + .il { color: #099 } // Literal.Number.Integer.Long +} diff --git a/about.md b/about.md new file mode 100644 index 0000000..d0e6de5 --- /dev/null +++ b/about.md @@ -0,0 +1,15 @@ +--- +layout: page +title: About +permalink: /about/ +--- + +This is the base Jekyll theme. You can find out more info about customizing your Jekyll theme, as well as basic Jekyll usage documentation at [jekyllrb.com](http://jekyllrb.com/) + +You can find the source code for the Jekyll new theme at: +{% include icon-github.html username="jglovier" %} / +[jekyll-new](https://github.com/jglovier/jekyll-new) + +You can find the source code for Jekyll at +{% include icon-github.html username="jekyll" %} / +[jekyll](https://github.com/jekyll/jekyll) diff --git a/css/main.scss b/css/main.scss new file mode 100644 index 0000000..f2e566e --- /dev/null +++ b/css/main.scss @@ -0,0 +1,53 @@ +--- +# Only the main Sass file needs front matter (the dashes are enough) +--- +@charset "utf-8"; + + + +// Our variables +$base-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +$base-font-size: 16px; +$base-font-weight: 400; +$small-font-size: $base-font-size * 0.875; +$base-line-height: 1.5; + +$spacing-unit: 30px; + +$text-color: #111; +$background-color: #fdfdfd; +$brand-color: #2a7ae2; + +$grey-color: #828282; +$grey-color-light: lighten($grey-color, 40%); +$grey-color-dark: darken($grey-color, 25%); + +// Width of the content area +$content-width: 800px; + +$on-palm: 600px; +$on-laptop: 800px; + + + +// Use media queries like this: +// @include media-query($on-palm) { +// .wrapper { +// padding-right: $spacing-unit / 2; +// padding-left: $spacing-unit / 2; +// } +// } +@mixin media-query($device) { + @media screen and (max-width: $device) { + @content; + } +} + + + +// Import partials from `sass_dir` (defaults to `_sass`) +@import + "base", + "layout", + "syntax-highlighting" +; diff --git a/feed.xml b/feed.xml new file mode 100644 index 0000000..a6628bd --- /dev/null +++ b/feed.xml @@ -0,0 +1,30 @@ +--- +layout: null +--- + + + + {{ site.title | xml_escape }} + {{ site.description | xml_escape }} + {{ site.url }}{{ site.baseurl }}/ + + {{ site.time | date_to_rfc822 }} + {{ site.time | date_to_rfc822 }} + Jekyll v{{ jekyll.version }} + {% for post in site.posts limit:10 %} + + {{ post.title | xml_escape }} + {{ post.content | xml_escape }} + {{ post.date | date_to_rfc822 }} + {{ post.url | prepend: site.baseurl | prepend: site.url }} + {{ post.url | prepend: site.baseurl | prepend: site.url }} + {% for tag in post.tags %} + {{ tag | xml_escape }} + {% endfor %} + {% for cat in post.categories %} + {{ cat | xml_escape }} + {% endfor %} + + {% endfor %} + + diff --git a/index.html b/index.html index 1500c3d..83d9398 100644 --- a/index.html +++ b/index.html @@ -1,1454 +1,23 @@ - - - - - Corestore by JohnEstropia - - - - - - - - +--- +layout: default +--- -
-

corestore

+
-

Build Status -Version -Platform -License -Carthage compatible

+

Posts

-

Unleashing the real power of Core Data with the elegance and safety of Swift

+
    + {% for post in site.posts %} +
  • + -
      -
    • Swift 2.2 (Xcode 7.3)
    • -
    • iOS 8+ / OSX 10.10+ / watchOS 2.0+ / tvOS 9.0+
    • -
    +

    + {{ post.title }} +

    +
  • + {% endfor %} +
+

subscribe via RSS

- -

-What CoreStore does better:

- -
    -
  • -Heavily supports multiple persistent stores per data stack, just the way .xcdatamodeld files are designed to. CoreStore will also manage one data stack by default, but you can create and manage as many as you need.
  • -
  • -Progressive Migrations! Just tell the data stack the sequence of model versions and CoreStore will automatically use progressive migrations if needed on stores added to that stack.
  • -
  • Ability to plug-in your own logging framework -
  • -
  • 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 managed object model file, so you are free to name entities and their class names independently.
  • -
  • Provides type-safe, easy to configure observers to replace NSFetchedResultsController and KVO -
  • -
  • Exposes API not just for fetching, but also for querying aggregates and property values -
  • -
  • Makes it hard to fall into common concurrency mistakes. All NSManagedObjectContext tasks are encapsulated into safer, higher-level abstractions without sacrificing flexibility and customizability.
  • -
  • Exposes clean and convenient API designed around Swift’s code elegance and type safety.
  • -
  • -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.
  • -
  • Efficient importing utilities!
  • -
- -

Vote for the next feature!

- -

-Contents

- - - -

-TL;DR (a.k.a. sample codes)

- -

Setting-up with progressive migration support:

- -
CoreStore.defaultStack = DataStack(
-    modelName: "MyStore",
-    migrationChain: ["MyStore", "MyStoreV2", "MyStoreV3"]
-)
- -

Adding a store:

- -
try CoreStore.addSQLiteStore(
-    fileName: "MyStore.sqlite",
-    completion: { (result) -> Void in
-        // ...
-    }
-)
- -

Starting transactions:

- -
CoreStore.beginAsynchronous { (transaction) -> Void in
-    let person = transaction.create(Into(MyPersonEntity))
-    person.name = "John Smith"
-    person.age = 42
-
-    transaction.commit { (result) -> Void in
-        switch result {
-            case .Success(let hasChanges): print("success!")
-            case .Failure(let error): print(error)
-        }
-    }
-}
- -

Fetching objects:

- -
let people = CoreStore.fetchAll(From(MyPersonEntity))
- -
let people = CoreStore.fetchAll(
-    From(MyPersonEntity),
-    Where("age > 30"),
-    OrderBy(.Ascending("name"), .Descending("age")),
-    Tweak { (fetchRequest) -> Void in
-        fetchRequest.includesPendingChanges = false
-    }
-)
- -

Querying values:

- -
let maxAge = CoreStore.queryValue(
-    From(MyPersonEntity),
-    Select<Int>(.Maximum("age"))
-)
- -

But really, there's a reason I wrote this huge README. Read up on the details!

- -

Check out the CoreStoreDemo app project for sample codes as well!

- -

-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 DataCoreStore
-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, UnsafeDataTransaction)
- -

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

- -

nested contexts

- -

Nesting context saves from child context to the root context 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 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.

- -

-Setting up

- -

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

- -
do {
-    try CoreStore.addSQLiteStoreAndWait()
-}
-catch {
-    // ...
-}
- -

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 SQLite store in the "Application Support/" directory (or the "Caches/" directory on tvOS) 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
-    migrationChain: ["MyStore", "MyStoreV2", "MyStoreV3"] // model versions for progressive migrations
-)
-
-do {
-    // creates an in-memory store with entities from the "Config1" configuration in the .xcdatamodeld file
-    let persistentStore = try dataStack.addInMemoryStoreAndWait(configuration: "Config1") // persistentStore is an NSPersistentStore instance
-    print("Successfully created an in-memory store: \(persistentStore)"
-}
-catch {
-    print("Failed creating an in-memory store with error: \(error as NSError)"
-}
-
-do {
-    try 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
-        resetStoreOnModelMismatch: true,
-        completion: { (result) -> Void in
-            switch result {
-            case .Success(let persistentStore):
-                print("Successfully added sqlite store: \(persistentStore)"
-            case .Failure(let error):
-                print("Failed adding sqlite store with error: \(error)"
-            }
-        }
-    )
-}
-catch {
-    print("Failed adding sqlite store with error: \(error as NSError)"
-}
-
-CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier access later on
- -

(If you have never heard of "Configurations", you'll find them in your .xcdatamodeld file) -xcode configurations screenshot

- -

In our sample code above, note that you don't need to do the CoreStore.defaultStack = dataStack line. You can just as well hold a reference to the DataStack like below and call all its instance methods directly:

- -
class MyViewController: UIViewController {
-    let dataStack = DataStack(modelName: "MyModel")
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        do {
-            try self.dataStack.addSQLiteStoreAndWait()
-        }
-        catch { // ...
-        }
-    }
-    func methodToBeCalledLaterOn() {
-        let objects = self.dataStack.fetchAll(From(MyEntity))
-        print(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()
-        do {
-            try CoreStore.addSQLiteStoreAndWait()
-        }
-        catch { // ...
-        }
-    }
-    func methodToBeCalledLaterOn() {
-        let objects = CoreStore.fetchAll(From(MyEntity))
-        print(objects)
-    }
-}
- -

-Migrations

- -

So far we have only seen addSQLiteStoreAndWait(...) used to initialize our persistent store. As the method name's "AndWait" suffix suggests, this method blocks so it should not do long tasks such as store migrations (in fact CoreStore won't even attempt to, and any model mismatch will be reported as an error). If migrations are expected, the asynchronous variant addSQLiteStore(... completion:) method should be used instead:

- -
do {
-    let progress: NSProgress? = try dataStack.addSQLiteStore(
-        fileName: "MyStore.sqlite",
-        configuration: "Config2",
-        completion: { (result) -> Void in
-            switch result {
-            case .Success(let persistentStore):
-                print("Successfully added sqlite store: \(persistentStore)")
-            case .Failure(let error):
-                print("Failed adding sqlite store with error: \(error)")
-            }
-        }
-    )
-}
-catch {
-    print("Failed adding sqlite store with error: \(error as NSError)"
-}
- -

The completion block reports a PersistentStoreResult that indicates success or failure.

- -

addSQLiteStore(...) throws an error if the store at the specified URL conflicts with an existing store in the DataStack, or if an existing sqlite file could not be read. If an error is thrown, the completion block will not be executed.

- -

Notice that this method also returns an optional NSProgress. If nil, no migrations are needed, thus progress reporting is unnecessary as well. If not nil, you can use this to track migration progress by using standard KVO on the "fractionCompleted" key, or by using a closure-based utility exposed in NSProgress+Convenience.swift:

- -
progress?.setProgressHandler { [weak self] (progress) -> Void in
-    self?.progressView?.setProgress(Float(progress.fractionCompleted), animated: true)
-    self?.percentLabel?.text = progress.localizedDescription // "50% completed"
-    self?.stepLabel?.text = progress.localizedAdditionalDescription // "0 of 2"
-}
- -

This closure is executed on the main thread so UIKit calls can be done safely.

- -

-Progressive migrations

- -

By default, CoreStore uses Core Data's default automatic migration mechanism. In other words, CoreStore will try to migrate the existing persistent store to the .xcdatamodeld file's current model version. If no mapping model is found from the store's version to the data model's version, CoreStore gives up and reports an error.

- -

The DataStack lets you specify hints on how to break a migration into several sub-migrations using a MigrationChain. This is typically passed to the DataStack initializer and will be applied to all stores added to the DataStack with addSQLiteStore(...) and its variants:

- -
let dataStack = DataStack(migrationChain: 
-    ["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4"])
- -

The most common usage is to pass in the .xcdatamodeld version names in increasing order as above.

- -

For more complex migration paths, you can also pass in a version tree that maps the key-values to the source-destination versions:

- -
let dataStack = DataStack(migrationChain: [
-    "MyAppModel": "MyAppModelV3",
-    "MyAppModelV2": "MyAppModelV4",
-    "MyAppModelV3": "MyAppModelV4"
-])
- -

This allows for different migration paths depending on the starting version. The example above resolves to the following paths:

- -
    -
  • MyAppModel-MyAppModelV3-MyAppModelV4
  • -
  • MyAppModelV2-MyAppModelV4
  • -
  • MyAppModelV3-MyAppModelV4
  • -
- -

Initializing with empty values (either nil, [], or [:]) instructs the DataStack to disable progressive migrations and revert to the default migration behavior (i.e. use the .xcdatamodel's current version as the final version):

- -
let dataStack = DataStack(migrationChain: nil)
- -

The MigrationChain is validated when passed to the DataStack and unless it is empty, will raise an assertion if any of the following conditions are met:

- -
    -
  • a version appears twice in an array
  • -
  • a version appears twice as a key in a dictionary literal
  • -
  • a loop is found in any of the paths
  • -
- -

One important thing to remember is that if a MigrationChain is specified, the .xcdatamodeld's "Current Version" will be bypassed and the MigrationChain's leafmost version will be the DataStack's base model version.

- -

-Forecasting migrations

- -

Sometimes migrations are huge and you may want prior information so your app could display a loading screen, or to display a confirmation dialog to the user. For this, CoreStore provides a requiredMigrationsForSQLiteStore(...) method you can use to inspect a persistent store before you actually call addSQLiteStore(...):

- -
do {
-    let migrationTypes: [MigrationType] = CoreStore.requiredMigrationsForSQLiteStore(fileName: "MyStore.sqlite")
-    if migrationTypes.count > 1
-        || (migrationTypes.filter { $0.isHeavyweightMigration }.count) > 0 {
-        // ... Show special waiting screen
-    }
-    else if migrationTypes.count > 0 {
-        // ... Show simple activity indicator
-    }
-    else {
-        // ... Do nothing
-    }
-
-    CoreStore.addSQLiteStore(/* ... */)
-}
-catch {
-    // ...
-}
- -

requiredMigrationsForSQLiteStore(...) returns an array of MigrationTypes, where each item in the array may be either of the following values:

- -
case Lightweight(sourceVersion: String, destinationVersion: String)
-case Heavyweight(sourceVersion: String, destinationVersion: String)
- -

Each MigrationType indicates the migration type for each step in the MigrationChain. Use these information as fit for your app.

- -

-Saving and processing transactions

- -

To ensure deterministic state for objects in the read-only NSManagedObjectContext, CoreStore does not expose API's for updating and saving directly from the main context (or any other context for that matter.) Instead, you spawn transactions from DataStack instances:

- -
let dataStack = self.dataStack
-dataStack.beginAsynchronous { (transaction) -> Void in
-    // make changes
-    transaction.commit()
-}
- -

or for the default stack, directly from CoreStore:

- -
CoreStore.beginAsynchronous { (transaction) -> Void in
-    // make changes
-    transaction.commit()
-}
- -

The commit() method saves the changes to the persistent store. If commit() is not called when the transaction block completes, all changes within the transaction is discarded.

- -

The examples above use beginAsynchronous(...), but there are actually 3 types of transactions at your disposal: asynchronous, synchronous, and unsafe.

- -

-Transaction types

- -

-Asynchronous transactions

- -

are spawned from beginAsynchronous(...). This method returns immediately and executes its closure from a background serial queue:

- -
CoreStore.beginAsynchronous { (transaction) -> Void in
-    // make changes
-    transaction.commit()
-}
- -

Transactions created from beginAsynchronous(...) are instances of AsynchronousDataTransaction.

- -

-Synchronous transactions

- -

are created from beginSynchronous(...). While the syntax is similar to its asynchronous counterpart, beginSynchronous(...) waits for its transaction block to complete before returning:

- -
CoreStore.beginSynchronous { (transaction) -> Void in
-    // make changes
-    transaction.commit()
-} 
- -

transaction above is a SynchronousDataTransaction instance.

- -

Since beginSynchronous(...) technically blocks two queues (the caller's queue and the transaction's background queue), it is considered less safe as it's more prone to deadlock. Take special care that the closure does not block on any other external queues.

- -

-Unsafe transactions

- -

are special in that they do not enclose updates within a closure:

- -
let transaction = CoreStore.beginUnsafe()
-// make changes
-downloadJSONWithCompletion({ (json) -> Void in
-
-    // make other changes
-    transaction.commit()
-})
-downloadAnotherJSONWithCompletion({ (json) -> Void in
-
-    // make some other changes
-    transaction.commit()
-})
- -

This allows for non-contiguous updates. Do note that this flexibility comes with a price: you are now responsible for managing concurrency for the transaction. As uncle Ben said, "with great power comes great race conditions."

- -

As the above example also shows, only unsafe transactions are allowed to call commit() multiple times; doing so with synchronous and asynchronous transactions will trigger an assert.

- -

You've seen how to create transactions, but we have yet to see how to make creates, updates, and deletes. The 3 types of transactions above are all subclasses of BaseDataTransaction, which implements the methods shown below.

- -

-Creating objects

- -

The create(...) method accepts an Into clause which specifies the entity for the object you want to create:

- -
let person = transaction.create(Into(MyPersonEntity))
- -

While the syntax is straightforward, CoreStore does not just naively insert a new object. This single line does the following:

- -
    -
  • Checks that the entity type exists in any of the transaction's parent persistent store
  • -
  • If the entity belongs to only one persistent store, a new object is inserted into that store and returned from create(...) -
  • -
  • If the entity does not belong to any store, an assert will be triggered. This is a programmer error and should never occur in production code. -
  • -
  • If the entity belongs to multiple stores, an assert will be triggered. This is also a programmer error and should never occur in production code. Normally, with Core Data you can insert an object in this state but saving the NSManagedObjectContext will always fail. CoreStore checks this for you at creation time when it makes sense (not during save).
  • -
- -

If the entity exists in multiple configurations, you need to provide the configuration name for the destination persistent store:

- -
let person = transaction.create(Into<MyPersonEntity>("Config1"))
-
- -

or if the persistent store is the auto-generated "Default" configuration, specify nil:

- -
let person = transaction.create(Into<MyPersonEntity>(nil))
-
- -

Note that if you do explicitly specify the configuration name, CoreStore will only try to insert the created object to that particular store and will fail if that store is not found; it will not fall back to any other configuration that the entity belongs to.

- -

-Updating objects

- -

After creating an object from the transaction, you can simply update its properties as normal:

- -
CoreStore.beginAsynchronous { (transaction) -> Void in
-    let person = transaction.create(Into(MyPersonEntity))
-    person.name = "John Smith"
-    person.age = 30
-    transaction.commit()
-}
- -

To update an existing object, fetch the object's instance from the transaction:

- -
CoreStore.beginAsynchronous { (transaction) -> Void in
-    let person = transaction.fetchOne(
-        From(MyPersonEntity),
-        Where("name", isEqualTo: "Jane Smith")
-    )
-    person.age = person.age + 1
-    transaction.commit()
-}
- -

(For more about fetching, see Fetching and querying)

- -

Do not update an instance that was not created/fetched from the transaction. If you have a reference to the object already, use the transaction's edit(...) method to get an editable proxy instance for that object:

- -
let jane: MyPersonEntity = // ...
-
-CoreStore.beginAsynchronous { (transaction) -> Void in
-    // WRONG: jane.age = jane.age + 1
-    // RIGHT:
-    let jane = transaction.edit(jane)! // using the same variable name protects us from misusing the non-transaction instance
-    jane.age = jane.age + 1
-    transaction.commit()
-}
- -

This is also true when updating an object's relationships. Make sure that the object assigned to the relationship is also created/fetched from the transaction:

- -
let jane: MyPersonEntity = // ...
-let john: MyPersonEntity = // ...
-
-CoreStore.beginAsynchronous { (transaction) -> Void in
-    // WRONG: jane.friends = [john]
-    // RIGHT:
-    let jane = transaction.edit(jane)!
-    let john = transaction.edit(john)!
-    jane.friends = NSSet(array: [john])
-    transaction.commit()
-}
- -

-Deleting objects

- -

Deleting an object is simpler because you can tell a transaction to delete an object directly without fetching an editable proxy (CoreStore does that for you):

- -
let john: MyPersonEntity = // ...
-
-CoreStore.beginAsynchronous { (transaction) -> Void in
-    transaction.delete(john)
-    transaction.commit()
-}
- -

or several objects at once:

- -
let john: MyPersonEntity = // ...
-let jane: MyPersonEntity = // ...
-
-CoreStore.beginAsynchronous { (transaction) -> Void in
-    transaction.delete(john, jane)
-    // transaction.delete([john, jane]) is also allowed
-    transaction.commit()
-}
- -

If you do not have references yet to the objects to be deleted, transactions have a deleteAll(...) method you can pass a query to:

- -
CoreStore.beginAsynchronous { (transaction) -> Void in
-    transaction.deleteAll(
-        From(MyPersonEntity)
-        Where("age > 30")
-    )
-    transaction.commit()
-}
- -

-Passing objects safely

- -

Always remember that the DataStack and individual transactions manage different NSManagedObjectContexts so you cannot just use objects between them. That's why transactions have an edit(...) method:

- -
let jane: MyPersonEntity = // ...
-
-CoreStore.beginAsynchronous { (transaction) -> Void in
-    let jane = transaction.edit(jane)!
-    jane.age = jane.age + 1
-    transaction.commit()
-}
- -

But CoreStore, DataStack and BaseDataTransaction have a very flexible fetchExisting(...) method that you can pass instances back and forth with:

- -
let jane: MyPersonEntity = // ...
-
-CoreStore.beginAsynchronous { (transaction) -> Void in
-    let jane = transaction.fetchExisting(jane)! // instance for transaction
-    jane.age = jane.age + 1
-    transaction.commit {
-        let jane = CoreStore.fetchExisting(jane)! // instance for DataStack
-        print(jane.age)
-    }
-}
- -

fetchExisting(...) also works with multiple NSManagedObjects or with NSManagedObjectIDs:

- -
var peopleIDs: [NSManagedObjectID] = // ...
-
-CoreStore.beginAsynchronous { (transaction) -> Void in
-    let jane = transaction.fetchOne(
-        From(MyPersonEntity),
-        Where("name", isEqualTo: "Jane Smith")
-    )
-    jane.friends = NSSet(array: transaction.fetchExisting(peopleIDs)!)
-    // ...
-}
- -

-Importing data

- -

Some times, if not most of the time, the data that we save to Core Data comes from external sources such as web servers or external files. Say you have a JSON dictionary, you may be extracting values as such:

- -
let json: [String: AnyObject] = // ...
-person.name = json["name"] as? NSString
-person.age = json["age"] as? NSNumber
-// ...
- -

If you have many attributes, you don't want to keep repeating this mapping everytime you want to import data. CoreStore lets you write the data mapping code just once, and all you have to do is call importObject(...) or importUniqueObject(...) through BaseDataTransaction subclasses:

- -
CoreStore.beginAsynchronous { (transaction) -> Void in
-    let json: [String: AnyObject] = // ...
-    try! transaction.importObject(
-        Into(MyPersonEntity),
-        source: json
-    )
-    transaction.commit()
-}
- -

To support data import for an entity, implement either ImportableObject or ImportableUniqueObject on the NSManagedObject subclass:

- -
    -
  • -ImportableObject: Use this protocol if the object have no inherent uniqueness and new objects should always be added when calling importObject(...).
  • -
  • -ImportableUniqueObject: Use this protocol to specify a unique ID for an object that will be used to distinguish whether a new object should be created or if an existing object should be updated when calling importUniqueObject(...).
  • -
- -

Both protocols require implementers to specify an ImportSource which can be set to any type that the object can extract data from:

- -
typealias ImportSource = NSDictionary
- -
typealias ImportSource = [String: AnyObject]
- -
typealias ImportSource = NSData
- -

You can even use external types from popular 3rd-party JSON libraries (SwiftyJSON's JSON type is a personal favorite), or just simple tuples or primitives.

- -

-ImportableObject -

- -

ImportableObject is a very simple protocol:

- -
public protocol ImportableObject: class {
-    typealias ImportSource
-    static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
-    func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws
-}
- -

First, set ImportSource to the expected type of the data source:

- -
typealias ImportSource = [String: AnyObject]
- -

This lets us call importObject(_:source:) with any [String: AnyObject] type as the argument to source:

- -
CoreStore.beginAsynchronous { (transaction) -> Void in
-    let json: [String: AnyObject] = // ...
-    try! transaction.importObject(
-        Into(MyPersonEntity),
-        source: json
-    )
-    // ...
-}
- -

The actual extraction and assignment of values should be implemented in the didInsertFromImportSource(...) method of the ImportableObject protocol:

- -
func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws {
-    self.name = source["name"] as? NSString
-    self.age = source["age"] as? NSNumber
-    // ...
-}
- -

Transactions also let you import multiple objects at once using the importObjects(_:sourceArray:) method:

- -
CoreStore.beginAsynchronous { (transaction) -> Void in
-    let jsonArray: [[String: AnyObject]] = // ...
-    try! transaction.importObjects(
-        Into(MyPersonEntity),
-        sourceArray: jsonArray
-    )
-    // ...
-}
- -

Doing so tells the transaction to iterate through the array of import sources and calls shouldInsertFromImportSource(...) on the ImportableObject to determine which instances should be created. You can do validations and return false from shouldInsertFromImportSource(...) if you want to skip importing from a source and continue on with the other sources in the array.

- -

If on the other hand, your validation in one of the sources failed in such a manner that all other sources should also be cancelled, you can throw from within didInsertFromImportSource(...):

- -
func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws {
-    self.name = source["name"] as? NSString
-    self.age = source["age"] as? NSNumber
-    // ...
-    if self.name == nil {
-        throw Errors.InvalidNameError
-    }
-}
- -

Doing so can let you abandon an invalid transaction immediately:

- -
CoreStore.beginAsynchronous { (transaction) -> Void in
-    let jsonArray: [[String: AnyObject]] = // ...
-    do {
-        try transaction.importObjects(
-            Into(MyPersonEntity),
-            sourceArray: jsonArray
-        )
-    }
-    catch {
-        return // Woops, don't save
-    }
-    transaction.commit {
-        // ...
-    }
-}
- -

-ImportableUniqueObject -

- -

Typically, we don't just keep creating objects every time we import data. Usually we also need to update already existing objects. Implementing the ImportableUniqueObject protocol lets you specify a "unique ID" that transactions can use to search existing objects before creating new ones:

- -
public protocol ImportableUniqueObject: ImportableObject {
-    typealias ImportSource
-    typealias UniqueIDType: NSObject
-
-    static var uniqueIDKeyPath: String { get }
-    var uniqueIDValue: UniqueIDType { get set }
-
-    static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
-    static func shouldUpdateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
-    static func uniqueIDFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws -> UniqueIDType?
-    func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws
-    func updateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws
-}
- -

Notice that it has the same insert methods as ImportableObject, with additional methods for updates and for specifying the unique ID:

- -
class var uniqueIDKeyPath: String {
-    return "personID" 
-}
-var uniqueIDValue: NSNumber { 
-    get { return self.personID }
-    set { self.personID = newValue }
-}
-class func uniqueIDFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws -> NSNumber? {
-    return source["id"] as? NSNumber
-}
- -

For ImportableUniqueObject, the extraction and assignment of values should be implemented from the updateFromImportSource(...) method. The didInsertFromImportSource(...) by default calls updateFromImportSource(...), but you can separate the implementation for inserts and updates if needed.

- -

You can then create/update an object by calling a transaction's importUniqueObject(...) method:

- -
CoreStore.beginAsynchronous { (transaction) -> Void in
-    let json: [String: AnyObject] = // ...
-    try! transaction.importUniqueObject(
-        Into(MyPersonEntity),
-        source: json
-    )
-    // ...
-}
- -

or multiple objects at once with the importUniqueObjects(...) method:

- -
CoreStore.beginAsynchronous { (transaction) -> Void in
-    let jsonArray: [[String: AnyObject]] = // ...
-    try! transaction.importObjects(
-        Into(MyPersonEntity),
-        sourceArray: jsonArray
-    )
-    // ...
-}
- -

As with ImportableObject, you can control wether to skip importing an object by implementing -shouldInsertFromImportSource(...) and shouldUpdateFromImportSource(...), or to cancel all objects by throwing an error from the uniqueIDFromImportSource(...), didInsertFromImportSource(...) or updateFromImportSource(...) methods.

- -

-Fetching and Querying

- -

Before we dive in, be aware that CoreStore distinguishes between fetching and querying:

- -
    -
  • A fetch executes searches from a specific transaction or data stack. This means fetches can include pending objects (i.e. before a transaction calls on commit().) Use fetches when: - -
      -
    • results need to be NSManagedObject instances
    • -
    • unsaved objects should be included in the search (though fetches can be configured to exclude unsaved ones)
    • -
    -
  • -
  • A query pulls data straight from the persistent store. This means faster searches when computing aggregates such as count, min, max, etc. Use queries when: - -
      -
    • you need to compute aggregate functions (see below for a list of supported functions)
    • -
    • results can be raw values like NSStrings, NSNumbers, Ints, NSDates, an NSDictionary of key-values, etc.
    • -
    • only values for specified attribute keys need to be included in the results
    • -
    • unsaved objects should be ignored
    • -
    -
  • -
- -

-From clause

- -

The search conditions for fetches and queries are specified using clauses. All fetches and queries require a From clause that indicates the target entity type:

- -
let people = CoreStore.fetchAll(From(MyPersonEntity))
-// CoreStore.fetchAll(From<MyPersonEntity>()) works as well
- -

people in the example above will be of type [MyPersonEntity]. The From(MyPersonEntity) clause indicates a fetch to all persistent stores that MyPersonEntity belong to.

- -

If the entity exists in multiple configurations and you need to only search from a particular configuration, indicate in the From clause the configuration name for the destination persistent store:

- -
let people = CoreStore.fetchAll(From<MyPersonEntity>("Config1")) // ignore objects in persistent stores other than the "Config1" configuration
- -

or if the persistent store is the auto-generated "Default" configuration, specify nil:

- -
let person = CoreStore.fetchAll(From<MyPersonEntity>(nil))
- -

Now we know how to use a From clause, let's move on to fetching and querying.

- -

-Fetching

- -

There are currently 5 fetch methods you can call from CoreStore, from a DataStack instance, or from a BaseDataTransaction instance. All of the methods below accept the same parameters: a required From clause, and an optional series of Where, OrderBy, and/or Tweak clauses.

- -
    -
  • -fetchAll(...) - returns an array of all objects that match the criteria.
  • -
  • -fetchOne(...) - returns the first object that match the criteria.
  • -
  • -fetchCount(...) - returns the number of objects that match the criteria.
  • -
  • -fetchObjectIDs(...) - returns an array of NSManagedObjectIDs for all objects that match the criteria.
  • -
  • -fetchObjectID(...) - returns the NSManagedObjectIDs for the first objects that match the criteria.
  • -
- -

Each method's purpose is straightforward, but we need to understand how to set the clauses for the fetch.

- -

-Where clause

- -

The Where clause is CoreStore's NSPredicate wrapper. It specifies the search filter to use when fetching (or querying). It implements all initializers that NSPredicate does (except for -predicateWithBlock:, which Core Data does not support):

- -
var people = CoreStore.fetchAll(
-    From(MyPersonEntity),
-    Where("%K > %d", "age", 30) // string format initializer
-)
-people = CoreStore.fetchAll(
-    From(MyPersonEntity),
-    Where(true) // boolean initializer
-)
- -

If you do have an existing NSPredicate instance already, you can pass that to Where as well:

- -
let predicate = NSPredicate(...)
-var people = CoreStore.fetchAll(
-    From(MyPersonEntity),
-    Where(predicate) // predicate initializer
-)
- -

Where clauses also implement the &&, ||, and ! logic operators, so you can provide logical conditions without writing too much AND, OR, and NOT strings:

- -
var people = CoreStore.fetchAll(
-    From(MyPersonEntity),
-    Where("age > %d", 30) && Where("gender == %@", "M")
-)
- -

If you do not provide a Where clause, all objects that belong to the specified From will be returned.

- -

-OrderBy clause

- -

The OrderBy clause is CoreStore's NSSortDescriptor wrapper. Use it to specify attribute keys in which to sort the fetch (or query) results with.

- -
var mostValuablePeople = CoreStore.fetchAll(
-    From(MyPersonEntity),
-    OrderBy(.Descending("rating"), .Ascending("surname"))
-)
- -

As seen above, OrderBy accepts a list of SortKey enumeration values, which can be either .Ascending or .Descending.

- -

You can use the + and += operator to append OrderBys together. This is useful when sorting conditionally:

- -
var orderBy = OrderBy(.Descending("rating"))
-if sortFromYoungest {
-    orderBy += OrderBy(.Ascending("age"))
-}
-var mostValuablePeople = CoreStore.fetchAll(
-    From(MyPersonEntity),
-    orderBy
-)
- -

-Tweak clause

- -

The Tweak clause lets you, uh, tweak the fetch (or query). Tweak exposes the NSFetchRequest in a closure where you can make changes to its properties:

- -
var people = CoreStore.fetchAll(
-    From(MyPersonEntity),
-    Where("age > %d", 30),
-    OrderBy(.Ascending("surname")),
-    Tweak { (fetchRequest) -> Void in
-        fetchRequest.includesPendingChanges = false
-        fetchRequest.returnsObjectsAsFaults = false
-        fetchRequest.includesSubentities = false
-    }
-)
- -

The clauses are evaluated the order they appear in the fetch/query, so you typically need to set Tweak as the last clause. -Tweak's closure is executed only just before the fetch occurs, so make sure that any values captured by the closure is not prone to race conditions.

- -

While Tweak lets you micro-configure the NSFetchRequest, note that CoreStore already preconfigured that NSFetchRequest to suitable defaults. Only use Tweak when you know what you are doing!

- -

-Querying

- -

One of the functionalities overlooked by other Core Data wrapper libraries is raw properties fetching. If you are familiar with NSDictionaryResultType and -[NSFetchedRequest propertiesToFetch], you probably know how painful it is to setup a query for raw values and aggregate values. CoreStore makes this easy by exposing the 2 methods below:

- -
    -
  • -queryValue(...) - returns a single raw value for an attribute or for an aggregate value. If there are multiple results, queryValue(...) only returns the first item.
  • -
  • -queryAttributes(...) - returns an array of dictionaries containing attribute keys with their corresponding values.
  • -
- -

Both methods above accept the same parameters: a required From clause, a required Select<T> clause, and an optional series of Where, OrderBy, GroupBy, and/or Tweak clauses.

- -

Setting up the From, Where, OrderBy, and Tweak clauses is similar to how you would when fetching. For querying, you also need to know how to use the Select<T> and GroupBy clauses.

- -

-Select<T> clause

- -

The Select<T> clause specifies the target attribute/aggregate key, as well as the expected return type:

- -
let johnsAge = CoreStore.queryValue(
-    From(MyPersonEntity),
-    Select<Int>("age"),
-    Where("name == %@", "John Smith")
-)
- -

The example above queries the "age" property for the first object that matches the Where condition. johnsAge will be bound to type Int?, as indicated by the Select<Int> generic type. For queryValue(...), the following are allowed as the return type (and therefore as the generic type for Select<T>):

- -
    -
  • Bool
  • -
  • Int8
  • -
  • Int16
  • -
  • Int32
  • -
  • Int64
  • -
  • Double
  • -
  • Float
  • -
  • String
  • -
  • NSNumber
  • -
  • NSString
  • -
  • NSDecimalNumber
  • -
  • NSDate
  • -
  • NSData
  • -
  • NSManagedObjectID
  • -
  • NSString
  • -
- -

For queryAttributes(...), only NSDictionary is valid for Select, thus you are allowed to omit the generic type:

- -
let allAges = CoreStore.queryAttributes(
-    From(MyPersonEntity),
-    Select("age")
-)
- -

If you only need a value for a particular attribute, you can just specify the key name (like we did with Select<Int>("age")), but several aggregate functions can also be used as parameter to Select:

- -
    -
  • .Average(...)
  • -
  • .Count(...)
  • -
  • .Maximum(...)
  • -
  • .Minimum(...)
  • -
  • .Sum(...)
  • -
- -
let oldestAge = CoreStore.queryValue(
-    From(MyPersonEntity),
-    Select<Int>(.Maximum("age"))
-)
- -

For queryAttributes(...) which returns an array of dictionaries, you can specify multiple attributes/aggregates to Select:

- -
let personJSON = CoreStore.queryAttributes(
-    From(MyPersonEntity),
-    Select("name", "age")
-)
- -

personJSON will then have the value:

- -
[
-    [
-        "name": "John Smith",
-        "age": 30
-    ],
-    [
-        "name": "Jane Doe",
-        "age": 22
-    ]
-]
- -

You can also include an aggregate as well:

- -
let personJSON = CoreStore.queryAttributes(
-    From(MyPersonEntity),
-    Select("name", .Count("friends"))
-)
- -

which returns:

- -
[
-    [
-        "name": "John Smith",
-        "count(friends)": 42
-    ],
-    [
-        "name": "Jane Doe",
-        "count(friends)": 231
-    ]
-]
- -

The "count(friends)" key name was automatically used by CoreStore, but you can specify your own key alias if you need:

- -
let personJSON = CoreStore.queryAttributes(
-    From(MyPersonEntity),
-    Select("name", .Count("friends", As: "friendsCount"))
-)
- -

which now returns:

- -
[
-    [
-        "name": "John Smith",
-        "friendsCount": 42
-    ],
-    [
-        "name": "Jane Doe",
-        "friendsCount": 231
-    ]
-]
- -

-GroupBy clause

- -

The GroupBy clause lets you group results by a specified attribute/aggregate. This is useful only for queryAttributes(...) since queryValue(...) just returns the first value.

- -
let personJSON = CoreStore.queryAttributes(
-    From(MyPersonEntity),
-    Select("age", .Count("age", As: "count")),
-    GroupBy("age")
-)
- -

this returns dictionaries that shows the count for each "age":

- -
[
-    [
-        "age": 42,
-        "count": 1
-    ],
-    [
-        "age": 22,
-        "count": 1
-    ]
-]
- -

-Logging and error handling

- -

One unfortunate thing when using some third-party libraries is that they usually pollute the console with their own logging mechanisms. CoreStore provides its own default logging class, but you can plug-in your own favorite logger by implementing the CoreStoreLogger protocol.

- -
final class MyLogger: CoreStoreLogger {
-    func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
-        // pass to your logger
-    }
-
-    func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
-        // pass to your logger
-    }
-
-    func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
-        // pass to your logger
-    }
-}
- -

Then pass an instance of this class to CoreStore:

- -
CoreStore.logger = MyLogger()
- -

Doing so channels all logging calls to your logger.

- -

Note that to keep the call stack information intact, all calls to these methods are NOT thread-managed. Therefore you have to make sure that your logger is thread-safe or you may otherwise have to dispatch your logging implementation to a serial queue.

- -

-Observing changes and notifications (unavailable on OSX)

- -

CoreStore provides type-safe wrappers for observing managed objects:

- -
    -
  • -ObjectMonitor: use to monitor changes to a single NSManagedObject instance (instead of Key-Value Observing)
  • -
  • -ListMonitor: use to monitor changes to a list of NSManagedObject instances (instead of NSFetchedResultsController)
  • -
- -

-Observe a single object

- -

To observe an object, implement the ObjectObserver protocol and specify the EntityType:

- -
class MyViewController: UIViewController, ObjectObserver {
-    func objectMonitor(monitor: ObjectMonitor<MyPersonEntity>, willUpdateObject object: MyPersonEntity) {
-        // ...
-    }
-
-    func objectMonitor(monitor: ObjectMonitor<MyPersonEntity>, didUpdateObject object: MyPersonEntity, changedPersistentKeys: Set<KeyPath>) {
-        // ...
-    }
-
-    func objectMonitor(monitor: ObjectMonitor<MyPersonEntity>, didDeleteObject object: MyPersonEntity) {
-        // ...
-    }
-}
- -

We then need to keep a ObjectMonitor instance and register our ObjectObserver as an observer:

- -
let person: MyPersonEntity = // ...
-self.monitor = CoreStore.monitorObject(person)
-self.monitor.addObserver(self)
- -

The controller will then notify our observer whenever the object's attributes change. You can add multiple ObjectObservers to a single ObjectMonitor without any problem. This means you can just share around the ObjectMonitor instance to different screens without problem.

- -

You can get ObjectMonitor's object through its object property. If the object is deleted, the object property will become nil to prevent further access.

- -

While ObjectMonitor exposes removeObserver(...) as well, it only stores weak references of the observers and will safely unregister deallocated observers.

- -

-Observe a list of objects

- -

To observe a list of objects, implement one of the ListObserver protocols and specify the EntityType:

- -
class MyViewController: UIViewController, ListObserver {
-    func listMonitorWillChange(monitor: ListMonitor<MyPersonEntity>) {
-        // ...
-    }
-
-    func listMonitorDidChange(monitor: ListMonitor<MyPersonEntity>) {
-        // ...
-    }
-}
- -

Including ListObserver, there are 3 observer protocols you can implement depending on how detailed you need to handle a change notification:

- -
    -
  • -ListObserver: lets you handle these callback methods:
  • -
- -
    func listMonitorWillChange(monitor: ListMonitor<MyPersonEntity>)
-
-    func listMonitorDidChange(monitor: ListMonitor<MyPersonEntity>)
- -
    -
  • -ListObjectObserver: in addition to ListObserver methods, also lets you handle object inserts, updates, and deletes:
  • -
- -
    func listMonitor(monitor: ListMonitor<MyPersonEntity>, didInsertObject object: MyPersonEntity, toIndexPath indexPath: NSIndexPath)
-
-    func listMonitor(monitor: ListMonitor<MyPersonEntity>, didDeleteObject object: MyPersonEntity, fromIndexPath indexPath: NSIndexPath)
-
-    func listMonitor(monitor: ListMonitor<MyPersonEntity>, didUpdateObject object: MyPersonEntity, atIndexPath indexPath: NSIndexPath)
-
-    func listMonitor(monitor: ListMonitor<MyPersonEntity>, didMoveObject object: MyPersonEntity, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)
- -
    -
  • -ListSectionObserver: in addition to ListObjectObserver methods, also lets you handle section inserts and deletes:
  • -
- -
    func listMonitor(monitor: ListMonitor<MyPersonEntity>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
-
-    func listMonitor(monitor: ListMonitor<MyPersonEntity>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)
- -

We then need to create a ListMonitor instance and register our ListObserver as an observer:

- -
self.monitor = CoreStore.monitorList(
-    From(MyPersonEntity),
-    Where("age > 30"),
-    OrderBy(.Ascending("name")),
-    Tweak { (fetchRequest) -> Void in
-        fetchRequest.fetchBatchSize = 20
-    }
-)
-self.monitor.addObserver(self)
- -

Similar to ObjectMonitor, a ListMonitor can also have multiple ListObservers registered to a single ListMonitor.

- -

If you have noticed, the monitorList(...) method accepts Where, OrderBy, and Tweak clauses exactly like a fetch. As the list maintained by ListMonitor needs to have a deterministic order, at least the From and OrderBy clauses are required.

- -

A ListMonitor created from monitorList(...) will maintain a single-section list. You can therefore access its contents with just an index:

- -
let firstPerson = self.monitor[0]
- -

If the list needs to be grouped into sections, create the ListMonitor instance with the monitorSectionedList(...) method and a SectionBy clause:

- -
self.monitor = CoreStore.monitorSectionedList(
-    From(MyPersonEntity),
-    SectionBy("age"),
-    Where("gender", isEqualTo: "M"),
-    OrderBy(.Ascending("age"), .Ascending("name")),
-    Tweak { (fetchRequest) -> Void in
-        fetchRequest.fetchBatchSize = 20
-    }
-)
- -

A list controller created this way will group the objects by the attribute key indicated by the SectionBy clause. One more thing to remember is that the OrderBy clause should sort the list in such a way that the SectionBy attribute would be sorted together (a requirement shared by NSFetchedResultsController.)

- -

The SectionBy clause can also be passed a closure to transform the section name into a displayable string:

- -
self.monitor = CoreStore.monitorSectionedList(
-    From(MyPersonEntity),
-    SectionBy("age") { (sectionName) -> String? in
-        "\(sectionName) years old"
-    },
-    OrderBy(.Ascending("age"), .Ascending("name"))
-)
- -

This is useful when implementing a UITableViewDelegate's section header:

- -
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
-    let sectionInfo = self.monitor.sectionInfoAtIndex(section)
-    // sectionInfo is an NSFetchedResultsSectionInfo instance
-    return sectionInfo.name
-}
- -

To access the objects of a sectioned list, use an NSIndexPath or a tuple:

- -
let indexPath = NSIndexPath(forRow: 2, inSection: 1)
-let person1 = self.monitor[indexPath]
-let person2 = self.monitor[1, 2]
-// person1 and person2 are the same object
- -

-Roadmap

- -
    -
  • Support iCloud stores
  • -
  • CoreSpotlight auto-indexing (experimental)
  • -
- -

-Installation

- -
    -
  • Requires: - -
      -
    • iOS 8 SDK and above
    • -
    • Swift 2.1 (Xcode 7.2)
    • -
    -
  • -
  • Dependencies: - - -
  • -
- -

-Install with CocoaPods

- -
pod 'CoreStore'
-
- -

This installs CoreStore as a framework. Declare import CoreStore in your swift file to use the library.

- -

-Install with Carthage

- -

In your Cartfile, add

- -
github "JohnEstropia/CoreStore" >= 1.4.4
-github "JohnEstropia/GCDKit" >= 1.1.7
-
- -

and run

- -
carthage update
-
- -

-Install as Git Submodule

- -
git submodule add https://github.com/JohnEstropia/CoreStore.git <destination directory>
-
- -

Drag and drop CoreStore.xcodeproj to your project.

- -

-To install as a framework:

- -

Drag and drop CoreStore.xcodeproj to your project.

- -

-To include directly in your app module:

- -

Add all .swift files to your project.

- -

-Changesets

- -

-Upgrading from v0.2.0 to 1.0.0

- -
    -
  • Renamed some classes/protocols to shorter, more relevant, easier to remember names:
  • -
  • -ManagedObjectController to ObjectMonitor -
  • -
  • -ManagedObjectObserver to ObjectObserver -
  • -
  • -ManagedObjectListController to ListMonitor -
  • -
  • -ManagedObjectListChangeObserver to ListObserver -
  • -
  • -ManagedObjectListObjectObserver to ListObjectObserver -
  • -
  • -ManagedObjectListSectionObserver to ListSectionObserver -
  • -
  • -SectionedBy to SectionBy (match tense with OrderBy and GroupBy) -The protocols above had their methods renamed as well, to retain the natural language semantics.
  • -
  • Several methods now throw errors insted of returning a result enum.
  • -
  • New migration utilities! (README still pending) Check out DataStack+Migration.swift and CoreStore+Migration.swift for the new methods, as well as DataStack.swift for its new initializer.
  • -
- -

-Contributions

- -

While CoreStore's design is pretty solid and the unit test and demo app work well, CoreStore is pretty much still in its early stage. With more exposure to production code usage and criticisms from the developer community, CoreStore hopes to mature as well. -Please feel free to report any issues, suggestions, or criticisms! -日本語で連絡していただいても構いません!

- -

-License

- -

CoreStore is released under an MIT license. See the LICENSE file for more information

- - - -
- - - - + diff --git a/params.json b/params.json deleted file mode 100644 index c4917e3..0000000 --- a/params.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"Corestore","tagline":"Unleashing the real power of Core Data with the elegance and safety of Swift","body":"![corestore](https://cloud.githubusercontent.com/assets/3029684/13368133/305f37d0-dd2b-11e5-8ae6-26e48050b8f6.png)\r\n\r\n[![Build Status](https://img.shields.io/travis/JohnEstropia/CoreStore/master.svg)](https://travis-ci.org/JohnEstropia/CoreStore)\r\n[![Version](https://img.shields.io/cocoapods/v/CoreStore.svg?style=flat)](http://cocoadocs.org/docsets/CoreStore)\r\n[![Platform](https://img.shields.io/cocoapods/p/CoreStore.svg?style=flat)](http://cocoadocs.org/docsets/CoreStore)\r\n[![License](https://img.shields.io/cocoapods/l/CoreStore.svg?style=flat)](https://raw.githubusercontent.com/JohnEstropia/CoreStore/master/LICENSE)\r\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\r\n\r\nUnleashing the real power of Core Data with the elegance and safety of Swift\r\n\r\n* Swift 2.2 (Xcode 7.3)\r\n* iOS 8+ / OSX 10.10+ / watchOS 2.0+ / tvOS 9.0+\r\n\r\n\r\n\r\n## What CoreStore does better:\r\n\r\n- **Heavily supports multiple persistent stores per data stack**, just the way *.xcdatamodeld* files are designed to. CoreStore will also manage one data stack by default, but you can create and manage as many as you need.\r\n- **Progressive Migrations!** Just tell the data stack the sequence of model versions and CoreStore will automatically use progressive migrations if needed on stores added to that stack.\r\n- Ability to **plug-in your own logging framework**\r\n- 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 managed object model file, so you are **free to name entities and their class names independently**.\r\n- Provides type-safe, easy to configure **observers to replace `NSFetchedResultsController` and KVO**\r\n- Exposes **API not just for fetching, but also for querying aggregates and property values**\r\n- Makes it hard to fall into common concurrency mistakes. All `NSManagedObjectContext` tasks are encapsulated into **safer, higher-level abstractions** without sacrificing flexibility and customizability.\r\n- Exposes clean and convenient API designed around **Swift’s code elegance and type safety**.\r\n- **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.\r\n- **Efficient importing utilities!**\r\n\r\n**[Vote for the next feature!](http://goo.gl/RIiHMP)**\r\n\r\n\r\n\r\n## Contents\r\n\r\n- [TL;DR (a.k.a. sample codes)](#tldr-aka-sample-codes)\r\n- [Architecture](#architecture)\r\n- CoreStore Tutorials (All of these have demos in the **CoreStoreDemo** app project!)\r\n - [Setting up](#setting-up)\r\n - [Migrations](#migrations)\r\n - [Progressive migrations](#progressive-migrations)\r\n - [Forecasting migrations](#forecasting-migrations)\r\n - [Saving and processing transactions](#saving-and-processing-transactions)\r\n - [Transaction types](#transaction-types)\r\n - [Asynchronous transactions](#asynchronous-transactions)\r\n - [Synchronous transactions](#synchronous-transactions)\r\n - [Unsafe transactions](#unsafe-transactions)\r\n - [Creating objects](#creating-objects)\r\n - [Updating objects](#updating-objects)\r\n - [Deleting objects](#deleting-objects)\r\n - [Passing objects safely](#passing-objects-safely)\r\n - [Importing data](#importing-data)\r\n - [Fetching and querying](#fetching-and-querying)\r\n - [`From` clause](#from-clause)\r\n - [Fetching](#fetching)\r\n - [`Where` clause](#where-clause)\r\n - [`OrderBy` clause](#orderby-clause)\r\n - [`Tweak` clause](#tweak-clause)\r\n - [Querying](#querying)\r\n - [`Select` clause](#selectt-clause)\r\n - [`GroupBy` clause](#groupby-clause)\r\n - [Logging and error handling](#logging-and-error-handling)\r\n - [Observing changes and notifications](#observing-changes-and-notifications) (unavailable on OSX)\r\n - [Observe a single object](#observe-a-single-object)\r\n - [Observe a list of objects](#observe-a-list-of-objects)\r\n- [Roadmap](#roadmap)\r\n- [Installation](#installation)\r\n- [Changesets](#changesets)\r\n - [Upgrading from v0.2.0 to 1.0.0](#upgrading-from-v020-to-100)\r\n\r\n\r\n\r\n## TL;DR (a.k.a. sample codes)\r\n\r\nSetting-up with progressive migration support:\r\n```swift\r\nCoreStore.defaultStack = DataStack(\r\n modelName: \"MyStore\",\r\n migrationChain: [\"MyStore\", \"MyStoreV2\", \"MyStoreV3\"]\r\n)\r\n```\r\n\r\nAdding a store:\r\n```swift\r\ntry CoreStore.addSQLiteStore(\r\n fileName: \"MyStore.sqlite\",\r\n completion: { (result) -> Void in\r\n // ...\r\n }\r\n)\r\n```\r\n\r\nStarting transactions:\r\n```swift\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n let person = transaction.create(Into(MyPersonEntity))\r\n person.name = \"John Smith\"\r\n person.age = 42\r\n\r\n transaction.commit { (result) -> Void in\r\n switch result {\r\n case .Success(let hasChanges): print(\"success!\")\r\n case .Failure(let error): print(error)\r\n }\r\n }\r\n}\r\n```\r\n\r\nFetching objects:\r\n```swift\r\nlet people = CoreStore.fetchAll(From(MyPersonEntity))\r\n```\r\n```swift\r\nlet people = CoreStore.fetchAll(\r\n From(MyPersonEntity),\r\n Where(\"age > 30\"),\r\n OrderBy(.Ascending(\"name\"), .Descending(\"age\")),\r\n Tweak { (fetchRequest) -> Void in\r\n fetchRequest.includesPendingChanges = false\r\n }\r\n)\r\n```\r\n\r\nQuerying values:\r\n```swift\r\nlet maxAge = CoreStore.queryValue(\r\n From(MyPersonEntity),\r\n Select(.Maximum(\"age\"))\r\n)\r\n```\r\n\r\nBut really, there's a reason I wrote this huge README. Read up on the details!\r\n\r\nCheck out the **CoreStoreDemo** app project for sample codes as well!\r\n\r\n\r\n\r\n## Architecture\r\nFor 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.\r\n\r\nIf you are already familiar with the inner workings of CoreData, here is a mapping of `CoreStore` abstractions:\r\n\r\n| *Core Data* | *CoreStore* |\r\n| --- | --- |\r\n| `NSManagedObjectModel` / `NSPersistentStoreCoordinator`
(.xcdatamodeld file) | `DataStack` |\r\n| `NSPersistentStore`
(\"Configuration\"s in the .xcdatamodeld file) | `DataStack` configuration
(multiple sqlite / in-memory stores per stack) |\r\n| `NSManagedObjectContext` | `BaseDataTransaction` subclasses
(`SynchronousDataTransaction`, `AsynchronousDataTransaction`, `UnsafeDataTransaction`) |\r\n\r\nPopular libraries [RestKit](https://github.com/RestKit/RestKit) and [MagicalRecord](https://github.com/magicalpanda/MagicalRecord) set up their `NSManagedObjectContext`s this way:\r\n\r\n\"nested\r\n\r\nNesting context saves from child context to the root context 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* on the child context:\r\n\r\n\"nested\r\n\r\nThis allows for a butter-smooth main thread, while still taking advantage of safe nested contexts.\r\n\r\n\r\n\r\n## Setting up\r\nThe simplest way to initialize CoreStore is to add a default store to the default stack:\r\n```swift\r\ndo {\r\n try CoreStore.addSQLiteStoreAndWait()\r\n}\r\ncatch {\r\n // ...\r\n}\r\n```\r\nThis one-liner does the following:\r\n- Triggers the lazy-initialization of `CoreStore.defaultStack` with a default `DataStack`\r\n- Sets up the stack's `NSPersistentStoreCoordinator`, the root saving `NSManagedObjectContext`, and the read-only main `NSManagedObjectContext`\r\n- Adds an SQLite store in the *\"Application Support/\"* directory (or the *\"Caches/\"* directory on tvOS) with the file name *\"[App bundle name].sqlite\"*\r\n- Creates and returns the `NSPersistentStore` instance on success, or an `NSError` on failure\r\n\r\nFor most cases, this configuration is usable as it is. But for more hardcore settings, refer to this extensive example:\r\n```swift\r\nlet dataStack = DataStack(\r\n modelName: \"MyModel\", // loads from the \"MyModel.xcdatamodeld\" file\r\n migrationChain: [\"MyStore\", \"MyStoreV2\", \"MyStoreV3\"] // model versions for progressive migrations\r\n)\r\n\r\ndo {\r\n // creates an in-memory store with entities from the \"Config1\" configuration in the .xcdatamodeld file\r\n let persistentStore = try dataStack.addInMemoryStoreAndWait(configuration: \"Config1\") // persistentStore is an NSPersistentStore instance\r\n print(\"Successfully created an in-memory store: \\(persistentStore)\"\r\n}\r\ncatch {\r\n print(\"Failed creating an in-memory store with error: \\(error as NSError)\"\r\n}\r\n\r\ndo {\r\n try dataStack.addSQLiteStore(\r\n fileURL: sqliteFileURL, // set the target file URL for the sqlite file\r\n configuration: \"Config2\", // use entities from the \"Config2\" configuration in the .xcdatamodeld file\r\n resetStoreOnModelMismatch: true,\r\n completion: { (result) -> Void in\r\n switch result {\r\n case .Success(let persistentStore):\r\n print(\"Successfully added sqlite store: \\(persistentStore)\"\r\n case .Failure(let error):\r\n print(\"Failed adding sqlite store with error: \\(error)\"\r\n }\r\n }\r\n )\r\n}\r\ncatch {\r\n print(\"Failed adding sqlite store with error: \\(error as NSError)\"\r\n}\r\n\r\nCoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier access later on\r\n```\r\n\r\n(If you have never heard of \"Configurations\", you'll find them in your *.xcdatamodeld* file)\r\n\"xcode\r\n\r\nIn our sample code above, note that you don't need to do the `CoreStore.defaultStack = dataStack` line. You can just as well hold a reference to the `DataStack` like below and call all its instance methods directly:\r\n```swift\r\nclass MyViewController: UIViewController {\r\n let dataStack = DataStack(modelName: \"MyModel\")\r\n override func viewDidLoad() {\r\n super.viewDidLoad()\r\n do {\r\n try self.dataStack.addSQLiteStoreAndWait()\r\n }\r\n catch { // ...\r\n }\r\n }\r\n func methodToBeCalledLaterOn() {\r\n let objects = self.dataStack.fetchAll(From(MyEntity))\r\n print(objects)\r\n }\r\n}\r\n```\r\nThe difference is when you set the stack as the `CoreStore.defaultStack`, you can call the stack's methods directly from `CoreStore` itself:\r\n```swift\r\nclass MyViewController: UIViewController {\r\n override func viewDidLoad() {\r\n super.viewDidLoad()\r\n do {\r\n try CoreStore.addSQLiteStoreAndWait()\r\n }\r\n catch { // ...\r\n }\r\n }\r\n func methodToBeCalledLaterOn() {\r\n let objects = CoreStore.fetchAll(From(MyEntity))\r\n print(objects)\r\n }\r\n}\r\n```\r\n\r\n\r\n## Migrations\r\nSo far we have only seen `addSQLiteStoreAndWait(...)` used to initialize our persistent store. As the method name's \"AndWait\" suffix suggests, this method blocks so it should not do long tasks such as store migrations (in fact CoreStore won't even attempt to, and any model mismatch will be reported as an error). If migrations are expected, the asynchronous variant `addSQLiteStore(... completion:)` method should be used instead:\r\n```swift\r\ndo {\r\n let progress: NSProgress? = try dataStack.addSQLiteStore(\r\n fileName: \"MyStore.sqlite\",\r\n configuration: \"Config2\",\r\n completion: { (result) -> Void in\r\n switch result {\r\n case .Success(let persistentStore):\r\n print(\"Successfully added sqlite store: \\(persistentStore)\")\r\n case .Failure(let error):\r\n print(\"Failed adding sqlite store with error: \\(error)\")\r\n }\r\n }\r\n )\r\n}\r\ncatch {\r\n print(\"Failed adding sqlite store with error: \\(error as NSError)\"\r\n}\r\n```\r\nThe `completion` block reports a `PersistentStoreResult` that indicates success or failure.\r\n\r\n`addSQLiteStore(...)` throws an error if the store at the specified URL conflicts with an existing store in the `DataStack`, or if an existing sqlite file could not be read. If an error is thrown, the `completion` block will not be executed.\r\n\r\nNotice that this method also returns an optional `NSProgress`. If `nil`, no migrations are needed, thus progress reporting is unnecessary as well. If not `nil`, you can use this to track migration progress by using standard KVO on the \"fractionCompleted\" key, or by using a closure-based utility exposed in *NSProgress+Convenience.swift*:\r\n```swift\r\nprogress?.setProgressHandler { [weak self] (progress) -> Void in\r\n self?.progressView?.setProgress(Float(progress.fractionCompleted), animated: true)\r\n self?.percentLabel?.text = progress.localizedDescription // \"50% completed\"\r\n self?.stepLabel?.text = progress.localizedAdditionalDescription // \"0 of 2\"\r\n}\r\n```\r\nThis closure is executed on the main thread so UIKit calls can be done safely.\r\n\r\n\r\n### Progressive migrations\r\nBy default, CoreStore uses Core Data's default automatic migration mechanism. In other words, CoreStore will try to migrate the existing persistent store to the *.xcdatamodeld* file's current model version. If no mapping model is found from the store's version to the data model's version, CoreStore gives up and reports an error.\r\n\r\nThe `DataStack` lets you specify hints on how to break a migration into several sub-migrations using a `MigrationChain`. This is typically passed to the `DataStack` initializer and will be applied to all stores added to the `DataStack` with `addSQLiteStore(...)` and its variants:\r\n```swift\r\nlet dataStack = DataStack(migrationChain: \r\n [\"MyAppModel\", \"MyAppModelV2\", \"MyAppModelV3\", \"MyAppModelV4\"])\r\n```\r\nThe most common usage is to pass in the *.xcdatamodeld* version names in increasing order as above.\r\n\r\nFor more complex migration paths, you can also pass in a version tree that maps the key-values to the source-destination versions:\r\n```swift\r\nlet dataStack = DataStack(migrationChain: [\r\n \"MyAppModel\": \"MyAppModelV3\",\r\n \"MyAppModelV2\": \"MyAppModelV4\",\r\n \"MyAppModelV3\": \"MyAppModelV4\"\r\n])\r\n```\r\nThis allows for different migration paths depending on the starting version. The example above resolves to the following paths:\r\n- MyAppModel-MyAppModelV3-MyAppModelV4\r\n- MyAppModelV2-MyAppModelV4\r\n- MyAppModelV3-MyAppModelV4\r\n\r\nInitializing with empty values (either `nil`, `[]`, or `[:]`) instructs the `DataStack` to disable progressive migrations and revert to the default migration behavior (i.e. use the .xcdatamodel's current version as the final version):\r\n```swift\r\nlet dataStack = DataStack(migrationChain: nil)\r\n```\r\n\r\nThe `MigrationChain` is validated when passed to the `DataStack` and unless it is empty, will raise an assertion if any of the following conditions are met:\r\n- a version appears twice in an array\r\n- a version appears twice as a key in a dictionary literal\r\n- a loop is found in any of the paths\r\n\r\nOne important thing to remember is that **if a `MigrationChain` is specified, the *.xcdatamodeld*'s \"Current Version\" will be bypassed** and the `MigrationChain`'s leafmost version will be the `DataStack`'s base model version.\r\n\r\n\r\n### Forecasting migrations\r\n\r\nSometimes migrations are huge and you may want prior information so your app could display a loading screen, or to display a confirmation dialog to the user. For this, CoreStore provides a `requiredMigrationsForSQLiteStore(...)` method you can use to inspect a persistent store before you actually call `addSQLiteStore(...)`:\r\n```swift\r\ndo {\r\n let migrationTypes: [MigrationType] = CoreStore.requiredMigrationsForSQLiteStore(fileName: \"MyStore.sqlite\")\r\n if migrationTypes.count > 1\r\n || (migrationTypes.filter { $0.isHeavyweightMigration }.count) > 0 {\r\n // ... Show special waiting screen\r\n }\r\n else if migrationTypes.count > 0 {\r\n // ... Show simple activity indicator\r\n }\r\n else {\r\n // ... Do nothing\r\n }\r\n\r\n CoreStore.addSQLiteStore(/* ... */)\r\n}\r\ncatch {\r\n // ...\r\n}\r\n```\r\n`requiredMigrationsForSQLiteStore(...)` returns an array of `MigrationType`s, where each item in the array may be either of the following values:\r\n```swift\r\ncase Lightweight(sourceVersion: String, destinationVersion: String)\r\ncase Heavyweight(sourceVersion: String, destinationVersion: String)\r\n```\r\nEach `MigrationType` indicates the migration type for each step in the `MigrationChain`. Use these information as fit for your app.\r\n\r\n\r\n\r\n## Saving and processing transactions\r\nTo ensure deterministic state for objects in the read-only `NSManagedObjectContext`, CoreStore does not expose API's for updating and saving directly from the main context (or any other context for that matter.) Instead, you spawn *transactions* from `DataStack` instances:\r\n```swift\r\nlet dataStack = self.dataStack\r\ndataStack.beginAsynchronous { (transaction) -> Void in\r\n // make changes\r\n transaction.commit()\r\n}\r\n```\r\nor for the default stack, directly from `CoreStore`:\r\n```swift\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n // make changes\r\n transaction.commit()\r\n}\r\n```\r\nThe `commit()` method saves the changes to the persistent store. If `commit()` is not called when the transaction block completes, all changes within the transaction is discarded.\r\n\r\nThe examples above use `beginAsynchronous(...)`, but there are actually 3 types of transactions at your disposal: *asynchronous*, *synchronous*, and *unsafe*.\r\n\r\n### Transaction types\r\n\r\n#### Asynchronous transactions\r\nare spawned from `beginAsynchronous(...)`. This method returns immediately and executes its closure from a background serial queue:\r\n```swift\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n // make changes\r\n transaction.commit()\r\n}\r\n```\r\nTransactions created from `beginAsynchronous(...)` are instances of `AsynchronousDataTransaction`.\r\n\r\n#### Synchronous transactions\r\nare created from `beginSynchronous(...)`. While the syntax is similar to its asynchronous counterpart, `beginSynchronous(...)` waits for its transaction block to complete before returning:\r\n```swift\r\nCoreStore.beginSynchronous { (transaction) -> Void in\r\n // make changes\r\n transaction.commit()\r\n} \r\n```\r\n`transaction` above is a `SynchronousDataTransaction` instance.\r\n\r\nSince `beginSynchronous(...)` technically blocks two queues (the caller's queue and the transaction's background queue), it is considered less safe as it's more prone to deadlock. Take special care that the closure does not block on any other external queues.\r\n\r\n#### Unsafe transactions\r\nare special in that they do not enclose updates within a closure:\r\n```swift\r\nlet transaction = CoreStore.beginUnsafe()\r\n// make changes\r\ndownloadJSONWithCompletion({ (json) -> Void in\r\n\r\n // make other changes\r\n transaction.commit()\r\n})\r\ndownloadAnotherJSONWithCompletion({ (json) -> Void in\r\n\r\n // make some other changes\r\n transaction.commit()\r\n})\r\n```\r\nThis allows for non-contiguous updates. Do note that this flexibility comes with a price: you are now responsible for managing concurrency for the transaction. As uncle Ben said, \"with great power comes great race conditions.\"\r\n\r\nAs the above example also shows, only unsafe transactions are allowed to call `commit()` multiple times; doing so with synchronous and asynchronous transactions will trigger an assert. \r\n\r\n\r\nYou've seen how to create transactions, but we have yet to see how to make *creates*, *updates*, and *deletes*. The 3 types of transactions above are all subclasses of `BaseDataTransaction`, which implements the methods shown below.\r\n\r\n### Creating objects\r\n\r\nThe `create(...)` method accepts an `Into` clause which specifies the entity for the object you want to create:\r\n```swift\r\nlet person = transaction.create(Into(MyPersonEntity))\r\n```\r\nWhile the syntax is straightforward, CoreStore does not just naively insert a new object. This single line does the following:\r\n- Checks that the entity type exists in any of the transaction's parent persistent store\r\n- If the entity belongs to only one persistent store, a new object is inserted into that store and returned from `create(...)`\r\n- If the entity does not belong to any store, an assert will be triggered. **This is a programmer error and should never occur in production code.**\r\n- If the entity belongs to multiple stores, an assert will be triggered. **This is also a programmer error and should never occur in production code.** Normally, with Core Data you can insert an object in this state but saving the `NSManagedObjectContext` will always fail. CoreStore checks this for you at creation time when it makes sense (not during save).\r\n\r\nIf the entity exists in multiple configurations, you need to provide the configuration name for the destination persistent store:\r\n\r\n let person = transaction.create(Into(\"Config1\"))\r\n\r\nor if the persistent store is the auto-generated \"Default\" configuration, specify `nil`:\r\n\r\n let person = transaction.create(Into(nil))\r\n\r\nNote that if you do explicitly specify the configuration name, CoreStore will only try to insert the created object to that particular store and will fail if that store is not found; it will not fall back to any other configuration that the entity belongs to. \r\n\r\n### Updating objects\r\n\r\nAfter creating an object from the transaction, you can simply update its properties as normal:\r\n```swift\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n let person = transaction.create(Into(MyPersonEntity))\r\n person.name = \"John Smith\"\r\n person.age = 30\r\n transaction.commit()\r\n}\r\n```\r\nTo update an existing object, fetch the object's instance from the transaction:\r\n```swift\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n let person = transaction.fetchOne(\r\n From(MyPersonEntity),\r\n Where(\"name\", isEqualTo: \"Jane Smith\")\r\n )\r\n person.age = person.age + 1\r\n transaction.commit()\r\n}\r\n```\r\n*(For more about fetching, see [Fetching and querying](#fetching-and-querying))*\r\n\r\n**Do not update an instance that was not created/fetched from the transaction.** If you have a reference to the object already, use the transaction's `edit(...)` method to get an editable proxy instance for that object:\r\n```swift\r\nlet jane: MyPersonEntity = // ...\r\n\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n // WRONG: jane.age = jane.age + 1\r\n // RIGHT:\r\n let jane = transaction.edit(jane)! // using the same variable name protects us from misusing the non-transaction instance\r\n jane.age = jane.age + 1\r\n transaction.commit()\r\n}\r\n```\r\nThis is also true when updating an object's relationships. Make sure that the object assigned to the relationship is also created/fetched from the transaction:\r\n```swift\r\nlet jane: MyPersonEntity = // ...\r\nlet john: MyPersonEntity = // ...\r\n\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n // WRONG: jane.friends = [john]\r\n // RIGHT:\r\n let jane = transaction.edit(jane)!\r\n let john = transaction.edit(john)!\r\n jane.friends = NSSet(array: [john])\r\n transaction.commit()\r\n}\r\n```\r\n\r\n### Deleting objects\r\n\r\nDeleting an object is simpler because you can tell a transaction to delete an object directly without fetching an editable proxy (CoreStore does that for you):\r\n```swift\r\nlet john: MyPersonEntity = // ...\r\n\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n transaction.delete(john)\r\n transaction.commit()\r\n}\r\n```\r\nor several objects at once:\r\n```swift\r\nlet john: MyPersonEntity = // ...\r\nlet jane: MyPersonEntity = // ...\r\n\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n transaction.delete(john, jane)\r\n // transaction.delete([john, jane]) is also allowed\r\n transaction.commit()\r\n}\r\n```\r\nIf you do not have references yet to the objects to be deleted, transactions have a `deleteAll(...)` method you can pass a query to:\r\n```swift\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n transaction.deleteAll(\r\n From(MyPersonEntity)\r\n Where(\"age > 30\")\r\n )\r\n transaction.commit()\r\n}\r\n```\r\n\r\n### Passing objects safely\r\n\r\nAlways remember that the `DataStack` and individual transactions manage different `NSManagedObjectContext`s so you cannot just use objects between them. That's why transactions have an `edit(...)` method:\r\n```swift\r\nlet jane: MyPersonEntity = // ...\r\n\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n let jane = transaction.edit(jane)!\r\n jane.age = jane.age + 1\r\n transaction.commit()\r\n}\r\n```\r\nBut `CoreStore`, `DataStack` and `BaseDataTransaction` have a very flexible `fetchExisting(...)` method that you can pass instances back and forth with:\r\n```swift\r\nlet jane: MyPersonEntity = // ...\r\n\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n let jane = transaction.fetchExisting(jane)! // instance for transaction\r\n jane.age = jane.age + 1\r\n transaction.commit {\r\n let jane = CoreStore.fetchExisting(jane)! // instance for DataStack\r\n print(jane.age)\r\n }\r\n}\r\n```\r\n`fetchExisting(...)` also works with multiple `NSManagedObject`s or with `NSManagedObjectID`s:\r\n```swift\r\nvar peopleIDs: [NSManagedObjectID] = // ...\r\n\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n let jane = transaction.fetchOne(\r\n From(MyPersonEntity),\r\n Where(\"name\", isEqualTo: \"Jane Smith\")\r\n )\r\n jane.friends = NSSet(array: transaction.fetchExisting(peopleIDs)!)\r\n // ...\r\n}\r\n```\r\n\r\n\r\n## Importing data\r\nSome times, if not most of the time, the data that we save to Core Data comes from external sources such as web servers or external files. Say you have a JSON dictionary, you may be extracting values as such:\r\n```swift\r\nlet json: [String: AnyObject] = // ...\r\nperson.name = json[\"name\"] as? NSString\r\nperson.age = json[\"age\"] as? NSNumber\r\n// ...\r\n```\r\nIf you have many attributes, you don't want to keep repeating this mapping everytime you want to import data. CoreStore lets you write the data mapping code just once, and all you have to do is call `importObject(...)` or `importUniqueObject(...)` through `BaseDataTransaction` subclasses:\r\n```swift\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n let json: [String: AnyObject] = // ...\r\n try! transaction.importObject(\r\n Into(MyPersonEntity),\r\n source: json\r\n )\r\n transaction.commit()\r\n}\r\n```\r\nTo support data import for an entity, implement either `ImportableObject` or `ImportableUniqueObject` on the `NSManagedObject` subclass:\r\n- `ImportableObject`: Use this protocol if the object have no inherent uniqueness and new objects should always be added when calling `importObject(...)`.\r\n- `ImportableUniqueObject`: Use this protocol to specify a unique ID for an object that will be used to distinguish whether a new object should be created or if an existing object should be updated when calling `importUniqueObject(...)`.\r\n\r\nBoth protocols require implementers to specify an `ImportSource` which can be set to any type that the object can extract data from:\r\n```swift\r\ntypealias ImportSource = NSDictionary\r\n```\r\n```swift\r\ntypealias ImportSource = [String: AnyObject]\r\n```\r\n```swift\r\ntypealias ImportSource = NSData\r\n```\r\nYou can even use external types from popular 3rd-party JSON libraries ([SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON)'s `JSON` type is a personal favorite), or just simple tuples or primitives.\r\n\r\n#### `ImportableObject`\r\n`ImportableObject` is a very simple protocol:\r\n```swift\r\npublic protocol ImportableObject: class {\r\n typealias ImportSource\r\n static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool\r\n func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws\r\n}\r\n```\r\nFirst, set `ImportSource` to the expected type of the data source:\r\n```swift\r\ntypealias ImportSource = [String: AnyObject]\r\n```\r\nThis lets us call `importObject(_:source:)` with any `[String: AnyObject]` type as the argument to `source`:\r\n```swift\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n let json: [String: AnyObject] = // ...\r\n try! transaction.importObject(\r\n Into(MyPersonEntity),\r\n source: json\r\n )\r\n // ...\r\n}\r\n```\r\nThe actual extraction and assignment of values should be implemented in the `didInsertFromImportSource(...)` method of the `ImportableObject` protocol:\r\n```swift\r\nfunc didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws {\r\n self.name = source[\"name\"] as? NSString\r\n self.age = source[\"age\"] as? NSNumber\r\n // ...\r\n}\r\n```\r\nTransactions also let you import multiple objects at once using the `importObjects(_:sourceArray:)` method:\r\n```swift\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n let jsonArray: [[String: AnyObject]] = // ...\r\n try! transaction.importObjects(\r\n Into(MyPersonEntity),\r\n sourceArray: jsonArray\r\n )\r\n // ...\r\n}\r\n```\r\nDoing so tells the transaction to iterate through the array of import sources and calls `shouldInsertFromImportSource(...)` on the `ImportableObject` to determine which instances should be created. You can do validations and return `false` from `shouldInsertFromImportSource(...)` if you want to skip importing from a source and continue on with the other sources in the array.\r\n\r\nIf on the other hand, your validation in one of the sources failed in such a manner that all other sources should also be cancelled, you can `throw` from within `didInsertFromImportSource(...)`:\r\n```swift\r\nfunc didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws {\r\n self.name = source[\"name\"] as? NSString\r\n self.age = source[\"age\"] as? NSNumber\r\n // ...\r\n if self.name == nil {\r\n throw Errors.InvalidNameError\r\n }\r\n}\r\n```\r\nDoing so can let you abandon an invalid transaction immediately:\r\n```swift\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n let jsonArray: [[String: AnyObject]] = // ...\r\n do {\r\n try transaction.importObjects(\r\n Into(MyPersonEntity),\r\n sourceArray: jsonArray\r\n )\r\n }\r\n catch {\r\n return // Woops, don't save\r\n }\r\n transaction.commit {\r\n // ...\r\n }\r\n}\r\n```\r\n\r\n#### `ImportableUniqueObject`\r\nTypically, we don't just keep creating objects every time we import data. Usually we also need to update already existing objects. Implementing the `ImportableUniqueObject` protocol lets you specify a \"unique ID\" that transactions can use to search existing objects before creating new ones:\r\n```swift\r\npublic protocol ImportableUniqueObject: ImportableObject {\r\n typealias ImportSource\r\n typealias UniqueIDType: NSObject\r\n\r\n static var uniqueIDKeyPath: String { get }\r\n var uniqueIDValue: UniqueIDType { get set }\r\n\r\n static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool\r\n static func shouldUpdateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool\r\n static func uniqueIDFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws -> UniqueIDType?\r\n func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws\r\n func updateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws\r\n}\r\n```\r\nNotice that it has the same insert methods as `ImportableObject`, with additional methods for updates and for specifying the unique ID:\r\n```swift\r\nclass var uniqueIDKeyPath: String {\r\n return \"personID\" \r\n}\r\nvar uniqueIDValue: NSNumber { \r\n get { return self.personID }\r\n set { self.personID = newValue }\r\n}\r\nclass func uniqueIDFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws -> NSNumber? {\r\n return source[\"id\"] as? NSNumber\r\n}\r\n```\r\nFor `ImportableUniqueObject`, the extraction and assignment of values should be implemented from the `updateFromImportSource(...)` method. The `didInsertFromImportSource(...)` by default calls `updateFromImportSource(...)`, but you can separate the implementation for inserts and updates if needed.\r\n\r\nYou can then create/update an object by calling a transaction's `importUniqueObject(...)` method:\r\n```swift\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n let json: [String: AnyObject] = // ...\r\n try! transaction.importUniqueObject(\r\n Into(MyPersonEntity),\r\n source: json\r\n )\r\n // ...\r\n}\r\n```\r\nor multiple objects at once with the `importUniqueObjects(...)` method:\r\n\r\n```swift\r\nCoreStore.beginAsynchronous { (transaction) -> Void in\r\n let jsonArray: [[String: AnyObject]] = // ...\r\n try! transaction.importObjects(\r\n Into(MyPersonEntity),\r\n sourceArray: jsonArray\r\n )\r\n // ...\r\n}\r\n```\r\nAs with `ImportableObject`, you can control wether to skip importing an object by implementing \r\n`shouldInsertFromImportSource(...)` and `shouldUpdateFromImportSource(...)`, or to cancel all objects by `throw`ing an error from the `uniqueIDFromImportSource(...)`, `didInsertFromImportSource(...)` or `updateFromImportSource(...)` methods.\r\n\r\n\r\n## Fetching and Querying\r\nBefore we dive in, be aware that CoreStore distinguishes between *fetching* and *querying*:\r\n- A *fetch* executes searches from a specific *transaction* or *data stack*. This means fetches can include pending objects (i.e. before a transaction calls on `commit()`.) Use fetches when:\r\n - results need to be `NSManagedObject` instances\r\n - unsaved objects should be included in the search (though fetches can be configured to exclude unsaved ones)\r\n- A *query* pulls data straight from the persistent store. This means faster searches when computing aggregates such as *count*, *min*, *max*, etc. Use queries when:\r\n - you need to compute aggregate functions (see below for a list of supported functions)\r\n - results can be raw values like `NSString`s, `NSNumber`s, `Int`s, `NSDate`s, an `NSDictionary` of key-values, etc.\r\n - only values for specified attribute keys need to be included in the results\r\n - unsaved objects should be ignored\r\n\r\n#### `From` clause\r\nThe search conditions for fetches and queries are specified using *clauses*. All fetches and queries require a `From` clause that indicates the target entity type:\r\n```swift\r\nlet people = CoreStore.fetchAll(From(MyPersonEntity))\r\n// CoreStore.fetchAll(From()) works as well\r\n```\r\n`people` in the example above will be of type `[MyPersonEntity]`. The `From(MyPersonEntity)` clause indicates a fetch to all persistent stores that `MyPersonEntity` belong to.\r\n\r\nIf the entity exists in multiple configurations and you need to only search from a particular configuration, indicate in the `From` clause the configuration name for the destination persistent store:\r\n```swift\r\nlet people = CoreStore.fetchAll(From(\"Config1\")) // ignore objects in persistent stores other than the \"Config1\" configuration\r\n```\r\nor if the persistent store is the auto-generated \"Default\" configuration, specify `nil`:\r\n```swift\r\nlet person = CoreStore.fetchAll(From(nil))\r\n```\r\nNow we know how to use a `From` clause, let's move on to fetching and querying.\r\n\r\n### Fetching\r\n\r\nThere are currently 5 fetch methods you can call from `CoreStore`, from a `DataStack` instance, or from a `BaseDataTransaction` instance. All of the methods below accept the same parameters: a required `From` clause, and an optional series of `Where`, `OrderBy`, and/or `Tweak` clauses.\r\n\r\n- `fetchAll(...)` - returns an array of all objects that match the criteria.\r\n- `fetchOne(...)` - returns the first object that match the criteria.\r\n- `fetchCount(...)` - returns the number of objects that match the criteria.\r\n- `fetchObjectIDs(...)` - returns an array of `NSManagedObjectID`s for all objects that match the criteria.\r\n- `fetchObjectID(...)` - returns the `NSManagedObjectID`s for the first objects that match the criteria.\r\n\r\nEach method's purpose is straightforward, but we need to understand how to set the clauses for the fetch.\r\n\r\n#### `Where` clause\r\n\r\nThe `Where` clause is CoreStore's `NSPredicate` wrapper. It specifies the search filter to use when fetching (or querying). It implements all initializers that `NSPredicate` does (except for `-predicateWithBlock:`, which Core Data does not support):\r\n```swift\r\nvar people = CoreStore.fetchAll(\r\n From(MyPersonEntity),\r\n Where(\"%K > %d\", \"age\", 30) // string format initializer\r\n)\r\npeople = CoreStore.fetchAll(\r\n From(MyPersonEntity),\r\n Where(true) // boolean initializer\r\n)\r\n```\r\nIf you do have an existing `NSPredicate` instance already, you can pass that to `Where` as well:\r\n```swift\r\nlet predicate = NSPredicate(...)\r\nvar people = CoreStore.fetchAll(\r\n From(MyPersonEntity),\r\n Where(predicate) // predicate initializer\r\n)\r\n```\r\n`Where` clauses also implement the `&&`, `||`, and `!` logic operators, so you can provide logical conditions without writing too much `AND`, `OR`, and `NOT` strings:\r\n```swift\r\nvar people = CoreStore.fetchAll(\r\n From(MyPersonEntity),\r\n Where(\"age > %d\", 30) && Where(\"gender == %@\", \"M\")\r\n)\r\n```\r\nIf you do not provide a `Where` clause, all objects that belong to the specified `From` will be returned.\r\n\r\n#### `OrderBy` clause\r\n\r\nThe `OrderBy` clause is CoreStore's `NSSortDescriptor` wrapper. Use it to specify attribute keys in which to sort the fetch (or query) results with.\r\n```swift\r\nvar mostValuablePeople = CoreStore.fetchAll(\r\n From(MyPersonEntity),\r\n OrderBy(.Descending(\"rating\"), .Ascending(\"surname\"))\r\n)\r\n```\r\nAs seen above, `OrderBy` accepts a list of `SortKey` enumeration values, which can be either `.Ascending` or `.Descending`.\r\n\r\nYou can use the `+` and `+=` operator to append `OrderBy`s together. This is useful when sorting conditionally:\r\n```swift\r\nvar orderBy = OrderBy(.Descending(\"rating\"))\r\nif sortFromYoungest {\r\n orderBy += OrderBy(.Ascending(\"age\"))\r\n}\r\nvar mostValuablePeople = CoreStore.fetchAll(\r\n From(MyPersonEntity),\r\n orderBy\r\n)\r\n```\r\n\r\n#### `Tweak` clause\r\n\r\nThe `Tweak` clause lets you, uh, *tweak* the fetch (or query). `Tweak` exposes the `NSFetchRequest` in a closure where you can make changes to its properties:\r\n```swift\r\nvar people = CoreStore.fetchAll(\r\n From(MyPersonEntity),\r\n Where(\"age > %d\", 30),\r\n OrderBy(.Ascending(\"surname\")),\r\n Tweak { (fetchRequest) -> Void in\r\n fetchRequest.includesPendingChanges = false\r\n fetchRequest.returnsObjectsAsFaults = false\r\n fetchRequest.includesSubentities = false\r\n }\r\n)\r\n```\r\nThe clauses are evaluated the order they appear in the fetch/query, so you typically need to set `Tweak` as the last clause.\r\n`Tweak`'s closure is executed only just before the fetch occurs, so make sure that any values captured by the closure is not prone to race conditions.\r\n\r\nWhile `Tweak` lets you micro-configure the `NSFetchRequest`, note that CoreStore already preconfigured that `NSFetchRequest` to suitable defaults. Only use `Tweak` when you know what you are doing!\r\n\r\n### Querying\r\n\r\nOne of the functionalities overlooked by other Core Data wrapper libraries is raw properties fetching. If you are familiar with `NSDictionaryResultType` and `-[NSFetchedRequest propertiesToFetch]`, you probably know how painful it is to setup a query for raw values and aggregate values. CoreStore makes this easy by exposing the 2 methods below:\r\n\r\n- `queryValue(...)` - returns a single raw value for an attribute or for an aggregate value. If there are multiple results, `queryValue(...)` only returns the first item.\r\n- `queryAttributes(...)` - returns an array of dictionaries containing attribute keys with their corresponding values.\r\n\r\nBoth methods above accept the same parameters: a required `From` clause, a required `Select` clause, and an optional series of `Where`, `OrderBy`, `GroupBy`, and/or `Tweak` clauses.\r\n\r\nSetting up the `From`, `Where`, `OrderBy`, and `Tweak` clauses is similar to how you would when fetching. For querying, you also need to know how to use the `Select` and `GroupBy` clauses.\r\n\r\n#### `Select` clause\r\n\r\nThe `Select` clause specifies the target attribute/aggregate key, as well as the expected return type: \r\n```swift\r\nlet johnsAge = CoreStore.queryValue(\r\n From(MyPersonEntity),\r\n Select(\"age\"),\r\n Where(\"name == %@\", \"John Smith\")\r\n)\r\n```\r\nThe example above queries the \"age\" property for the first object that matches the `Where` condition. `johnsAge` will be bound to type `Int?`, as indicated by the `Select` generic type. For `queryValue(...)`, the following are allowed as the return type (and therefore as the generic type for `Select`):\r\n- `Bool`\r\n- `Int8`\r\n- `Int16`\r\n- `Int32`\r\n- `Int64`\r\n- `Double`\r\n- `Float`\r\n- `String`\r\n- `NSNumber`\r\n- `NSString`\r\n- `NSDecimalNumber`\r\n- `NSDate`\r\n- `NSData`\r\n- `NSManagedObjectID`\r\n- `NSString`\r\n\r\nFor `queryAttributes(...)`, only `NSDictionary` is valid for `Select`, thus you are allowed to omit the generic type:\r\n```swift\r\nlet allAges = CoreStore.queryAttributes(\r\n From(MyPersonEntity),\r\n Select(\"age\")\r\n)\r\n```\r\n\r\nIf you only need a value for a particular attribute, you can just specify the key name (like we did with `Select(\"age\")`), but several aggregate functions can also be used as parameter to `Select`:\r\n- `.Average(...)`\r\n- `.Count(...)`\r\n- `.Maximum(...)`\r\n- `.Minimum(...)`\r\n- `.Sum(...)`\r\n\r\n```swift\r\nlet oldestAge = CoreStore.queryValue(\r\n From(MyPersonEntity),\r\n Select(.Maximum(\"age\"))\r\n)\r\n```\r\n\r\nFor `queryAttributes(...)` which returns an array of dictionaries, you can specify multiple attributes/aggregates to `Select`:\r\n```swift\r\nlet personJSON = CoreStore.queryAttributes(\r\n From(MyPersonEntity),\r\n Select(\"name\", \"age\")\r\n)\r\n```\r\n`personJSON` will then have the value:\r\n```swift\r\n[\r\n [\r\n \"name\": \"John Smith\",\r\n \"age\": 30\r\n ],\r\n [\r\n \"name\": \"Jane Doe\",\r\n \"age\": 22\r\n ]\r\n]\r\n```\r\nYou can also include an aggregate as well:\r\n```swift\r\nlet personJSON = CoreStore.queryAttributes(\r\n From(MyPersonEntity),\r\n Select(\"name\", .Count(\"friends\"))\r\n)\r\n```\r\nwhich returns:\r\n```swift\r\n[\r\n [\r\n \"name\": \"John Smith\",\r\n \"count(friends)\": 42\r\n ],\r\n [\r\n \"name\": \"Jane Doe\",\r\n \"count(friends)\": 231\r\n ]\r\n]\r\n```\r\nThe `\"count(friends)\"` key name was automatically used by CoreStore, but you can specify your own key alias if you need:\r\n```swift\r\nlet personJSON = CoreStore.queryAttributes(\r\n From(MyPersonEntity),\r\n Select(\"name\", .Count(\"friends\", As: \"friendsCount\"))\r\n)\r\n```\r\nwhich now returns:\r\n```swift\r\n[\r\n [\r\n \"name\": \"John Smith\",\r\n \"friendsCount\": 42\r\n ],\r\n [\r\n \"name\": \"Jane Doe\",\r\n \"friendsCount\": 231\r\n ]\r\n]\r\n```\r\n\r\n#### `GroupBy` clause\r\n\r\nThe `GroupBy` clause lets you group results by a specified attribute/aggregate. This is useful only for `queryAttributes(...)` since `queryValue(...)` just returns the first value.\r\n```swift\r\nlet personJSON = CoreStore.queryAttributes(\r\n From(MyPersonEntity),\r\n Select(\"age\", .Count(\"age\", As: \"count\")),\r\n GroupBy(\"age\")\r\n)\r\n```\r\nthis returns dictionaries that shows the count for each `\"age\"`:\r\n```swift\r\n[\r\n [\r\n \"age\": 42,\r\n \"count\": 1\r\n ],\r\n [\r\n \"age\": 22,\r\n \"count\": 1\r\n ]\r\n]\r\n```\r\n\r\n## Logging and error handling\r\nOne unfortunate thing when using some third-party libraries is that they usually pollute the console with their own logging mechanisms. CoreStore provides its own default logging class, but you can plug-in your own favorite logger by implementing the `CoreStoreLogger` protocol.\r\n```swift\r\nfinal class MyLogger: CoreStoreLogger {\r\n func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {\r\n // pass to your logger\r\n }\r\n \r\n func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {\r\n // pass to your logger\r\n }\r\n \r\n func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {\r\n // pass to your logger\r\n }\r\n}\r\n```\r\nThen pass an instance of this class to `CoreStore`:\r\n```swift\r\nCoreStore.logger = MyLogger()\r\n```\r\nDoing so channels all logging calls to your logger.\r\n\r\nNote that to keep the call stack information intact, all calls to these methods are **NOT** thread-managed. Therefore you have to make sure that your logger is thread-safe or you may otherwise have to dispatch your logging implementation to a serial queue.\r\n\r\n## Observing changes and notifications (unavailable on OSX)\r\nCoreStore provides type-safe wrappers for observing managed objects:\r\n\r\n- `ObjectMonitor`: use to monitor changes to a single `NSManagedObject` instance (instead of Key-Value Observing)\r\n- `ListMonitor`: use to monitor changes to a list of `NSManagedObject` instances (instead of `NSFetchedResultsController`)\r\n\r\n### Observe a single object\r\n\r\nTo observe an object, implement the `ObjectObserver` protocol and specify the `EntityType`:\r\n```swift\r\nclass MyViewController: UIViewController, ObjectObserver {\r\n func objectMonitor(monitor: ObjectMonitor, willUpdateObject object: MyPersonEntity) {\r\n // ...\r\n }\r\n \r\n func objectMonitor(monitor: ObjectMonitor, didUpdateObject object: MyPersonEntity, changedPersistentKeys: Set) {\r\n // ...\r\n }\r\n \r\n func objectMonitor(monitor: ObjectMonitor, didDeleteObject object: MyPersonEntity) {\r\n // ...\r\n }\r\n}\r\n```\r\nWe then need to keep a `ObjectMonitor` instance and register our `ObjectObserver` as an observer:\r\n```swift\r\nlet person: MyPersonEntity = // ...\r\nself.monitor = CoreStore.monitorObject(person)\r\nself.monitor.addObserver(self)\r\n```\r\nThe controller will then notify our observer whenever the object's attributes change. You can add multiple `ObjectObserver`s to a single `ObjectMonitor` without any problem. This means you can just share around the `ObjectMonitor` instance to different screens without problem.\r\n\r\nYou can get `ObjectMonitor`'s object through its `object` property. If the object is deleted, the `object` property will become `nil` to prevent further access. \r\n\r\nWhile `ObjectMonitor` exposes `removeObserver(...)` as well, it only stores `weak` references of the observers and will safely unregister deallocated observers. \r\n\r\n### Observe a list of objects\r\nTo observe a list of objects, implement one of the `ListObserver` protocols and specify the `EntityType`:\r\n```swift\r\nclass MyViewController: UIViewController, ListObserver {\r\n func listMonitorWillChange(monitor: ListMonitor) {\r\n // ...\r\n }\r\n \r\n func listMonitorDidChange(monitor: ListMonitor) {\r\n // ...\r\n }\r\n}\r\n```\r\nIncluding `ListObserver`, there are 3 observer protocols you can implement depending on how detailed you need to handle a change notification:\r\n- `ListObserver`: lets you handle these callback methods:\r\n```swift\r\n func listMonitorWillChange(monitor: ListMonitor)\r\n\r\n func listMonitorDidChange(monitor: ListMonitor)\r\n```\r\n- `ListObjectObserver`: in addition to `ListObserver` methods, also lets you handle object inserts, updates, and deletes:\r\n```swift\r\n func listMonitor(monitor: ListMonitor, didInsertObject object: MyPersonEntity, toIndexPath indexPath: NSIndexPath)\r\n\r\n func listMonitor(monitor: ListMonitor, didDeleteObject object: MyPersonEntity, fromIndexPath indexPath: NSIndexPath)\r\n\r\n func listMonitor(monitor: ListMonitor, didUpdateObject object: MyPersonEntity, atIndexPath indexPath: NSIndexPath)\r\n\r\n func listMonitor(monitor: ListMonitor, didMoveObject object: MyPersonEntity, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)\r\n```\r\n- `ListSectionObserver`: in addition to `ListObjectObserver` methods, also lets you handle section inserts and deletes:\r\n```swift\r\n func listMonitor(monitor: ListMonitor, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)\r\n\r\n func listMonitor(monitor: ListMonitor, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)\r\n```\r\n\r\nWe then need to create a `ListMonitor` instance and register our `ListObserver` as an observer:\r\n```swift\r\nself.monitor = CoreStore.monitorList(\r\n From(MyPersonEntity),\r\n Where(\"age > 30\"),\r\n OrderBy(.Ascending(\"name\")),\r\n Tweak { (fetchRequest) -> Void in\r\n fetchRequest.fetchBatchSize = 20\r\n }\r\n)\r\nself.monitor.addObserver(self)\r\n```\r\nSimilar to `ObjectMonitor`, a `ListMonitor` can also have multiple `ListObserver`s registered to a single `ListMonitor`.\r\n\r\nIf you have noticed, the `monitorList(...)` method accepts `Where`, `OrderBy`, and `Tweak` clauses exactly like a fetch. As the list maintained by `ListMonitor` needs to have a deterministic order, at least the `From` and `OrderBy` clauses are required.\r\n\r\nA `ListMonitor` created from `monitorList(...)` will maintain a single-section list. You can therefore access its contents with just an index:\r\n```swift\r\nlet firstPerson = self.monitor[0]\r\n```\r\n\r\nIf the list needs to be grouped into sections, create the `ListMonitor` instance with the `monitorSectionedList(...)` method and a `SectionBy` clause:\r\n```swift\r\nself.monitor = CoreStore.monitorSectionedList(\r\n From(MyPersonEntity),\r\n SectionBy(\"age\"),\r\n Where(\"gender\", isEqualTo: \"M\"),\r\n OrderBy(.Ascending(\"age\"), .Ascending(\"name\")),\r\n Tweak { (fetchRequest) -> Void in\r\n fetchRequest.fetchBatchSize = 20\r\n }\r\n)\r\n```\r\nA list controller created this way will group the objects by the attribute key indicated by the `SectionBy` clause. One more thing to remember is that the `OrderBy` clause should sort the list in such a way that the `SectionBy` attribute would be sorted together (a requirement shared by `NSFetchedResultsController`.)\r\n\r\nThe `SectionBy` clause can also be passed a closure to transform the section name into a displayable string:\r\n```swift\r\nself.monitor = CoreStore.monitorSectionedList(\r\n From(MyPersonEntity),\r\n SectionBy(\"age\") { (sectionName) -> String? in\r\n \"\\(sectionName) years old\"\r\n },\r\n OrderBy(.Ascending(\"age\"), .Ascending(\"name\"))\r\n)\r\n```\r\nThis is useful when implementing a `UITableViewDelegate`'s section header:\r\n```swift\r\nfunc tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {\r\n let sectionInfo = self.monitor.sectionInfoAtIndex(section)\r\n // sectionInfo is an NSFetchedResultsSectionInfo instance\r\n return sectionInfo.name\r\n}\r\n```\r\n\r\nTo access the objects of a sectioned list, use an `NSIndexPath` or a tuple:\r\n```swift\r\nlet indexPath = NSIndexPath(forRow: 2, inSection: 1)\r\nlet person1 = self.monitor[indexPath]\r\nlet person2 = self.monitor[1, 2]\r\n// person1 and person2 are the same object\r\n```\r\n\r\n\r\n# Roadmap\r\n- Support iCloud stores\r\n- CoreSpotlight auto-indexing (experimental)\r\n\r\n\r\n# Installation\r\n- Requires:\r\n - iOS 8 SDK and above\r\n - Swift 2.1 (Xcode 7.2)\r\n- Dependencies:\r\n - [GCDKit](https://github.com/JohnEstropia/GCDKit)\r\n\r\n### Install with CocoaPods\r\n```\r\npod 'CoreStore'\r\n```\r\nThis installs CoreStore as a framework. Declare `import CoreStore` in your swift file to use the library.\r\n\r\n### Install with Carthage\r\nIn your `Cartfile`, add\r\n```\r\ngithub \"JohnEstropia/CoreStore\" >= 1.4.4\r\ngithub \"JohnEstropia/GCDKit\" >= 1.1.7\r\n```\r\nand run \r\n```\r\ncarthage update\r\n```\r\n\r\n### Install as Git Submodule\r\n```\r\ngit submodule add https://github.com/JohnEstropia/CoreStore.git \r\n```\r\nDrag and drop **CoreStore.xcodeproj** to your project.\r\n\r\n#### To install as a framework:\r\nDrag and drop **CoreStore.xcodeproj** to your project.\r\n\r\n#### To include directly in your app module:\r\nAdd all *.swift* files to your project.\r\n\r\n\r\n\r\n# Changesets\r\n### Upgrading from v0.2.0 to 1.0.0\r\n- Renamed some classes/protocols to shorter, more relevant, easier to remember names:\r\n- `ManagedObjectController` to `ObjectMonitor`\r\n- `ManagedObjectObserver` to `ObjectObserver`\r\n- `ManagedObjectListController` to `ListMonitor`\r\n- `ManagedObjectListChangeObserver` to `ListObserver`\r\n- `ManagedObjectListObjectObserver` to `ListObjectObserver`\r\n- `ManagedObjectListSectionObserver` to `ListSectionObserver`\r\n- `SectionedBy` to `SectionBy` (match tense with `OrderBy` and `GroupBy`)\r\nThe protocols above had their methods renamed as well, to retain the natural language semantics.\r\n- Several methods now `throw` errors insted of returning a result `enum`.\r\n- New migration utilities! (README still pending) Check out *DataStack+Migration.swift* and *CoreStore+Migration.swift* for the new methods, as well as *DataStack.swift* for its new initializer.\r\n\r\n\r\n\r\n# Contributions\r\nWhile CoreStore's design is pretty solid and the unit test and demo app work well, CoreStore is pretty much still in its early stage. With more exposure to production code usage and criticisms from the developer community, CoreStore hopes to mature as well.\r\nPlease feel free to report any issues, suggestions, or criticisms!\r\n日本語で連絡していただいても構いません!\r\n\r\n## License\r\nCoreStore is released under an MIT license. See the LICENSE file for more information\r\n","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."} \ No newline at end of file diff --git a/stylesheets/github-light.css b/stylesheets/github-light.css deleted file mode 100644 index 872a6f4..0000000 --- a/stylesheets/github-light.css +++ /dev/null @@ -1,116 +0,0 @@ -/* - Copyright 2014 GitHub Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -.pl-c /* comment */ { - color: #969896; -} - -.pl-c1 /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */, -.pl-s .pl-v /* string variable */ { - color: #0086b3; -} - -.pl-e /* entity */, -.pl-en /* entity.name */ { - color: #795da3; -} - -.pl-s .pl-s1 /* string source */, -.pl-smi /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ { - color: #333; -} - -.pl-ent /* entity.name.tag */ { - color: #63a35c; -} - -.pl-k /* keyword, storage, storage.type */ { - color: #a71d5d; -} - -.pl-pds /* punctuation.definition.string, string.regexp.character-class */, -.pl-s /* string */, -.pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */, -.pl-sr /* string.regexp */, -.pl-sr .pl-cce /* string.regexp constant.character.escape */, -.pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */, -.pl-sr .pl-sre /* string.regexp source.ruby.embedded */ { - color: #183691; -} - -.pl-v /* variable */ { - color: #ed6a43; -} - -.pl-id /* invalid.deprecated */ { - color: #b52a1d; -} - -.pl-ii /* invalid.illegal */ { - background-color: #b52a1d; - color: #f8f8f8; -} - -.pl-sr .pl-cce /* string.regexp constant.character.escape */ { - color: #63a35c; - font-weight: bold; -} - -.pl-ml /* markup.list */ { - color: #693a17; -} - -.pl-mh /* markup.heading */, -.pl-mh .pl-en /* markup.heading entity.name */, -.pl-ms /* meta.separator */ { - color: #1d3e81; - font-weight: bold; -} - -.pl-mq /* markup.quote */ { - color: #008080; -} - -.pl-mi /* markup.italic */ { - color: #333; - font-style: italic; -} - -.pl-mb /* markup.bold */ { - color: #333; - font-weight: bold; -} - -.pl-md /* markup.deleted, meta.diff.header.from-file */ { - background-color: #ffecec; - color: #bd2c00; -} - -.pl-mi1 /* markup.inserted, meta.diff.header.to-file */ { - background-color: #eaffea; - color: #55a532; -} - -.pl-mdr /* meta.diff.range */ { - color: #795da3; - font-weight: bold; -} - -.pl-mo /* meta.output */ { - color: #1d3e81; -} - diff --git a/stylesheets/normalize.css b/stylesheets/normalize.css deleted file mode 100644 index 30366a6..0000000 --- a/stylesheets/normalize.css +++ /dev/null @@ -1,424 +0,0 @@ -/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ - -/** - * 1. Set default font family to sans-serif. - * 2. Prevent iOS text size adjust after orientation change, without disabling - * user zoom. - */ - -html { - font-family: sans-serif; /* 1 */ - -ms-text-size-adjust: 100%; /* 2 */ - -webkit-text-size-adjust: 100%; /* 2 */ -} - -/** - * Remove default margin. - */ - -body { - margin: 0; -} - -/* HTML5 display definitions - ========================================================================== */ - -/** - * Correct `block` display not defined for any HTML5 element in IE 8/9. - * Correct `block` display not defined for `details` or `summary` in IE 10/11 - * and Firefox. - * Correct `block` display not defined for `main` in IE 11. - */ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -menu, -nav, -section, -summary { - display: block; -} - -/** - * 1. Correct `inline-block` display not defined in IE 8/9. - * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. - */ - -audio, -canvas, -progress, -video { - display: inline-block; /* 1 */ - vertical-align: baseline; /* 2 */ -} - -/** - * Prevent modern browsers from displaying `audio` without controls. - * Remove excess height in iOS 5 devices. - */ - -audio:not([controls]) { - display: none; - height: 0; -} - -/** - * Address `[hidden]` styling not present in IE 8/9/10. - * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. - */ - -[hidden], -template { - display: none; -} - -/* Links - ========================================================================== */ - -/** - * Remove the gray background color from active links in IE 10. - */ - -a { - background-color: transparent; -} - -/** - * Improve readability when focused and also mouse hovered in all browsers. - */ - -a:active, -a:hover { - outline: 0; -} - -/* Text-level semantics - ========================================================================== */ - -/** - * Address styling not present in IE 8/9/10/11, Safari, and Chrome. - */ - -abbr[title] { - border-bottom: 1px dotted; -} - -/** - * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. - */ - -b, -strong { - font-weight: bold; -} - -/** - * Address styling not present in Safari and Chrome. - */ - -dfn { - font-style: italic; -} - -/** - * Address variable `h1` font-size and margin within `section` and `article` - * contexts in Firefox 4+, Safari, and Chrome. - */ - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/** - * Address styling not present in IE 8/9. - */ - -mark { - background: #ff0; - color: #000; -} - -/** - * Address inconsistent and variable font size in all browsers. - */ - -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` affecting `line-height` in all browsers. - */ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -/* Embedded content - ========================================================================== */ - -/** - * Remove border when inside `a` element in IE 8/9/10. - */ - -img { - border: 0; -} - -/** - * Correct overflow not hidden in IE 9/10/11. - */ - -svg:not(:root) { - overflow: hidden; -} - -/* Grouping content - ========================================================================== */ - -/** - * Address margin not present in IE 8/9 and Safari. - */ - -figure { - margin: 1em 40px; -} - -/** - * Address differences between Firefox and other browsers. - */ - -hr { - box-sizing: content-box; - height: 0; -} - -/** - * Contain overflow in all browsers. - */ - -pre { - overflow: auto; -} - -/** - * Address odd `em`-unit font size rendering in all browsers. - */ - -code, -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} - -/* Forms - ========================================================================== */ - -/** - * Known limitation: by default, Chrome and Safari on OS X allow very limited - * styling of `select`, unless a `border` property is set. - */ - -/** - * 1. Correct color not being inherited. - * Known issue: affects color of disabled elements. - * 2. Correct font properties not being inherited. - * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. - */ - -button, -input, -optgroup, -select, -textarea { - color: inherit; /* 1 */ - font: inherit; /* 2 */ - margin: 0; /* 3 */ -} - -/** - * Address `overflow` set to `hidden` in IE 8/9/10/11. - */ - -button { - overflow: visible; -} - -/** - * Address inconsistent `text-transform` inheritance for `button` and `select`. - * All other form control elements do not inherit `text-transform` values. - * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. - * Correct `select` style inheritance in Firefox. - */ - -button, -select { - text-transform: none; -} - -/** - * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` - * and `video` controls. - * 2. Correct inability to style clickable `input` types in iOS. - * 3. Improve usability and consistency of cursor style between image-type - * `input` and others. - */ - -button, -html input[type="button"], /* 1 */ -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; /* 2 */ - cursor: pointer; /* 3 */ -} - -/** - * Re-set default cursor for disabled elements. - */ - -button[disabled], -html input[disabled] { - cursor: default; -} - -/** - * Remove inner padding and border in Firefox 4+. - */ - -button::-moz-focus-inner, -input::-moz-focus-inner { - border: 0; - padding: 0; -} - -/** - * Address Firefox 4+ setting `line-height` on `input` using `!important` in - * the UA stylesheet. - */ - -input { - line-height: normal; -} - -/** - * It's recommended that you don't attempt to style these elements. - * Firefox's implementation doesn't respect box-sizing, padding, or width. - * - * 1. Address box sizing set to `content-box` in IE 8/9/10. - * 2. Remove excess padding in IE 8/9/10. - */ - -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Fix the cursor style for Chrome's increment/decrement buttons. For certain - * `font-size` values of the `input`, it causes the cursor style of the - * decrement button to change from `default` to `text`. - */ - -input[type="number"]::-webkit-inner-spin-button, -input[type="number"]::-webkit-outer-spin-button { - height: auto; -} - -/** - * 1. Address `appearance` set to `searchfield` in Safari and Chrome. - * 2. Address `box-sizing` set to `border-box` in Safari and Chrome - * (include `-moz` to future-proof). - */ - -input[type="search"] { - -webkit-appearance: textfield; /* 1 */ /* 2 */ - box-sizing: content-box; -} - -/** - * Remove inner padding and search cancel button in Safari and Chrome on OS X. - * Safari (but not Chrome) clips the cancel button when the search input has - * padding (and `textfield` appearance). - */ - -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** - * Define consistent border, margin, and padding. - */ - -fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} - -/** - * 1. Correct `color` not being inherited in IE 8/9/10/11. - * 2. Remove padding so people aren't caught out if they zero out fieldsets. - */ - -legend { - border: 0; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Remove default vertical scrollbar in IE 8/9/10/11. - */ - -textarea { - overflow: auto; -} - -/** - * Don't inherit the `font-weight` (applied by a rule above). - * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. - */ - -optgroup { - font-weight: bold; -} - -/* Tables - ========================================================================== */ - -/** - * Remove most spacing between table cells. - */ - -table { - border-collapse: collapse; - border-spacing: 0; -} - -td, -th { - padding: 0; -} diff --git a/stylesheets/stylesheet.css b/stylesheets/stylesheet.css deleted file mode 100644 index b5f20c2..0000000 --- a/stylesheets/stylesheet.css +++ /dev/null @@ -1,245 +0,0 @@ -* { - box-sizing: border-box; } - -body { - padding: 0; - margin: 0; - font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 1.5; - color: #606c71; } - -a { - color: #1e6bb8; - text-decoration: none; } - a:hover { - text-decoration: underline; } - -.btn { - display: inline-block; - margin-bottom: 1rem; - color: rgba(255, 255, 255, 0.7); - background-color: rgba(255, 255, 255, 0.08); - border-color: rgba(255, 255, 255, 0.2); - border-style: solid; - border-width: 1px; - border-radius: 0.3rem; - transition: color 0.2s, background-color 0.2s, border-color 0.2s; } - .btn + .btn { - margin-left: 1rem; } - -.btn:hover { - color: rgba(255, 255, 255, 0.8); - text-decoration: none; - background-color: rgba(255, 255, 255, 0.2); - border-color: rgba(255, 255, 255, 0.3); } - -@media screen and (min-width: 64em) { - .btn { - padding: 0.75rem 1rem; } } - -@media screen and (min-width: 42em) and (max-width: 64em) { - .btn { - padding: 0.6rem 0.9rem; - font-size: 0.9rem; } } - -@media screen and (max-width: 42em) { - .btn { - display: block; - width: 100%; - padding: 0.75rem; - font-size: 0.9rem; } - .btn + .btn { - margin-top: 1rem; - margin-left: 0; } } - -.page-header { - color: #fff; - text-align: center; - background-color: #159957; - background-image: linear-gradient(120deg, #155799, #159957); } - -@media screen and (min-width: 64em) { - .page-header { - padding: 5rem 6rem; } } - -@media screen and (min-width: 42em) and (max-width: 64em) { - .page-header { - padding: 3rem 4rem; } } - -@media screen and (max-width: 42em) { - .page-header { - padding: 2rem 1rem; } } - -.project-name { - margin-top: 0; - margin-bottom: 0.1rem; } - -@media screen and (min-width: 64em) { - .project-name { - font-size: 3.25rem; } } - -@media screen and (min-width: 42em) and (max-width: 64em) { - .project-name { - font-size: 2.25rem; } } - -@media screen and (max-width: 42em) { - .project-name { - font-size: 1.75rem; } } - -.project-tagline { - margin-bottom: 2rem; - font-weight: normal; - opacity: 0.7; } - -@media screen and (min-width: 64em) { - .project-tagline { - font-size: 1.25rem; } } - -@media screen and (min-width: 42em) and (max-width: 64em) { - .project-tagline { - font-size: 1.15rem; } } - -@media screen and (max-width: 42em) { - .project-tagline { - font-size: 1rem; } } - -.main-content :first-child { - margin-top: 0; } -.main-content img { - max-width: 100%; } -.main-content h1, .main-content h2, .main-content h3, .main-content h4, .main-content h5, .main-content h6 { - margin-top: 2rem; - margin-bottom: 1rem; - font-weight: normal; - color: #159957; } -.main-content p { - margin-bottom: 1em; } -.main-content code { - padding: 2px 4px; - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 0.9rem; - color: #383e41; - background-color: #f3f6fa; - border-radius: 0.3rem; } -.main-content pre { - padding: 0.8rem; - margin-top: 0; - margin-bottom: 1rem; - font: 1rem Consolas, "Liberation Mono", Menlo, Courier, monospace; - color: #567482; - word-wrap: normal; - background-color: #f3f6fa; - border: solid 1px #dce6f0; - border-radius: 0.3rem; } - .main-content pre > code { - padding: 0; - margin: 0; - font-size: 0.9rem; - color: #567482; - word-break: normal; - white-space: pre; - background: transparent; - border: 0; } -.main-content .highlight { - margin-bottom: 1rem; } - .main-content .highlight pre { - margin-bottom: 0; - word-break: normal; } -.main-content .highlight pre, .main-content pre { - padding: 0.8rem; - overflow: auto; - font-size: 0.9rem; - line-height: 1.45; - border-radius: 0.3rem; } -.main-content pre code, .main-content pre tt { - display: inline; - max-width: initial; - padding: 0; - margin: 0; - overflow: initial; - line-height: inherit; - word-wrap: normal; - background-color: transparent; - border: 0; } - .main-content pre code:before, .main-content pre code:after, .main-content pre tt:before, .main-content pre tt:after { - content: normal; } -.main-content ul, .main-content ol { - margin-top: 0; } -.main-content blockquote { - padding: 0 1rem; - margin-left: 0; - color: #819198; - border-left: 0.3rem solid #dce6f0; } - .main-content blockquote > :first-child { - margin-top: 0; } - .main-content blockquote > :last-child { - margin-bottom: 0; } -.main-content table { - display: block; - width: 100%; - overflow: auto; - word-break: normal; - word-break: keep-all; } - .main-content table th { - font-weight: bold; } - .main-content table th, .main-content table td { - padding: 0.5rem 1rem; - border: 1px solid #e9ebec; } -.main-content dl { - padding: 0; } - .main-content dl dt { - padding: 0; - margin-top: 1rem; - font-size: 1rem; - font-weight: bold; } - .main-content dl dd { - padding: 0; - margin-bottom: 1rem; } -.main-content hr { - height: 2px; - padding: 0; - margin: 1rem 0; - background-color: #eff0f1; - border: 0; } - -@media screen and (min-width: 64em) { - .main-content { - max-width: 64rem; - padding: 2rem 6rem; - margin: 0 auto; - font-size: 1.1rem; } } - -@media screen and (min-width: 42em) and (max-width: 64em) { - .main-content { - padding: 2rem 4rem; - font-size: 1.1rem; } } - -@media screen and (max-width: 42em) { - .main-content { - padding: 2rem 1rem; - font-size: 1rem; } } - -.site-footer { - padding-top: 2rem; - margin-top: 2rem; - border-top: solid 1px #eff0f1; } - -.site-footer-owner { - display: block; - font-weight: bold; } - -.site-footer-credits { - color: #819198; } - -@media screen and (min-width: 64em) { - .site-footer { - font-size: 1rem; } } - -@media screen and (min-width: 42em) and (max-width: 64em) { - .site-footer { - font-size: 1rem; } } - -@media screen and (max-width: 42em) { - .site-footer { - font-size: 0.9rem; } }