Unleashing the real power of Core Data with the elegance and safety of Swift
Dependency managers
Contact
Nesting saves from child context to the root context ensures maximum data integrity between contexts without blocking the main queue. But in reality, merging contexts is still by far faster than saving contexts. CoreStore's `DataStack` takes the best of both worlds by treating the main `NSManagedObjectContext` as a read-only context, and only allows changes to be made within *transactions* on the child context:
This allows for a butter-smooth main thread, while still taking advantage of safe nested contexts.
## Setting up
The simplest way to initialize CoreStore is to add a default store to the default stack:
```swift
try CoreStore.addStorageAndWait()
```
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 `SQLiteStore` in the *"Application Support/
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(xcodeModelName: "MyModel") // keep reference to the stack
override func viewDidLoad() {
super.viewDidLoad()
do {
try self.dataStack.addStorageAndWait(SQLiteStore.self)
}
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 {
// elsewhere: CoreStore.defaultStack = DataStack(modelName: "MyModel")
override func viewDidLoad() {
super.viewDidLoad()
do {
try CoreStore.addStorageAndWait(SQLiteStore.self)
}
catch { // ...
}
}
func methodToBeCalledLaterOn() {
let objects = CoreStore.fetchAll(From(MyEntity))
print(objects)
}
}
```
Notice that in our previous examples, `addStorageAndWait(_:)` and `addStorage(_:completion:)` both accept either `InMemoryStore`, `SQLiteStore`, or `ICloudStore`. These implement the `StorageInterface` protocol.
### In-memory store
The most basic `StorageInterface` concrete type is the `InMemoryStore`, which just stores objects in memory. Since `InMemoryStore`s always start with a fresh empty data, they do not need any migration information.
```swift
try CoreStore.addStorageAndWait(
InMemoryStore(
configuration: "Config2" // optional. Use entities from the "Config2" configuration in the .xcdatamodeld file
)
)
```
### Local Store
The most common `StorageInterface` you will probably use is the `SQLiteStore`, which saves data in a local SQLite file.
```swift
let migrationProgress = CoreStore.addStorage(
SQLiteStore(
fileName: "MyStore.sqlite",
configuration: "Config2", // optional. Use entities from the "Config2" configuration in the .xcdatamodeld file
migrationMappingProviders: [Bundle.main], // optional. The bundles that contain required .xcmappingmodel files
localStorageOptions: .recreateStoreOnModelMismatch // optional. Provides settings that tells the DataStack how to setup the persistent store
),
completion: { /* ... */ }
)
```
Refer to the *SQLiteStore.swift* source documentation for detailed explanations for each of the default values.
CoreStore can decide the default values for these properties, so `SQLiteStore`s can be initialized with no arguments:
```swift
try CoreStore.addStorageAndWait(SQLiteStore())
```
The file-related properties above are actually requirements of another protocol that `SQLiteStore` implements, the `LocalStorage` protocol:
```swift
public protocol LocalStorage: StorageInterface {
var fileURL: NSURL { get }
var migrationMappingProviders: [SchemaMappingProvider] { get }
var localStorageOptions: LocalStorageOptions { get }
func dictionary(forOptions: LocalStorageOptions) -> [String: AnyObject]?
func cs_eraseStorageAndWait(metadata: [String: Any], soureModelHint: NSManagedObjectModel?) throws
}
```
If you have custom `NSIncrementalStore` or `NSAtomicStore` subclasses, you can implement this protocol and use it similarly to `SQLiteStore`.
### iCloud Store
> **Important:** The iCloud Store is currently in beta. Please use with caution. If you have any concerns please do send me a message on [Twitter](https://twitter.com/JohnEstropia) or on the [CoreStore Slack Team](http://swift-corestore-slack.herokuapp.com/)
As a counterpart to `LocalStorage`, the `CloudStorage` protocol abstracts stores managed in the cloud. CoreStore currently provides the concrete class `ICloudStore`. Unlike `InMemoryStore` and `SQLiteStore` though, the `ICloudStore`'s initializer may return `nil` if the iCloud container could not be located or if iCloud is not available on the device:
```swift
guard let storage = ICloudStore(
ubiquitousContentName: "MyAppCloudData", // the name of the store in iCloud
ubiquitousContentTransactionLogsSubdirectory: "logs/config1", // optional. Subdirectory path for the transaction logs
ubiquitousContainerID: "iCloud.com.mycompany.myapp.containername", // optional. The container if your app has multiple ubiquity container identifiers in its entitlements
ubiquitousPeerToken: "9614d658014f4151a95d8048fb717cf0", // optional. A per-application salt to allow multiple apps on the same device to share a Core Data store integrated with iCloud
configuration: "Config1", // optional. Use entities from the "Config1" configuration in the .xcdatamodeld file
cloudStorageOptions: .recreateLocalStoreOnModelMismatch // optional. Provides settings that tells the DataStack how to setup the persistent store
) else {
// The iCloud container could not be located or if iCloud is not available on the device.
// Handle appropriately
return
}
CoreStore.addStorage(,
storage,
completion: { result in
switch result {
case .success(let storage): // ...
case .failure(let error): // ...
}
}
)
```
If your app is using iCloud stores, you may want to be notified of particular iCloud events. The `ICloudStoreObserver` functions are all optional, so you may implement only the ones your app is interested in:
```swift
public protocol ICloudStoreObserver: class {
func iCloudStoreWillFinishUbiquitousStoreInitialImport(storage storage: ICloudStore, dataStack: DataStack)
func iCloudStoreDidFinishUbiquitousStoreInitialImport(storage storage: ICloudStore, dataStack: DataStack)
func iCloudStoreWillAddAccount(storage storage: ICloudStore, dataStack: DataStack)
func iCloudStoreDidAddAccount(storage storage: ICloudStore, dataStack: DataStack)
func iCloudStoreWillRemoveAccount(storage storage: ICloudStore, dataStack: DataStack)
func iCloudStoreDidRemoveAccount(storage storage: ICloudStore, dataStack: DataStack)
func iCloudStoreWillRemoveContent(storage storage: ICloudStore, dataStack: DataStack)
func iCloudStoreDidRemoveContent(storage storage: ICloudStore, dataStack: DataStack)
}
```
To register your `ICloudStoreObserver`, call `addObserver(_:)` on the `ICloudStore` instance:
```swift
guard let storage = ICloudStore(/* ... */) else {
return
}
storage.addObserver(self) // assuming self implements ICloudStoreObserver
CoreStore.addStorage(,
storage,
completion: { result in
switch result {
case .success(let storage): // ... You may also call storage.addObserver(_:) here
case .failure(let error): // ...
}
}
)
```
The `ICloudStore` only keeps weak references of the registered observers. You may call `removeObserver(_:)` for precise deregistration, but `ICloudStore` automatically removes deallocated observers.
## Migrations
### Declaring model versions
Until CoreStore 4.0, model versions were always assumed to be declared in *.xcdatamodeld* files. The `DataStack` loads these for us by accepting the *.xcdatamodeld* file name and the `Bundle` where the files can be found:
```swift
CoreStore.defaultStack = DataStack(
xcodeModelName: "MyModel",
bundle: Bundle.main,
migrationChain: ["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4"]
)
```
Starting CoreStore 4.0, model versions are now expressed as a first-class protocol, `DynamicSchema`. CoreStore currently supports the following schema classes:
- **`XcodeDataModelSchema`**: a model version with entities loaded from a *.xcdatamodeld* file.
- **`CoreStoreSchema`**: a model version created with `CoreStoreObject` entities. *(See [Type-safe `CoreStoreObject`s](#type-safe-corestore-objects))*
- **`UnsafeDataModelSchema`**: a model version created with an existing `NSManagedObjectModel` instance.
All the `DynamicSchema` for all model versions are then collected within a single `SchemaHistory` instance, which is then handed to the `DataStack`. Here are some common use cases:
**Multiple model versions grouped in a *.xcdatamodeld* file (Core Data standard method)**
```swift
CoreStore.defaultStack = DataStack(
xcodeModelName: "MyModel",
bundle: Bundle.main,
migrationChain: ["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4"]
)
```
**`CoreStoreSchema`-based model version (No *.xcdatamodeld* file needed)**
*(For more details, see also [Type-safe `CoreStoreObject`s](#type-safe-corestore-objects))*
```swift
class Animal: CoreStoreObject {
// ...
}
class Dog: Animal {
// ...
}
class Person: CoreStoreObject {
// ...
}
CoreStore.defaultStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
Entity
`CoreStoreError.mappingModelNotFoundError`:
These are all implemented with `CustomDebugStringConvertible.debugDescription`, so they work with lldb's `po` command as well.
## Observing changes and notifications (unavailable on macOS)
CoreStore provides type-safe wrappers for observing managed objects:
- `ObjectMonitor`: use to monitor changes to a single `NSManagedObject` or `CoreStoreObject` instance (instead of Key-Value Observing)
- `ListMonitor`: use to monitor changes to a list of `NSManagedObject` or `CoreStoreObject` 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| Swift | Objective-C |
|---|---|
try CoreStore.addStorageAndWait(SQLiteStore.self)
|
NSError *error
[CSCoreStore addSQLiteStorageAndWait:[CSSQLiteStore new] error:&error]
|
CoreStore.perform(
asynchronous: { (transaction) in
// ...
},
completion: { (result) in
switch result {
case .success: print("Done")
case .failure(let error): print(error)
}
}
)
|
[CSCoreStore beginAsynchronous:^(CSAsynchronousDataTransaction *transaction) {
// ...
[transaction commitWithCompletion:^(CSSaveResult *result) {
if (result.isSuccess) {
NSLog(@"Done");
}
else if (result.isFailure) {
NSLog(@"error: %@", result.error);
}
}];
}];
|