diff --git a/CoreStore/Convenience Helpers/NSManagedObject+Convenience.swift b/CoreStore/Convenience Helpers/NSManagedObject+Convenience.swift index 9c88f94..989e85c 100644 --- a/CoreStore/Convenience Helpers/NSManagedObject+Convenience.swift +++ b/CoreStore/Convenience Helpers/NSManagedObject+Convenience.swift @@ -31,6 +31,12 @@ import CoreData public extension NSManagedObject { + /** + Provides a convenience wrapper for accessing `primitiveValueForKey(...)` with proper calls to `willAccessValueForKey(...)` and `didAccessValueForKey(...)`. This is useful when implementing accessor methods for transient attributes. + + - parameter KVCKey: the KVC key + - returns: the primitive value for the KVC key + */ public func accessValueForKVCKey(KVCKey: KeyPath) -> AnyObject? { self.willAccessValueForKey(KVCKey) @@ -40,6 +46,12 @@ public extension NSManagedObject { return primitiveValue } + /** + Provides a convenience wrapper for setting `setPrimitiveValue(...)` with proper calls to `willChangeValueForKey(...)` and `didChangeValueForKey(...)`. This is useful when implementing mutator methods for transient attributes. + + - parameter value: the value to set the KVC key with + - parameter KVCKey: the KVC key + */ public func setValue(value: AnyObject?, forKVCKey KVCKey: KeyPath) { self.willChangeValueForKey(KVCKey) diff --git a/CoreStore/Convenience Helpers/NSProgress+Convenience.swift b/CoreStore/Convenience Helpers/NSProgress+Convenience.swift index 197edbc..0d811b3 100644 --- a/CoreStore/Convenience Helpers/NSProgress+Convenience.swift +++ b/CoreStore/Convenience Helpers/NSProgress+Convenience.swift @@ -33,6 +33,10 @@ public extension NSProgress { // MARK: Public + /** + Sets a closure that the `NSProgress` calls whenever its `fractionCompleted` changes. You can use this instead of setting up KVO. + - parameter closure: the closure to execute on progress change + */ public func setProgressHandler(closure: ((progress: NSProgress) -> Void)?) { self.progressObserver.progressHandler = closure diff --git a/CoreStore/Internal/NSManagedObjectContext+Querying.swift b/CoreStore/Internal/NSManagedObjectContext+Querying.swift index 2eaca52..9d7bcdd 100644 --- a/CoreStore/Internal/NSManagedObjectContext+Querying.swift +++ b/CoreStore/Internal/NSManagedObjectContext+Querying.swift @@ -251,6 +251,7 @@ internal extension NSManagedObjectContext { fetchRequest.fetchLimit = 0 fetchRequest.resultType = .ManagedObjectResultType fetchRequest.returnsObjectsAsFaults = true + fetchRequest.includesPropertyValues = false for clause in deleteClauses { diff --git a/CoreStore/Migrating/MigrationChain.swift b/CoreStore/Migrating/MigrationChain.swift index 424d6ae..784c579 100644 --- a/CoreStore/Migrating/MigrationChain.swift +++ b/CoreStore/Migrating/MigrationChain.swift @@ -32,7 +32,7 @@ import CoreData /** A `MigrationChain` indicates the sequence of model versions to be used as the order for incremental migration. This is typically passed to the `DataStack` initializer and will be applied to all stores added to the `DataStack` with `addSQLiteStore(...)` and its variants. -Initializing with empty values (either `nil`, `[]`, or `[:]`) signifies to use the .xcdatamodel's current version as the final version, and to disable incremental migrations: +Initializing with empty values (either `nil`, `[]`, or `[:]`) instructs the `DataStack` to use the .xcdatamodel's current version as the final version, and to disable incremental migrations: let dataStack = DataStack(migrationChain: nil) @@ -151,8 +151,7 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D self.versionTree = versionTree self.rootVersions = Set(versionTree.keys).subtract(versionTree.values) self.leafVersions = leafVersions - self.valid = valid - && Set(versionTree.keys).union(versionTree.values).filter { isVersionAmbiguous($0) }.count <= 0 + self.valid = valid && Set(versionTree.keys).union(versionTree.values).filter { isVersionAmbiguous($0) }.count <= 0 } diff --git a/CoreStore/Migrating/MigrationResult.swift b/CoreStore/Migrating/MigrationResult.swift index 5e36990..a72f2e3 100644 --- a/CoreStore/Migrating/MigrationResult.swift +++ b/CoreStore/Migrating/MigrationResult.swift @@ -137,12 +137,12 @@ public enum MigrationResult { // MARK: Public /** - `MigrationResult.Success` indicates that the `commit()` for the transaction succeeded, either because the save succeeded or because there were no changes to save. The associated value `hasChanges` indicates if there were saved changes or not. + `MigrationResult.Success` indicates either the migration succeeded, or there were no migrations needed. The associated value is an array of `MigrationType`s reflecting the migration steps completed. */ case Success([MigrationType]) /** - `SaveResult.Failure` indicates that the `commit()` for the transaction failed. The associated object for this value is the related `NSError` instance. + `SaveResult.Failure` indicates that the migration failed. The associated object for this value is the related `NSError` instance. */ case Failure(NSError) @@ -166,9 +166,7 @@ public enum MigrationResult { internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) { - self.init(NSError( - coreStoreErrorCode: errorCode, - userInfo: userInfo)) + self.init(NSError(coreStoreErrorCode: errorCode, userInfo: userInfo)) } } diff --git a/README.md b/README.md index 6fbf1dc..305dc91 100644 --- a/README.md +++ b/README.md @@ -9,23 +9,46 @@ Unleashing the real power of Core Data with the elegance and safety of Swift [Click here for a wiki version of this README](https://github.com/JohnEstropia/CoreStore/wiki) -[Upgrading from 0.2.0 to 1.0.0](#changes-from-v020-to-100) -## Another Core Data wrapper? +## Contents -I have used (and abused) Core Data for almost 5 years. While the majority of Core Data wrappers serve their purpose really well (I worked with [MagicalRecord](https://github.com/magicalpanda/MagicalRecord) for a looong time), I have always felt that they "wrap" too much of the Core Data SDK's functionality. - -For example: -- a lot of iOS devs have never used (or heard of) "Configurations" -- very few are aware that entities can be saved in separate *sqlite* files to boost performance and reduce data corruption -- we're forced to name our `NSManagedObject` subclasses exactly the same as our Entities -- and so on... - -I wrote this library when Swift was made public, and CoreStore is now a powerhouse with functionalities rarely implemented in other Core Data libraries. +- [What CoreStore does better](#what-corestore-does-better) +- [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) + - [Incremental migrations](#incremental-migrations) + - [Saving and processing transactions](#saving-and-processing-transactions) + - [Transaction types](#transaction-types) + - [Asynchronous transactions](#asynchronous-transactions) + - [Synchronous transactions](#synchronous-transactions) + - [Detached transactions](#detached-transactions) + - [Creating objects](#creating-objects) + - [Updating objects](#updating-objects) + - [Deleting objects](#deleting-objects) + - [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) + - [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) -### What CoreStore does better: + +## 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. - Ability to plug-in your own logging framework @@ -39,19 +62,33 @@ I wrote this library when Swift was made public, and CoreStore is now a powerhou **CoreStore's goal is not to expose shorter, magical syntax, but to provide an API that focuses on readability, consistency, and safety.** + ## TL;DR (a.k.a. sample codes) -Quick-setup: +Setting-up with incremental migration support: +```swift +CoreStore.defaultStack = DataStack( + modelName: "MyStore", + migrationChain: ["MyStore", "MyStoreV2", "MyStoreV3"] +) +``` + +Adding a store: ```swift do { - try CoreStore.addSQLiteStoreAndWait(fileName: "MyStore.sqlite") + try CoreStore.addSQLiteStore( + fileName: "MyStore.sqlite", + completion: { (result) -> Void in + // ... + } + ) } catch { // ... } ``` -Simple transactions: +Starting transactions: ```swift CoreStore.beginAsynchronous { (transaction) -> Void in let person = transaction.create(Into(MyPersonEntity)) @@ -67,7 +104,7 @@ CoreStore.beginAsynchronous { (transaction) -> Void in } ``` -Easy fetching: +Fetching objects: ```swift let people = CoreStore.fetchAll(From(MyPersonEntity)) ``` @@ -82,7 +119,7 @@ let people = CoreStore.fetchAll( ) ``` -Simple queries: +Querying values: ```swift let maxAge = CoreStore.queryValue( From(MyPersonEntity), @@ -95,20 +132,6 @@ 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! -## Contents - -- Tutorials - - [Architecture](#architecture) - - [Setting up](#setup) - - [Saving and processing transactions](#transactions) - - [Fetching and querying](#fetch_query) - - [Logging and error handling](#logging) - - [Observing changes and notifications](#observing) -- [Roadmap](#roadmap) -- [Installation](#installation) - -(All of these have demos in the **CoreStoreDemo** app project!) - ## 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. @@ -217,6 +240,13 @@ class MyViewController: UIViewController { ``` +## Migrations + + +### Incremental migrations + + + ## 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: @@ -236,7 +266,7 @@ CoreStore.beginAsynchronous { (transaction) -> Void in ``` 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 you disposal: *asynchronous*, *synchronous*, and *detached*. +The examples above use `beginAsynchronous(...)`, but there are actually 3 types of transactions at your disposal: *asynchronous*, *synchronous*, and *detached*. ### Transaction types @@ -329,7 +359,7 @@ CoreStore.beginAsynchronous { (transaction) -> Void in transaction.commit() } ``` -*(For more about fetching, read [Fetching and querying](#fetch_query))* +*(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 @@ -810,22 +840,8 @@ let person2 = self.monitor[1, 2] // person1 and person2 are the same object ``` -# Changes 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. -- New migration utilities! (README still pending) Check out *DataStack+Migration.swift* and *CoreStore+Migration.swift* for the new methods. - # Roadmap -- Migration utilities (In progress!) -- Swift 2.0 syntax (In progress!) - Data importing utilities for transactions - Support iCloud stores @@ -860,6 +876,23 @@ 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!