-
+
@@ -14,15 +14,15 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
Contact
-
-
+
+
-* **Swift 5.0:** iOS 10+ / macOS 10.12+ / watchOS 3.0+ / tvOS 10.0+
-* Previously supported Swift versions: [Swift 3.2](https://github.com/JohnEstropia/CoreStore/tree/4.2.3), [Swift 4.2](https://github.com/JohnEstropia/CoreStore/tree/6.2.1)
+* **Swift 5.1:** iOS 10+ / macOS 10.12+ / watchOS 3.0+ / tvOS 10.0+
+* Previously supported Swift versions: [Swift 3.2](https://github.com/JohnEstropia/CoreStore/tree/4.2.3), [Swift 4.2](https://github.com/JohnEstropia/CoreStore/tree/6.2.1), [Swift 5.0](https://github.com/JohnEstropia/CoreStore/tree/6.3.2)
-Upgrading from CoreStore 5.x (min iOS 9) to 6.x (min iOS 10)? Check out the [new features](#features) and make sure to read the [Change logs](https://github.com/JohnEstropia/CoreStore/releases).
+Upgrading from CoreStore 6.x (swift 5.0) to 7.x (Swift 5.1)? Check out the [🆕 features](#features) and make sure to read the [Change logs](https://github.com/JohnEstropia/CoreStore/releases).
CoreStore is now part of the [Swift Source Compatibility projects](https://swift.org/source-compatibility/#current-list-of-projects).
@@ -33,17 +33,18 @@ CoreStore was (and is) heavily shaped by real-world needs of developing data-dep
### Features
+- 🆕**Backwards-portable DiffableDataSources implementation!** `UITableViews` and `UICollectionViews` now have a new ally: `ListPublisher`s provide diffable snapshots that make reloading animations very easy and very safe. Say goodbye to `UITableViews` and `UICollectionViews` reload errors!
- **💎Tight design around Swift’s code elegance and type safety.** CoreStore fully utilizes Swift's community-driven language features.
- **🚦Safer concurrency architecture.** CoreStore makes it hard to fall into common concurrency mistakes. The main `NSManagedObjectContext` is strictly read-only, while all updates are done through serial *transactions*. *(See [Saving and processing transactions](#saving-and-processing-transactions))*
- **🔍Clean fetching and querying API.** Fetching objects is easy, but querying for raw aggregates (`min`, `max`, etc.) and raw property values is now just as convenient. *(See [Fetching and querying](#fetching-and-querying))*
-- **🔭Type-safe, easy to configure observers.** You don't have to deal with the burden of setting up `NSFetchedResultsController`s and KVO. As an added bonus, `ListMonitor`s and `ObjectMonitor`s can have multiple observers. This means you can have multiple view controllers efficiently share a single resource! *(See [Observing changes and notifications](#observing-changes-and-notifications))*
+- **🔭Type-safe, easy to configure observers.** You don't have to deal with the burden of setting up `NSFetchedResultsController`s and KVO. As an added bonus, list and object observable types all support multiple observers. This means you can have multiple view controllers efficiently share a single resource! *(See [Observing changes and notifications](#observing-changes-and-notifications))*
- **📥Efficient importing utilities.** Map your entities once with their corresponding import source (JSON for example), and importing from *transactions* becomes elegant. Uniquing is also done with an efficient find-and-replace algorithm. *(See [Importing data](#importing-data))*
-- **🗑Say goodbye to *.xcdatamodeld* files!** The new `CoreStoreObject` is *the* replacement to `NSManagedObject`. `CoreStoreObject` subclasses can declare type-safe properties all in Swift code, no need to maintain separate resource files for the models. As bonus, these special properties support custom types, and can be used to create type-safe keypaths and queries. *(See [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects))*
+- **🗑Say goodbye to *.xcdatamodeld* files!** While CoreStore supports `NSManagedObject`s, it offers `CoreStoreObject` whose subclasses can declare type-safe properties all in Swift code without the need to maintain separate resource files for the models. As bonus, these special properties support custom types, and can be used to create type-safe keypaths and queries. *(See [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects))*
- **🔗Progressive migrations.** No need to think how to migrate from all previous model versions to your latest model. Just tell the `DataStack` the sequence of version strings (`MigrationChain`s) and CoreStore will automatically use progressive migrations when needed. *(See [Migrations](#migrations))*
- **Easier custom migrations.** Say goodbye to *.xcmappingmodel* files; CoreStore can now infer entity mappings when possible, while still allowing an easy way to write custom mappings. *(See [Migrations](#migrations))*
- **📝Plug-in your own logging framework.** Although a default logger is built-in, all logging, asserting, and error reporting can be funneled to `CoreStoreLogger` protocol implementations. *(See [Logging and error reporting](#logging-and-error-reporting))*
- **⛓Heavy support for multiple persistent stores per data stack.** CoreStore lets you manage separate stores in a single `DataStack`, just the way *.xcdatamodeld* configurations are designed to. CoreStore will also manage one stack by default, but you can create and manage as many as you need. *(See [Setting up](#setting-up))*
-- **🎯Free to name entities and their class names independently.** CoreStore gets around a restriction with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you can assign different names for the entities and their class names.
+- **🎯Free to name entities and their class names independently.** CoreStore gets around a restriction with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you can assign independent names for the entities and their class names.
- **📙Full Documentation.** No magic here; all public classes, functions, properties, etc. have detailed *Apple Docs*. This *README* also introduces a lot of concepts and explains a lot of CoreStore's behavior.
- **ℹ️Informative (and pretty) logs.** All CoreStore and Core Data-related types now have very informative and pretty print outputs! *(See [Logging and error reporting](#logging-and-error-reporting))*
- **🎗Objective-C support!** Is your project transitioning from Objective-C to Swift but still can't quite fully convert some huge classes to Swift yet? CoreStore adjusts to the ever-increasing Swift adoption. While still written in pure Swift, all CoreStore types have their corresponding Objective-C-visible "bridging classes". *(See [Objective-C support](#objective-c-support))*
@@ -87,8 +88,10 @@ CoreStore was (and is) heavily shaped by real-world needs of developing data-dep
- [Logging and error reporting](#logging-and-error-reporting)
- [Observing changes and notifications](#observing-changes-and-notifications)
- [Observe a single property](#observe-a-single-property)
- - [Observe a single object](#observe-a-single-object)
- - [Observe a list of objects](#observe-a-list-of-objects)
+ - 🆕[Observe a single object's updates](#observe-a-single-objects-updates)
+ - [Observe a single object's per-property updates](#observe-a-single-objects-per-property-updates)
+ - 🆕[Observe a diffable list](#observe-a-diffable-list)
+ - [Observe detailed list changes](#observe-detailed-list-changes)
- [Objective-C support](#objective-c-support)
- [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects)
- [`VersionLock`s](#versionlocks)
@@ -105,7 +108,7 @@ CoreStore was (and is) heavily shaped by real-world needs of developing data-dep
Setting-up with progressive migration support:
```swift
-CoreStore.defaultStack = DataStack(
+dataStack = DataStack(
xcodeModelName: "MyStore",
migrationChain: ["MyStore", "MyStoreV2", "MyStoreV3"]
)
@@ -113,7 +116,7 @@ CoreStore.defaultStack = DataStack(
Adding a store:
```swift
-CoreStore.addStorage(
+dataStack.addStorage(
SQLiteStore(fileName: "MyStore.sqlite"),
completion: { (result) -> Void in
// ...
@@ -123,7 +126,7 @@ CoreStore.addStorage(
Starting transactions:
```swift
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
let person = transaction.create(Into())
person.name = "John Smith"
@@ -140,12 +143,12 @@ CoreStore.perform(
Fetching objects (simple):
```swift
-let people = try CoreStore.fetchAll(From())
+let people = try dataStack.fetchAll(From())
```
Fetching objects (complex):
```swift
-let people = try CoreStore.fetchAll(
+let people = try dataStack.fetchAll(
From()
.where(\.age > 30),
.orderBy(.ascending(\.name), .descending(.\age)),
@@ -155,7 +158,7 @@ let people = try CoreStore.fetchAll(
Querying values:
```swift
-let maxAge = try CoreStore.queryValue(
+let maxAge = try dataStack.queryValue(
From()
.select(Int.self, .maximum(\.age))
)
@@ -174,15 +177,15 @@ If you are already familiar with the inner workings of CoreData, here is a mappi
| *Core Data* | *CoreStore* |
| --- | --- |
-| `NSManagedObjectModel` / `NSPersistentStoreCoordinator` (.xcdatamodeld file) | `DataStack` |
-| `NSPersistentStore` ("Configuration"s in the .xcdatamodeld file) | `StorageInterface` implementations (`InMemoryStore`, `SQLiteStore`, `ICloudStore`) |
+| `NSPersistentContainer` (.xcdatamodeld file) | `DataStack` |
+| `NSPersistentStoreDescription` ("Configuration"s in the .xcdatamodeld file) | `StorageInterface` implementations (`InMemoryStore`, `SQLiteStore`) |
| `NSManagedObjectContext` | `BaseDataTransaction` subclasses (`SynchronousDataTransaction`, `AsynchronousDataTransaction`, `UnsafeDataTransaction`) |
A lot of Core Data wrapper libraries set up their `NSManagedObjectContext`s this way:
-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:
+Nesting saves from child context to the root context ensures maximum data integrity between contexts without blocking the main queue. But in reality, merging contexts is still by far faster than saving contexts. CoreStore's `DataStack` takes the best of both worlds by treating the main `NSManagedObjectContext` as a read-only context (or "viewContext"), and only allows changes to be made within *transactions* on the child context:
@@ -193,10 +196,10 @@ This allows for a butter-smooth main thread, while still taking advantage of saf
## Setting up
The simplest way to initialize CoreStore is to add a default store to the default stack:
```swift
-try CoreStore.addStorageAndWait()
+try CoreStoreDefaults.dataStack.addStorageAndWait()
```
This one-liner does the following:
-- Triggers the lazy-initialization of `CoreStore.defaultStack` with a default `DataStack`
+- Triggers the lazy-initialization of `CoreStoreDefaults.dataStack` 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/"* 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
@@ -223,13 +226,13 @@ let migrationProgress = dataStack.addStorage(
}
)
-CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier access later on
+CoreStoreDefaults.dataStack = 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
>
-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:
+In our sample code above, note that you don't need to do the `CoreStoreDefaults.dataStack = 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
@@ -242,25 +245,7 @@ class MyViewController: UIViewController {
}
}
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())
+ let objects = self.dataStack.fetchAll(From())
print(objects)
}
}
@@ -268,12 +253,12 @@ class MyViewController: UIViewController {
> 💡By default, CoreStore will initialize `NSManagedObject`s from *.xcdatamodeld* files, but you can create models completely from source code using `CoreStoreObject`s and `CoreStoreSchema`. To use this feature, refer to [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects).
-Notice that in our previous examples, `addStorageAndWait(_:)` and `addStorage(_:completion:)` both accept either `InMemoryStore`, `SQLiteStore`, or `ICloudStore`. These implement the `StorageInterface` protocol.
+Notice that in our previous examples, `addStorageAndWait(_:)` and `addStorage(_:completion:)` both accept either `InMemoryStore`, or `SQLiteStore`. 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(
+try dataStack.addStorageAndWait(
InMemoryStore(
configuration: "Config2" // optional. Use entities from the "Config2" configuration in the .xcdatamodeld file
)
@@ -283,7 +268,7 @@ try CoreStore.addStorageAndWait(
### 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(
+let migrationProgress = dataStack.addStorage(
SQLiteStore(
fileName: "MyStore.sqlite",
configuration: "Config2", // optional. Use entities from the "Config2" configuration in the .xcdatamodeld file
@@ -297,7 +282,7 @@ Refer to the *SQLiteStore.swift* source documentation for detailed explanations
CoreStore can decide the default values for these properties, so `SQLiteStore`s can be initialized with no arguments:
```swift
-try CoreStore.addStorageAndWait(SQLiteStore())
+try dataStack.addStorageAndWait(SQLiteStore())
```
The file-related properties above are actually requirements of another protocol that `SQLiteStore` implements, the `LocalStorage` protocol:
@@ -316,16 +301,7 @@ If you have custom `NSIncrementalStore` or `NSAtomicStore` subclasses, you can i
## 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:
+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.
@@ -334,7 +310,7 @@ All the `DynamicSchema` for all model versions are then collected within a singl
**Multiple model versions grouped in a *.xcdatamodeld* file (Core Data standard method)**
```swift
-CoreStore.defaultStack = DataStack(
+CoreStoreDefaults.dataStack = DataStack(
xcodeModelName: "MyModel",
bundle: Bundle.main,
migrationChain: ["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4"]
@@ -354,7 +330,7 @@ class Person: CoreStoreObject {
// ...
}
-CoreStore.defaultStack = DataStack(
+CoreStoreDefaults.dataStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
@@ -391,7 +367,7 @@ let newSchema = CoreStoreSchema(
Entity("Person")
]
)
-CoreStore.defaultStack = DataStack(
+CoreStoreDefaults.dataStack = DataStack(
schemaHistory: SchemaHistory(
legacySchema + [newSchema],
migrationChain: ["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4", "V1"]
@@ -427,7 +403,7 @@ enum V1 {
}
}
-CoreStore.defaultStack = DataStack(
+CoreStoreDefaults.dataStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
@@ -450,7 +426,7 @@ CoreStore.defaultStack = DataStack(
### Starting migrations
-We have seen `addStorageAndWait(...)` used to initialize our persistent store. As the method name's *~AndWait* suffix suggests though, this method blocks so it should not do long tasks such as store migrations. In fact CoreStore will only attempt a synchronous **lightweight** migration if you explicitly provide the `.allowSynchronousLightweightMigration` option:
+We have seen `addStorageAndWait(...)` used to initialize our persistent store. As the method name's *~AndWait* suffix suggests though, this method blocks so it should not do long tasks such as data migrations. In fact CoreStore will only attempt a synchronous **lightweight** migration if you explicitly provide the `.allowSynchronousLightweightMigration` option:
```swift
try dataStack.addStorageAndWait(
SQLiteStore(
@@ -533,7 +509,7 @@ Sometimes migrations are huge and you may want prior information so your app cou
```swift
do {
let storage = SQLiteStorage(fileName: "MyStore.sqlite")
- let migrationTypes: [MigrationType] = try CoreStore.requiredMigrationsForStorage(storage)
+ let migrationTypes: [MigrationType] = try dataStack.requiredMigrationsForStorage(storage)
if migrationTypes.count > 1
|| (migrationTypes.filter { $0.isHeavyweightMigration }.count) > 0 {
// ... will migrate more than once. Show special waiting screen
@@ -544,7 +520,7 @@ do {
else {
// ... Do nothing
}
- CoreStore.addStorage(storage, completion: { /* ... */ })
+ dataStack.addStorage(storage, completion: { /* ... */ })
}
catch {
// ... either inspection of the store failed, or if no mapping model was found/inferred
@@ -560,11 +536,11 @@ Each `MigrationType` indicates the migration type for each step in the `Migratio
### Custom migrations
-Before CoreStore 4.0, the only way to implement custom migrations is to use Core Data's standard method: declaring entity mappings through *.xcmappingmodel* files. Starting CoreStore 4.0, new ways to declare migration mappings have been added:
+CoreStore offers several ways to declare migration mappings:
-- `InferredSchemaMappingProvider`: The default mapping provider which tries to infer model migration between two `DynamicSchema` versions either by searching all *.xcmappingmodel* files from `Bundle.allBundles`, or by relying on lightweight migration if possible.
-- `XcodeSchemaMappingProvider`: A mapping provider which loads entity mappings from *.xcmappingmodel* files in a specified `Bundle`.
- `CustomSchemaMappingProvider`: A mapping provider that infers mapping initially, but also accepts custom mappings for specified entities. This was added to support custom migrations with `CoreStoreObject`s as well, but may also be used with `NSManagedObject`s.
+- `XcodeSchemaMappingProvider`: A mapping provider which loads entity mappings from *.xcmappingmodel* files in a specified `Bundle`.
+- `InferredSchemaMappingProvider`: The default mapping provider which tries to infer model migration between two `DynamicSchema` versions either by searching all *.xcmappingmodel* files from `Bundle.allBundles`, or by relying on lightweight migration if possible.
These mapping providers conform to `SchemaMappingProvider` and can be passed to `SQLiteStore`'s initializer:
```swift
@@ -631,18 +607,7 @@ dataStack.perform(
}
)
```
-or for the default stack, directly from `CoreStore`:
-```swift
-CoreStore.perform(
- asynchronous: { (transaction) -> Void in
- // make changes
- },
- completion: { (result) -> Void in
- // ...
- }
-)
-```
-Transaction blocks automatically save changes once the block completes. To cancel and rollback a transaction, throw a `CoreStoreError.userCancelled` from inside the closure by calling `try transaction.cancel()`:
+Transaction closures automatically save changes once the closures completes. To cancel and rollback a transaction, throw a `CoreStoreError.userCancelled` from inside the closure by calling `try transaction.cancel()`:
```swift
dataStack.perform(
asynchronous: { (transaction) -> Void in
@@ -668,7 +633,7 @@ The examples above use `perform(asynchronous:...)`, but there are actually 3 typ
#### Asynchronous transactions
are spawned from `perform(asynchronous:...)`. This method returns immediately and executes its closure from a background serial queue. The return value for the closure is declared as a generic type, so any value returned from the closure can be passed to the completion result:
```swift
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Bool in
// make changes
return transaction.hasChanges
@@ -683,7 +648,7 @@ CoreStore.perform(
```
The success and failure can also be declared as separate handlers:
```swift
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Int in
// make changes
return transaction.delete(objects)
@@ -704,7 +669,7 @@ Transactions created from `perform(asynchronous:...)` are instances of `Asynchro
#### Synchronous transactions
are created from `perform(synchronous:...)`. While the syntax is similar to its asynchronous counterpart, `perform(synchronous:...)` waits for its transaction block to complete before returning:
```swift
-let hasChanges = CoreStore.perform(
+let hasChanges = dataStack.perform(
synchronous: { (transaction) -> Bool in
// make changes
return transaction.hasChanges
@@ -717,14 +682,14 @@ Since `perform(synchronous:...)` technically blocks two queues (the caller's que
By default, `perform(synchronous:...)` will wait for observers such as `ListMonitor`s to be notified before the method returns. This may cause deadlocks, especially if you are calling this from the main thread. To reduce this risk, you may try to set the `waitForAllObservers:` parameter to `false`. Doing so tells the `SynchronousDataTransaction` to block only until it completes saving. It will not wait for other context's to receive those changes. This reduces deadlock risk but may have surprising side-effects:
```swift
-CoreStore.perform(
+dataStack.perform(
synchronous: { (transaction) in
let person = transaction.create(Into())
person.name = "John"
},
waitForAllObservers: false
)
-let newPerson = CoreStore.fetchOne(From.where(\.name == "John"))
+let newPerson = dataStack.fetchOne(From.where(\.name == "John"))
// newPerson may be nil!
// The DataStack may have not yet received the update notification.
```
@@ -733,7 +698,7 @@ Due to this complicated nature of synchronous transactions, if your app has very
#### Unsafe transactions
are special in that they do not enclose updates within a closure:
```swift
-let transaction = CoreStore.beginUnsafe()
+let transaction = dataStack.beginUnsafe()
// make changes
downloadJSONWithCompletion({ (json) -> Void in
@@ -779,7 +744,7 @@ Note that if you do explicitly specify the configuration name, CoreStore will on
After creating an object from the transaction, you can simply update its properties as normal:
```swift
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
let person = transaction.create(Into())
person.name = "John Smith"
@@ -790,7 +755,7 @@ CoreStore.perform(
```
To update an existing object, fetch the object's instance from the transaction:
```swift
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
let person = try transaction.fetchOne(
From()
@@ -807,7 +772,7 @@ CoreStore.perform(
```swift
let jane: MyPersonEntity = // ...
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
// WRONG: jane.age = jane.age + 1
// RIGHT:
@@ -822,7 +787,7 @@ This is also true when updating an object's relationships. Make sure that the ob
let jane: MyPersonEntity = // ...
let john: MyPersonEntity = // ...
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
// WRONG: jane.friends = [john]
// RIGHT:
@@ -840,7 +805,7 @@ Deleting an object is simpler because you can tell a transaction to delete an ob
```swift
let john: MyPersonEntity = // ...
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
transaction.delete(john)
},
@@ -852,7 +817,7 @@ or several objects at once:
let john: MyPersonEntity = // ...
let jane: MyPersonEntity = // ...
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
try transaction.delete(john, jane)
// try transaction.delete([john, jane]) is also allowed
@@ -862,7 +827,7 @@ CoreStore.perform(
```
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.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
try transaction.deleteAll(
From()
@@ -879,7 +844,7 @@ Always remember that the `DataStack` and individual transactions manage differen
```swift
let jane: MyPersonEntity = // ...
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
let jane = transaction.edit(jane)!
jane.age = jane.age + 1
@@ -891,14 +856,14 @@ But `CoreStore`, `DataStack` and `BaseDataTransaction` have a very flexible `fet
```swift
let jane: MyPersonEntity = // ...
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> MyPersonEntity in
let jane = transaction.fetchExisting(jane)! // instance for transaction
jane.age = jane.age + 1
return jane
},
success: { (transactionJane) in
- let jane = CoreStore.fetchExisting(transactionJane)! // instance for DataStack
+ let jane = dataStack.fetchExisting(transactionJane)! // instance for DataStack
print(jane.age)
},
failure: { (error) in
@@ -910,7 +875,7 @@ CoreStore.perform(
```swift
var peopleIDs: [NSManagedObjectID] = // ...
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
let jane = try transaction.fetchOne(
From()
@@ -925,7 +890,7 @@ CoreStore.perform(
## 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. If you have for example a JSON dictionary, you may be extracting values as such:
+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. If you have a JSON dictionary for example, you may be extracting values as such:
```swift
let json: [String: Any] = // ...
person.name = json["name"] as? NSString
@@ -934,7 +899,7 @@ 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.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
let json: [String: Any] = // ...
try! transaction.importObject(
@@ -976,7 +941,7 @@ typealias ImportSource = [String: Any]
```
This lets us call `importObject(_:source:)` with any `[String: Any]` type as the argument to `source`:
```swift
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
let json: [String: Any] = // ...
try! transaction.importObject(
@@ -998,7 +963,7 @@ func didInsert(from source: ImportSource, in transaction: BaseDataTransaction) t
```
Transactions also let you import multiple objects at once using the `importObjects(_:sourceArray:)` method:
```swift
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
let jsonArray: [[String: Any]] = // ...
try! transaction.importObjects(
@@ -1025,7 +990,7 @@ func didInsert(from source: ImportSource, in transaction: BaseDataTransaction) t
```
Doing so can let you abandon an invalid transaction immediately:
```swift
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
let jsonArray: [[String: Any]] = // ...
@@ -1080,7 +1045,7 @@ For `ImportableUniqueObject`, the extraction and assignment of values should be
You can then create/update an object by calling a transaction's `importUniqueObject(...)` method:
```swift
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
let json: [String: Any] = // ...
try! transaction.importUniqueObject(
@@ -1095,7 +1060,7 @@ CoreStore.perform(
or multiple objects at once with the `importUniqueObjects(...)` method:
```swift
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) -> Void in
let jsonArray: [[String: AnyObject]] = // ...
try! transaction.importUniqueObjects(
@@ -1107,7 +1072,7 @@ CoreStore.perform(
completion: { _ in }
)
```
-As with `ImportableObject`, you can control wether to skip importing an object by implementing
+As with `ImportableObject`, you can control whether to skip importing an object by implementing
`shouldInsert(from:in:)` and `shouldUpdate(from:in:)`, or to cancel all objects by `throw`ing an error from the `uniqueID(from:in:)`, `didInsert(from:in:)` or `update(from:in:)` methods.
@@ -1125,17 +1090,17 @@ Before we dive in, be aware that CoreStore distinguishes between *fetching* and
#### `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 = try CoreStore.fetchAll(From())
+let people = try dataStack.fetchAll(From())
```
`people` in the example above will be of type `[MyPersonEntity]`. The `From()` 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 = try CoreStore.fetchAll(From("Config1")) // ignore objects in persistent stores other than the "Config1" configuration
+let people = try dataStack.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 = try CoreStore.fetchAll(From(nil))
+let person = try dataStack.fetchAll(From(nil))
```
Now we know how to use a `From` clause, let's move on to fetching and querying.
@@ -1155,11 +1120,11 @@ Each method's purpose is straightforward, but we need to understand how to set t
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 = try CoreStore.fetchAll(
+var people = try dataStack.fetchAll(
From(),
Where("%K > %d", "age", 30) // string format initializer
)
-people = try CoreStore.fetchAll(
+people = try dataStack.fetchAll(
From(),
Where(true) // boolean initializer
)
@@ -1167,21 +1132,21 @@ people = try CoreStore.fetchAll(
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(
+var people = dataStack.fetchAll(
From(),
Where(predicate) // predicate initializer
)
```
-⭐️Starting CoreStore 5.0, `Where` clauses became more type-safe and are now generic types. To avoid verbose repetition of the generic object type, fetch methods now support **Fetch Chain builders**. We can also use Swift's Smart KeyPaths as the `Where` clause expression:
+ `Where` clauses are generic types. To avoid verbose repetition of the generic object type, fetch methods support **Fetch Chain builders**. We can also use Swift's Smart KeyPaths as the `Where` clause expression:
```swift
-var people = try CoreStore.fetchAll(
+var people = try dataStack.fetchAll(
From()
.where(\.age > 30) // Type-safe!
)
```
`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 = try CoreStore.fetchAll(
+var people = try dataStack.fetchAll(
From()
.where(\.age > 30 && \.gender == "M")
)
@@ -1192,15 +1157,15 @@ If you do not provide a `Where` clause, all objects that belong to the specified
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 = try CoreStore.fetchAll(
+var mostValuablePeople = try dataStack.fetchAll(
From(),
OrderBy(.descending("rating"), .ascending("surname"))
)
```
As seen above, `OrderBy` accepts a list of `SortKey` enumeration values, which can be either `.ascending` or `.descending`.
-⭐️As with `Where` clauses, CoreStore 5.0 turned `OrderBy` clauses into generic types. To avoid verbose repetition of the generic object type, fetch methods now support **Fetch Chain builders**. We can also use Swift's Smart KeyPaths as the `OrderBy` clause expression:
+As with `Where` clauses, `OrderBy` clauses are also generic types. To avoid verbose repetition of the generic object type, fetch methods support **Fetch Chain builders**. We can also use Swift's Smart KeyPaths as the `OrderBy` clause expression:
```swift
-var people = try CoreStore.fetchAll(
+var people = try dataStack.fetchAll(
From()
.orderBy(.descending(\.rating), .ascending(\.surname)) // Type-safe!
)
@@ -1212,7 +1177,7 @@ var orderBy = OrderBy(.descending(\.rating))
if sortFromYoungest {
orderBy += OrderBy(.ascending(\.age))
}
-var mostValuablePeople = try CoreStore.fetchAll(
+var mostValuablePeople = try dataStack.fetchAll(
From(),
orderBy
)
@@ -1222,7 +1187,7 @@ var mostValuablePeople = try CoreStore.fetchAll(
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 = try CoreStore.fetchAll(
+var people = try dataStack.fetchAll(
From(),
Where("age > %d", 30),
OrderBy(.ascending("surname")),
@@ -1235,7 +1200,7 @@ var people = try CoreStore.fetchAll(
```
`Tweak` also supports **Fetch Chain builders**:
```swift
-var people = try CoreStore.fetchAll(
+var people = try dataStack.fetchAll(
From(),
.where(\.age > 30)
.orderBy(.ascending(\.surname))
@@ -1266,24 +1231,24 @@ Setting up the `From`, `Where`, `OrderBy`, and `Tweak` clauses is similar to how
The `Select` clause specifies the target attribute/aggregate key, as well as the expected return type:
```swift
-let johnsAge = try CoreStore.queryValue(
+let johnsAge = try dataStack.queryValue(
From(),
Select("age"),
- Where("name == %@", "John Smith")
+ 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(...)`, types that conform to `QueryableAttributeType` are allowed as the return type (and therefore as the generic type for `Select`).
For `queryAttributes(...)`, only `NSDictionary` is valid for `Select`, thus you are allowed to omit the generic type:
```swift
-let allAges = try CoreStore.queryAttributes(
+let allAges = try dataStack.queryAttributes(
From(),
Select("age")
)
```
-⭐️Starting CoreStore 5.0, query methods now support **Query Chain builders**. We can also use Swift's Smart KeyPaths to use in the expressions:
+query methods also support **Query Chain builders**. We can also use Swift's Smart KeyPaths to use in the expressions:
```swift
-let johnsAge = try CoreStore.queryValue(
+let johnsAge = try dataStack.queryValue(
From()
.select(\.age) // binds the result to Int
.where(\.name == "John Smith")
@@ -1298,7 +1263,7 @@ If you only need a value for a particular attribute, you can just specify the ke
- `.sum(...)`
```swift
-let oldestAge = try CoreStore.queryValue(
+let oldestAge = try dataStack.queryValue(
From(),
Select(.maximum("age"))
)
@@ -1306,7 +1271,7 @@ let oldestAge = try CoreStore.queryValue(
For `queryAttributes(...)` which returns an array of dictionaries, you can specify multiple attributes/aggregates to `Select`:
```swift
-let personJSON = try CoreStore.queryAttributes(
+let personJSON = try dataStack.queryAttributes(
From(),
Select("name", "age")
)
@@ -1326,7 +1291,7 @@ let personJSON = try CoreStore.queryAttributes(
```
You can also include an aggregate as well:
```swift
-let personJSON = try CoreStore.queryAttributes(
+let personJSON = try dataStack.queryAttributes(
From(),
Select("name", .count("friends"))
)
@@ -1346,7 +1311,7 @@ which returns:
```
The `"count(friends)"` key name was automatically used by CoreStore, but you can specify your own key alias if you need:
```swift
-let personJSON = try CoreStore.queryAttributes(
+let personJSON = try dataStack.queryAttributes(
From(),
Select("name", .count("friends", as: "friendsCount"))
)
@@ -1369,15 +1334,15 @@ which now returns:
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 = try CoreStore.queryAttributes(
+let personJSON = try dataStack.queryAttributes(
From(),
Select("age", .count("age", as: "count")),
GroupBy("age")
)
```
-⭐️Starting CoreStore 5.0, `GroupBy` clauses are now also generic types and now support **Query Chain builders**. We can also use Swift's Smart KeyPaths to use in the expressions:
+`GroupBy` clauses are also generic types and support **Query Chain builders**. We can also use Swift's Smart KeyPaths to use in the expressions:
```swift
-let personJSON = try CoreStore.queryAttributes(
+let personJSON = try dataStack.queryAttributes(
From()
.select(.attribute(\.age), .count(\.age, as: "count"))
.groupBy(\.age)
@@ -1407,9 +1372,9 @@ public protocol CoreStoreLogger {
func abort(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
}
```
-Implement this protocol with your custom class then pass the instance to `CoreStore.logger`:
+Implement this protocol with your custom class then pass the instance to `CoreStoreDefaults.logger`:
```swift
-CoreStore.logger = MyLogger()
+CoreStoreDefaults.logger = MyLogger()
```
Doing so channels all logging calls to your logger.
@@ -1419,8 +1384,7 @@ Take special care when implementing `CoreStoreLogger`'s `assert(...)` and `abort
- `assert(...)`: The behavior between `DEBUG` and release builds, or `-O` and `-Onone`, are all left to the implementers' responsibility. CoreStore calls `CoreStoreLogger.assert(...)` only for invalid but usually recoverable errors (for example, early validation failures that may cause an error thrown and handled somewhere else)
- `abort(...)`: This method is *the* last-chance for your app to *synchronously* log a fatal error within CoreStore. The app will be terminated right after this function is called (CoreStore calls `fatalError()` internally)
-Starting CoreStore 2.0, all CoreStore types now have very useful (and pretty formatted!) `print(...)` outputs.
-
+All CoreStore types have very useful (and pretty formatted!) `print(...)` outputs.
A couple of examples, `ListMonitor`:
@@ -1435,8 +1399,15 @@ These are all implemented with `CustomDebugStringConvertible.debugDescription`,
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`)
+| | 🆕[*ObjectPublisher*](#observe-a-single-objects-updates) | [*ObjectMonitor*](#observe-a-single-objects-per-property-updates) | 🆕[*ListPublisher*](#observe-a-diffable-list) | [*ListMonitor*](#observe-detailed-list-changes) |
+| --- | --- | --- | --- | --- |
+| *Number of objects* | 1 | 1 | N | N |
+| *Allows multiple observers* | ✅ | ✅ | ✅ | ✅ |
+| *Emits fine-grained changes* | ❌ | ✅ | ❌ | ✅ |
+| *Emits DiffableDataSource snapshots* | ✅ | ❌ | ✅ | ❌ |
+| *Delegate methods* | ❌ | ✅ | ❌ | ✅ |
+| *Closure callback* | ✅ | ❌ | ✅ | ❌ |
+| *SwiftUI support* | ✅ | ❌ | ✅ | ❌ |
### Observe a single property
To get notifications for single property changes in an object, there are two methods depending on the object's base class.
@@ -1457,10 +1428,35 @@ let observer = person.age.observe(options: [.new]) { (person, change)
For both methods, you will need to keep a reference to the returned `observer` for the duration of the observation.
+### Observe a single object's updates
-### Observe a single object
+Observers of an `ObjectPublisher` can receive notifications if any of the object's property changes. You can create an `ObjectPublisher` from the object directly:
+```swift
+let objectPublisher: ObjectPublisher = person.asPublisher(in: dataStack)
+```
+or by indexing a `ListPublisher`'s `ListSnapshot`:
+```swift
+let listPublisher: ListPublisher = // ...
+// ...
+let objectPublisher = listPublisher.snapshot[indexPath]
+```
+(See [`ListPublisher` examples](#observe-a-diffable-list) below)
-To observe an object itself as a whole, implement the `ObjectObserver` protocol and specify the `EntityType`:
+To receive notifications, call the `ObjectPublisher`'s `addObserve(...)` method passing the owner of the callback closure:
+```swift
+objectPublisher.addObserver(self) { [weak self] (objectPublisher) in
+ let snapshot: ObjectSnapshot = objectPublisher.snapshot
+ // handle changes
+}
+```
+Note that the owner instance will not be retained. You may call `ObjectPublisher.removeObserver(...)` explicitly to stop receiving notifications, but the `ObjectPublisher` also discontinues sending events to deallocated observers.
+
+The `ObjectSnapshot` returned from the `ObjectPublisher.snapshot` property returns a full-copy `struct` of all properties of the object. This is ideal for managing states as they are thread-safe and are not affected by further changes to the actual object. `ObjectPublisher` automatically updates its `snapshot` value to the latest state of the object.
+
+
+### Observe a single object's per-property updates
+
+If you need to track specifically which properties change in an object, implement the `ObjectObserver` protocol and specify the `EntityType`:
```swift
class MyViewController: UIViewController, ObjectObserver {
func objectMonitor(monitor: ObjectMonitor, willUpdateObject object: MyPersonEntity) {
@@ -1476,10 +1472,10 @@ class MyViewController: UIViewController, ObjectObserver {
}
}
```
-We then need to keep a `ObjectMonitor` instance and register our `ObjectObserver` as an observer:
+We then need to keep an `ObjectMonitor` instance and register our `ObjectObserver` as an observer:
```swift
let person: MyPersonEntity = // ...
-self.monitor = CoreStore.monitorObject(person)
+self.monitor = dataStack.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.
@@ -1488,8 +1484,51 @@ You can get `ObjectMonitor`'s object through its `object` property. If the objec
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`:
+### Observe a diffable list
+
+Observers of a `ListPublisher` can receive notifications whenever its fetched result set changes. You can create a `ListPublisher` by fetching from the `DataStack`:
+```swift
+let listPublisher = dataStack.listPublisher(
+ From()
+ .sectionBy(\.age") { "Age \($0)" } // sections are optional
+ .where(\.title == "Engineer")
+ .orderBy(.ascending(\.lastName))
+)
+```
+To receive notifications, call the `ListPublisher`'s `addObserve(...)` method passing the owner of the callback closure:
+```swift
+listPublisher.addObserver(self) { [weak self] (listPublisher) in
+ let snapshot: ListSnapshot = listPublisher.snapshot
+ // handle changes
+}
+```
+Note that the owner instance will not be retained. You may call `ListPublisher.removeObserver(...)` explicitly to stop receiving notifications, but the `ListPublisher` also discontinues sending events to deallocated observers.
+
+The `ListSnapshot` returned from the `ListPublisher.snapshot` property returns a full-copy `struct` of all sections and `NSManagedObject` items in the list. This is ideal for managing states as they are thread-safe and are not affected by further changes to the result set. `ListPublisher` automatically updates its `snapshot` value to the latest state of the fetch.
+
+Unlike `ListMonitor`s (See [`ListMonitor` examples](#observe-detailed-list-changes) below), a `ListPublisher` does not track detailed inserts, deletes, and moves. In return, a `ListPublisher` is a lot more lightweight and are designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s:
+```swift
+self.dataSource = DiffableDataSource.CollectionViewAdapter(
+ collectionView: self.collectionView,
+ dataStack: CoreStoreDefaults.dataStack,
+ cellProvider: { (collectionView, indexPath, person) in
+ let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PersonCell") as! PersonCell
+ cell.setPerson(person)
+ return cell
+ }
+)
+
+// ...
+
+listPublisher.addObserver(self) { [weak self] (listPublisher) in
+ self?.dataSource?.apply(
+ listPublisher.snapshot, animatingDifferences: true
+ )
+}
+```
+
+### Observe detailed list changes
+If you need to track each object's inserts, deletes, moves, and updates, implement one of the `ListObserver` protocols and specify the `EntityType`:
```swift
class MyViewController: UIViewController, ListObserver {
func listMonitorDidChange(monitor: ListMonitor) {
@@ -1513,10 +1552,10 @@ Including `ListObserver`, there are 3 observer protocols you can implement depen
- `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)
+ func listMonitor(_ monitor: ListMonitor, didInsertObject object: MyPersonEntity, toIndexPath indexPath: IndexPath)
+ func listMonitor(_ monitor: ListMonitor, didDeleteObject object: MyPersonEntity, fromIndexPath indexPath: IndexPath)
+ func listMonitor(_ monitor: ListMonitor, didUpdateObject object: MyPersonEntity, atIndexPath indexPath: IndexPath)
+ func listMonitor(_ monitor: ListMonitor, didMoveObject object: MyPersonEntity, fromIndexPath: IndexPath, toIndexPath: IndexPath)
```
- `ListSectionObserver`: in addition to `ListObjectObserver` methods, also lets you handle section inserts and deletes:
```swift
@@ -1526,7 +1565,7 @@ Including `ListObserver`, there are 3 observer protocols you can implement depen
We then need to create a `ListMonitor` instance and register our `ListObserver` as an observer:
```swift
-self.monitor = CoreStore.monitorList(
+self.monitor = dataStack.monitorList(
From()
.where(\.age > 30)
.orderBy(.ascending(\.name))
@@ -1545,7 +1584,7 @@ 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(
+self.monitor = dataStack.monitorSectionedList(
From()
.sectionBy(\.age)
.where(\.gender == "M")
@@ -1557,7 +1596,7 @@ A list controller created this way will group the objects by the attribute key i
The `SectionBy` clause can also be passed a closure to transform the section name into a displayable string:
```swift
-self.monitor = CoreStore.monitorSectionedList(
+self.monitor = dataStack.monitorSectionedList(
From()
.sectionBy(\.age) { (sectionName) -> String? in
"\(sectionName) years old"
@@ -1573,9 +1612,9 @@ func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -
}
```
-To access the objects of a sectioned list, use an `NSIndexPath` or a tuple:
+To access the objects of a sectioned list, use an `IndexPath` or a tuple:
```swift
-let indexPath = NSIndexPath(forRow: 2, inSection: 1)
+let indexPath = IndexPath(row: 2, section: 1)
let person1 = self.monitor[indexPath]
let person2 = self.monitor[1, 2]
// person1 and person2 are the same object
@@ -1590,7 +1629,7 @@ All CoreStore types are still written in pure Swift, but most core types have Ob
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) in
// ...
},
@@ -1631,7 +1670,7 @@ All of these `CS`-prefixed bridging classes have very similar usage to the exist
For example, you may have a new, modern Swift class that holds a `ListMonitor`:
```swift
class MyViewController: UIViewController {
- let monitor = CoreStore.monitorList(From(), ...)
+ let monitor = dataStack.monitorList(From(), ...)
// ...
}
```
@@ -1645,7 +1684,7 @@ Now let's say you have a legacy Objective-C class that previously uses `NSFetche
When you need to instantiate this class from Swift, you just call `bridgeToObjectiveC`:
```swift
class MyViewController: UIViewController {
- let monitor = CoreStore.monitorList(From(), ...)
+ let monitor = dataStack.monitorList(From(), ...)
func showOldController() {
let controller = MYOldViewController(monitor: self.monitor.bridgeToObjectiveC)
self.presentViewController(controller, animated: true, completion: nil)
@@ -1719,7 +1758,7 @@ Also note how `Relationship`s are linked statically with the `inverse:` argument
To tell the `DataStack` about these types, add all `CoreStoreObject`s' entities to a `CoreStoreSchema`:
```swift
-CoreStore.defaultStack = DataStack(
+CoreStoreDefaults.dataStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
@@ -1729,13 +1768,13 @@ CoreStore.defaultStack = DataStack(
]
)
)
-CoreStore.addStorage(/* ... */)
+CoreStoreDefaults.dataStack.addStorage(/* ... */)
```
And that's all CoreStore needs to build the model; **we don't need *.xcdatamodeld* files anymore.**
These special properties' values can be accessed or mutated using `.value`:
```swift
-CoreStore.perform(
+dataStack.perform(
asynchronous: { (transaction) in
let dog: Dog = transaction.fetchOne(From())!
// ...
@@ -1755,7 +1794,7 @@ let keyPath: String = Dog.keyPath { $0.nickname }
```
as well as `Where` and `OrderBy` clauses
```swift
-let puppies = try CoreStore.fetchAll(
+let puppies = try dataStack.fetchAll(
From()
.where(\.age < 1)
.orderBy(.ascending(\.age))
@@ -1790,7 +1829,7 @@ CoreStoreSchema(
```
You can also get this hash after the `DataStack` has been fully set up by printing to the console:
```swift
-print(CoreStore.defaultStack.modelSchema.printCoreStoreSchema())
+print(CoreStoreDefaults.dataStack.modelSchema.printCoreStoreSchema())
```
Once the version lock is set, any changes in the properties or to the model will trigger an assertion failure similar to this:
@@ -1801,8 +1840,8 @@ Once the version lock is set, any changes in the properties or to the model will
# Installation
- Requires:
- iOS 10 SDK and above
- - Swift 5.0 (Xcode 10+)
- - For previous Swift versions: [Swift 3.2](https://github.com/JohnEstropia/CoreStore/tree/4.2.3), [Swift 4.2](https://github.com/JohnEstropia/CoreStore/tree/6.2.1)
+ - Swift 5.1 (Xcode 11+)
+ - For previous Swift versions: [Swift 3.2](https://github.com/JohnEstropia/CoreStore/tree/4.2.3), [Swift 4.2](https://github.com/JohnEstropia/CoreStore/tree/6.2.1), [Swift 5.0](https://github.com/JohnEstropia/CoreStore/tree/6.3.2)
- Dependencies:
- *None*
- Other notes:
@@ -1811,7 +1850,7 @@ Once the version lock is set, any changes in the properties or to the model will
### Install with CocoaPods
In your `Podfile`, add
```
-pod 'CoreStore', '~> 6.3'
+pod 'CoreStore', '~> 7.0'
```
and run
```
@@ -1822,7 +1861,7 @@ This installs CoreStore as a framework. Declare `import CoreStore` in your swift
### Install with Carthage
In your `Cartfile`, add
```
-github "JohnEstropia/CoreStore" >= 6.3.0
+github "JohnEstropia/CoreStore" >= 7.0.0
```
and run
```
@@ -1833,7 +1872,7 @@ This installs CoreStore as a framework. Declare `import CoreStore` in your swift
#### Install with Swift Package Manager:
```swift
dependencies: [
- .package(url: "https://github.com/JohnEstropia/CoreStore.git", from: "6.3.0"))
+ .package(url: "https://github.com/JohnEstropia/CoreStore.git", from: "7.0.0"))
]
```
Declare `import CoreStore` in your swift file to use the library.
diff --git a/Sources/AsynchronousDataTransaction.swift b/Sources/AsynchronousDataTransaction.swift
index fd89666..874f098 100644
--- a/Sources/AsynchronousDataTransaction.swift
+++ b/Sources/AsynchronousDataTransaction.swift
@@ -30,7 +30,7 @@ import CoreData
// MARK: - AsynchronousDataTransaction
/**
- The `AsynchronousDataTransaction` provides an interface for `DynamicObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.perform(asynchronous:...)`, or from `CoreStore.perform(synchronous:...)`.
+ The `AsynchronousDataTransaction` provides an interface for `DynamicObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.perform(asynchronous:...)`.
*/
public final class AsynchronousDataTransaction: BaseDataTransaction {
@@ -66,7 +66,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
- parameter into: the `Into` clause indicating the destination `NSManagedObject` or `CoreStoreObject` entity type and the destination configuration
- returns: a new `NSManagedObject` or `CoreStoreObject` instance of the specified entity type.
*/
- public override func create(_ into: Into) -> D {
+ public override func create(_ into: Into) -> O {
Internals.assert(
!self.isCommitted,
@@ -82,7 +82,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
- parameter object: the `NSManagedObject` or `CoreStoreObject` to be edited
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
*/
- public override func edit(_ object: D?) -> D? {
+ public override func edit(_ object: O?) -> O? {
Internals.assert(
!self.isCommitted,
@@ -99,7 +99,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
- parameter objectID: the `NSManagedObjectID` for the object to be edited
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
*/
- public override func edit(_ into: Into, _ objectID: NSManagedObjectID) -> D? {
+ public override func edit(_ into: Into, _ objectID: NSManagedObjectID) -> O? {
Internals.assert(
!self.isCommitted,
@@ -108,51 +108,50 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
return super.edit(into, objectID)
}
-
+
/**
- Deletes a specified `NSManagedObject` or `CoreStoreObject`.
-
- - parameter object: the `NSManagedObject` or `CoreStoreObject` to be deleted
+ Deletes the objects with the specified `NSManagedObjectID`s.
+
+ - parameter objectIDs: the `NSManagedObjectID`s of the objects to delete
*/
- public override func delete(_ object: D?) {
-
- Internals.assert(
- !self.isCommitted,
- "Attempted to delete an entity of type \(Internals.typeName(object)) from an already committed \(Internals.typeName(self))."
- )
-
- super.delete(object)
- }
-
- /**
- Deletes the specified `DynamicObject`s.
-
- - parameter object1: the `DynamicObject` to be deleted
- - parameter object2: another `DynamicObject` to be deleted
- - parameter objects: other `DynamicObject`s to be deleted
- */
- public override func delete(_ object1: D?, _ object2: D?, _ objects: D?...) {
-
+ public override func delete(objectIDs: S) where S.Iterator.Element: NSManagedObjectID {
+
Internals.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(Internals.typeName(self))."
)
-
- super.delete(([object1, object2] + objects).compactMap { $0 })
+
+ super.delete(objectIDs: objectIDs)
}
-
+
/**
- Deletes the specified `DynamicObject`s.
-
- - parameter objects: the `DynamicObject`s to be deleted
+ Deletes the specified `NSManagedObject`s or `CoreStoreObject`s represented by series of `ObjectRepresentation`s.
+
+ - parameter object: the `ObjectRepresentation` representing an `NSManagedObject` or `CoreStoreObject` to be deleted
+ - parameter objects: other `ObjectRepresentation`s representing `NSManagedObject`s or `CoreStoreObject`s to be deleted
*/
- public override func delete(_ objects: S) where S.Iterator.Element: DynamicObject {
-
+ public override func delete(_ object: O?, _ objects: O?...) {
+
Internals.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(Internals.typeName(self))."
)
-
+
+ super.delete(([object] + objects).compactMap { $0 })
+ }
+
+ /**
+ Deletes the specified `NSManagedObject`s or `CoreStoreObject`s represented by an `ObjectRepresenation`.
+
+ - parameter objects: the `ObjectRepresenation`s representing `NSManagedObject`s or `CoreStoreObject`s to be deleted
+ */
+ public override func delete(_ objects: S) where S.Iterator.Element: ObjectRepresentation {
+
+ Internals.assert(
+ !self.isCommitted,
+ "Attempted to delete an entities from an already committed \(Internals.typeName(self))."
+ )
+
super.delete(objects)
}
diff --git a/Sources/AttributeProtocol.swift b/Sources/AttributeProtocol.swift
index 0890817..df70185 100644
--- a/Sources/AttributeProtocol.swift
+++ b/Sources/AttributeProtocol.swift
@@ -29,11 +29,10 @@ import CoreData
// MARK: - AttributeProtocol
-internal protocol AttributeProtocol: AnyObject {
+internal protocol AttributeProtocol: PropertyProtocol {
static var attributeType: NSAttributeType { get }
- var keyPath: KeyPathString { get }
var isOptional: Bool { get }
var isTransient: Bool { get }
var allowsExternalBinaryDataStorage: Bool { get }
@@ -44,5 +43,5 @@ internal protocol AttributeProtocol: AnyObject {
var rawObject: CoreStoreManagedObject? { get set }
var getter: CoreStoreManagedObject.CustomGetter? { get }
var setter: CoreStoreManagedObject.CustomSetter? { get }
- var valueForSnapshot: Any { get }
+ var valueForSnapshot: Any? { get }
}
diff --git a/Sources/BaseDataTransaction+Importing.swift b/Sources/BaseDataTransaction+Importing.swift
index c18e78f..3d6e742 100644
--- a/Sources/BaseDataTransaction+Importing.swift
+++ b/Sources/BaseDataTransaction+Importing.swift
@@ -39,9 +39,9 @@ extension BaseDataTransaction {
- throws: an `Error` thrown from any of the `ImportableObject` methods
- returns: the created `ImportableObject` instance, or `nil` if the import was ignored
*/
- public func importObject(
- _ into: Into,
- source: D.ImportSource) throws -> D? {
+ public func importObject(
+ _ into: Into,
+ source: O.ImportSource) throws -> O? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -65,13 +65,13 @@ extension BaseDataTransaction {
/**
Updates an existing `ImportableObject` by importing values from the specified import source.
- - parameter object: the `NSManagedObject` to update
+ - parameter object: the `ImportableObject` to update
- parameter source: the object to import values from
- throws: an `Error` thrown from any of the `ImportableObject` methods
*/
- public func importObject(
- _ object: D,
- source: D.ImportSource) throws {
+ public func importObject(
+ _ object: O,
+ source: O.ImportSource) throws {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -97,9 +97,9 @@ extension BaseDataTransaction {
- throws: an `Error` thrown from any of the `ImportableObject` methods
- returns: the array of created `ImportableObject` instances
*/
- public func importObjects(
- _ into: Into,
- sourceArray: S) throws -> [D] where S.Iterator.Element == D.ImportSource {
+ public func importObjects(
+ _ into: Into,
+ sourceArray: S) throws -> [O] where S.Iterator.Element == O.ImportSource {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -108,7 +108,7 @@ extension BaseDataTransaction {
return try autoreleasepool {
- return try sourceArray.compactMap { (source) -> D? in
+ return try sourceArray.compactMap { (source) -> O? in
let entityType = into.entityClass
guard entityType.shouldInsert(from: source, in: self) else {
@@ -133,9 +133,9 @@ extension BaseDataTransaction {
- throws: an `Error` thrown from any of the `ImportableUniqueObject` methods
- returns: the created/updated `ImportableUniqueObject` instance, or `nil` if the import was ignored
*/
- public func importUniqueObject(
- _ into: Into,
- source: D.ImportSource) throws -> D? {
+ public func importUniqueObject(
+ _ into: Into,
+ source: O.ImportSource) throws -> O? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -151,7 +151,7 @@ extension BaseDataTransaction {
return nil
}
- if let object = try self.fetchOne(From(entityType), Where(uniqueIDKeyPath, isEqualTo: uniqueIDValue)) {
+ if let object = try self.fetchOne(From(entityType), Where(uniqueIDKeyPath, isEqualTo: uniqueIDValue)) {
guard entityType.shouldUpdate(from: source, in: self) else {
@@ -185,10 +185,10 @@ extension BaseDataTransaction {
- throws: an `Error` thrown from any of the `ImportableUniqueObject` methods
- returns: the array of created/updated `ImportableUniqueObject` instances
*/
- public func importUniqueObjects(
- _ into: Into,
+ public func importUniqueObjects(
+ _ into: Into,
sourceArray: S,
- preProcess: @escaping (_ mapping: [D.UniqueIDType: D.ImportSource]) throws -> [D.UniqueIDType: D.ImportSource] = { $0 }) throws -> [D] where S.Iterator.Element == D.ImportSource {
+ preProcess: @escaping (_ mapping: [O.UniqueIDType: O.ImportSource]) throws -> [O.UniqueIDType: O.ImportSource] = { $0 }) throws -> [O] where S.Iterator.Element == O.ImportSource {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -198,10 +198,10 @@ extension BaseDataTransaction {
return try autoreleasepool {
let entityType = into.entityClass
- var importSourceByID = Dictionary()
+ var importSourceByID = Dictionary()
let sortedIDs = try autoreleasepool {
- return try sourceArray.compactMap { (source) -> D.UniqueIDType? in
+ return try sourceArray.compactMap { (source) -> O.UniqueIDType? in
guard let uniqueIDValue = try entityType.uniqueID(from: source, in: self) else {
@@ -214,13 +214,13 @@ extension BaseDataTransaction {
importSourceByID = try autoreleasepool { try preProcess(importSourceByID) }
- var existingObjectsByID = Dictionary()
+ var existingObjectsByID = Dictionary()
try self
- .fetchAll(From(entityType), Where(entityType.uniqueIDKeyPath, isMemberOf: sortedIDs))
+ .fetchAll(From(entityType), Where(entityType.uniqueIDKeyPath, isMemberOf: sortedIDs))
.forEach { existingObjectsByID[$0.uniqueIDValue] = $0 }
- var processedObjectIDs = Set()
- var result = [D]()
+ var processedObjectIDs = Set()
+ var result = [O]()
for objectID in sortedIDs where !processedObjectIDs.contains(objectID) {
diff --git a/Sources/BaseDataTransaction+Querying.swift b/Sources/BaseDataTransaction+Querying.swift
index 7485e76..7345acf 100644
--- a/Sources/BaseDataTransaction+Querying.swift
+++ b/Sources/BaseDataTransaction+Querying.swift
@@ -39,7 +39,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the number of `DynamicObject`s deleted
*/
@discardableResult
- public func deleteAll(_ from: From, _ deleteClauses: DeleteClause...) throws -> Int {
+ public func deleteAll(_ from: From, _ deleteClauses: DeleteClause...) throws -> Int {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -56,7 +56,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the number of `DynamicObject`s deleted
*/
@discardableResult
- public func deleteAll(_ from: From, _ deleteClauses: [DeleteClause]) throws -> Int {
+ public func deleteAll(_ from: From, _ deleteClauses: [DeleteClause]) throws -> Int {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -93,7 +93,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- parameter object: a reference to the object created/fetched outside the transaction
- returns: the `DynamicObject` instance if the object exists in the transaction, or `nil` if not found.
*/
- public func fetchExisting(_ object: D) -> D? {
+ public func fetchExisting(_ object: O) -> O? {
return self.context.fetchExisting(object)
}
@@ -104,7 +104,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- parameter objectID: the `NSManagedObjectID` for the object
- returns: the `DynamicObject` instance if the object exists in the transaction, or `nil` if not found.
*/
- public func fetchExisting(_ objectID: NSManagedObjectID) -> D? {
+ public func fetchExisting(_ objectID: NSManagedObjectID) -> O? {
return self.context.fetchExisting(objectID)
}
@@ -115,7 +115,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- parameter objects: an array of `DynamicObject`s created/fetched outside the transaction
- returns: the `DynamicObject` array for objects that exists in the transaction
*/
- public func fetchExisting(_ objects: S) -> [D] where S.Iterator.Element == D {
+ public func fetchExisting(_ objects: S) -> [O] where S.Iterator.Element == O {
return self.context.fetchExisting(objects)
}
@@ -126,7 +126,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- parameter objectIDs: the `NSManagedObjectID` array for the objects
- returns: the `DynamicObject` array for objects that exists in the transaction
*/
- public func fetchExisting(_ objectIDs: S) -> [D] where S.Iterator.Element == NSManagedObjectID {
+ public func fetchExisting(_ objectIDs: S) -> [O] where S.Iterator.Element == NSManagedObjectID {
return self.context.fetchExisting(objectIDs)
}
@@ -139,7 +139,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the first `DynamicObject` instance that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
- public func fetchOne(_ from: From, _ fetchClauses: FetchClause...) throws -> D? {
+ public func fetchOne(_ from: From, _ fetchClauses: FetchClause...) throws -> O? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -156,7 +156,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the first `DynamicObject` instance that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
- public func fetchOne(_ from: From, _ fetchClauses: [FetchClause]) throws -> D? {
+ public func fetchOne(_ from: From, _ fetchClauses: [FetchClause]) throws -> O? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -195,7 +195,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: all `DynamicObject` instances that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
- public func fetchAll(_ from: From, _ fetchClauses: FetchClause...) throws -> [D] {
+ public func fetchAll(_ from: From, _ fetchClauses: FetchClause...) throws -> [O] {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -212,7 +212,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: all `DynamicObject` instances that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
- public func fetchAll(_ from: From, _ fetchClauses: [FetchClause]) throws -> [D] {
+ public func fetchAll(_ from: From, _ fetchClauses: [FetchClause]) throws -> [O] {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -251,7 +251,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the number of `DynamicObject`s that satisfy the specified `FetchClause`s
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
- public func fetchCount(_ from: From, _ fetchClauses: FetchClause...) throws -> Int {
+ public func fetchCount(_ from: From, _ fetchClauses: FetchClause...) throws -> Int {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -268,7 +268,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the number of `DynamicObject`s that satisfy the specified `FetchClause`s
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
- public func fetchCount(_ from: From, _ fetchClauses: [FetchClause]) throws -> Int {
+ public func fetchCount(_ from: From, _ fetchClauses: [FetchClause]) throws -> Int {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -307,7 +307,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
- public func fetchObjectID(_ from: From, _ fetchClauses: FetchClause...) throws -> NSManagedObjectID? {
+ public func fetchObjectID(_ from: From, _ fetchClauses: FetchClause...) throws -> NSManagedObjectID? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -324,7 +324,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
- public func fetchObjectID(_ from: From, _ fetchClauses: [FetchClause]) throws -> NSManagedObjectID? {
+ public func fetchObjectID(_ from: From, _ fetchClauses: [FetchClause]) throws -> NSManagedObjectID? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -363,7 +363,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
- public func fetchObjectIDs(_ from: From, _ fetchClauses: FetchClause...) throws -> [NSManagedObjectID] {
+ public func fetchObjectIDs(_ from: From, _ fetchClauses: FetchClause...) throws -> [NSManagedObjectID] {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -380,7 +380,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
- public func fetchObjectIDs(_ from: From, _ fetchClauses: [FetchClause]) throws -> [NSManagedObjectID] {
+ public func fetchObjectIDs(_ from: From, _ fetchClauses: [FetchClause]) throws -> [NSManagedObjectID] {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -425,7 +425,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the result of the the query, or `nil` if no match was found. The type of the return value is specified by the generic type of the `Select` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
- public func queryValue(_ from: From, _ selectClause: Select, _ queryClauses: QueryClause...) throws -> U? {
+ public func queryValue(_ from: From, _ selectClause: Select, _ queryClauses: QueryClause...) throws -> U? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -445,7 +445,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the result of the the query, or `nil` if no match was found. The type of the return value is specified by the generic type of the `Select` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
- public func queryValue(_ from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) throws -> U? {
+ public func queryValue(_ from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) throws -> U? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -489,7 +489,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
- public func queryAttributes(_ from: From, _ selectClause: Select, _ queryClauses: QueryClause...) throws -> [[String: Any]] {
+ public func queryAttributes(_ from: From, _ selectClause: Select, _ queryClauses: QueryClause...) throws -> [[String: Any]] {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -509,7 +509,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
- public func queryAttributes(_ from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) throws -> [[String: Any]] {
+ public func queryAttributes(_ from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) throws -> [[String: Any]] {
Internals.assert(
self.isRunningInAllowedQueue(),
diff --git a/Sources/BaseDataTransaction.swift b/Sources/BaseDataTransaction.swift
index 501617a..59fe52f 100644
--- a/Sources/BaseDataTransaction.swift
+++ b/Sources/BaseDataTransaction.swift
@@ -50,7 +50,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter into: the `Into` clause indicating the destination `NSManagedObject` or `CoreStoreObject` entity type and the destination configuration
- returns: a new `NSManagedObject` or `CoreStoreObject` instance of the specified entity type.
*/
- public func create(_ into: Into) -> D {
+ public func create(_ into: Into) -> O {
let entityClass = into.entityClass
Internals.assert(
@@ -121,7 +121,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter object: the `NSManagedObject` or `CoreStoreObject` type to be edited
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
*/
- public func edit(_ object: D?) -> D? {
+ public func edit(_ object: O?) -> O? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -141,7 +141,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter objectID: the `NSManagedObjectID` for the object to be edited
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
*/
- public func edit(_ into: Into, _ objectID: NSManagedObjectID) -> D? {
+ public func edit(_ into: Into, _ objectID: NSManagedObjectID) -> O? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -154,49 +154,56 @@ public /*abstract*/ class BaseDataTransaction {
)
return self.fetchExisting(objectID)
}
-
+
/**
- Deletes a specified `NSManagedObject` or `CoreStoreObject`.
-
- - parameter object: the `NSManagedObject` or `CoreStoreObject` to be deleted
+ Deletes the objects with the specified `NSManagedObjectID`s.
+
+ - parameter objectIDs: the `NSManagedObjectID`s of the objects to delete
*/
- public func delete(_ object: D?) {
-
+ public func delete(objectIDs: S) where S.Iterator.Element: NSManagedObjectID {
+
Internals.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete an entity outside its designated queue."
)
let context = self.context
- object
- .flatMap(context.fetchExisting)
- .flatMap({ context.delete($0.cs_toRaw()) })
+ objectIDs.forEach {
+
+ context.fetchExisting($0).map(context.delete(_:))
+ }
}
/**
- Deletes the specified `NSManagedObject`s or `CoreStoreObject`s.
+ Deletes the specified `NSManagedObject`s or `CoreStoreObject`s represented by series of `ObjectRepresentation`s.
- - parameter object1: the `NSManagedObject` or `CoreStoreObject` to be deleted
- - parameter object2: another `NSManagedObject` or `CoreStoreObject` to be deleted
- - parameter objects: other `NSManagedObject`s or `CoreStoreObject`s to be deleted
+ - parameter object: the `ObjectRepresentation` representing an `NSManagedObject` or `CoreStoreObject` to be deleted
+ - parameter objects: other `ObjectRepresentation`s representing `NSManagedObject`s or `CoreStoreObject`s to be deleted
*/
- public func delete(_ object1: D?, _ object2: D?, _ objects: D?...) {
+ public func delete(_ object: O?, _ objects: O?...) {
- self.delete(([object1, object2] + objects).compactMap { $0 })
+ Internals.assert(
+ self.isRunningInAllowedQueue(),
+ "Attempted to delete an entity outside its designated queue."
+ )
+ self.delete(([object] + objects).compactMap { $0 })
}
/**
- Deletes the specified `NSManagedObject`s or `CoreStoreObject`s.
+ Deletes the specified `NSManagedObject`s or `CoreStoreObject`s represented by an `ObjectRepresenation`.
- - parameter objects: the `NSManagedObject`s or `CoreStoreObject`s to be deleted
+ - parameter objects: the `ObjectRepresenation`s representing `NSManagedObject`s or `CoreStoreObject`s to be deleted
*/
- public func delete(_ objects: S) where S.Iterator.Element: DynamicObject {
+ public func delete(_ objects: S) where S.Iterator.Element: ObjectRepresentation {
Internals.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete entities outside their designated queue."
)
let context = self.context
- objects.forEach { context.fetchExisting($0).flatMap({ context.delete($0.cs_toRaw()) }) }
+ objects.forEach {
+
+ $0.asEditable(in: self).map({ context.delete($0.cs_toRaw()) })
+ }
}
/**
@@ -217,10 +224,10 @@ public /*abstract*/ class BaseDataTransaction {
/**
Returns `true` if the object has any property values changed. This method should not be called after the `commit()` method was called.
- - parameter entity: the `DynamicObject` instance
+ - parameter object: the `DynamicObject` instance
- returns: `true` if the object has any property values changed.
*/
- public func objectHasPersistentChangedValues(_ entity: D) -> Bool {
+ public func objectHasPersistentChangedValues(_ object: O) -> Bool {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -230,7 +237,7 @@ public /*abstract*/ class BaseDataTransaction {
!self.isCommitted,
"Attempted to access inserted objects from an already committed \(Internals.typeName(self))."
)
- return entity.cs_toRaw().hasPersistentChangedValues
+ return object.cs_toRaw().hasPersistentChangedValues
}
/**
@@ -239,7 +246,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter entity: the `DynamicObject` subclass to filter
- returns: a `Set` of pending `DynamicObject`s of the specified type that were inserted to the transaction.
*/
- public func insertedObjects(_ entity: D.Type) -> Set {
+ public func insertedObjects(_ entity: O.Type) -> Set {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -276,7 +283,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter entity: the `DynamicObject` subclass to filter
- returns: a `Set` of pending `NSManagedObjectID`s of the specified type that were inserted to the transaction.
*/
- public func insertedObjectIDs(_ entity: D.Type) -> Set {
+ public func insertedObjectIDs(_ entity: O.Type) -> Set {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -295,7 +302,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter entity: the `DynamicObject` subclass to filter
- returns: a `Set` of pending `DynamicObject`s of the specified type that were updated in the transaction.
*/
- public func updatedObjects(_ entity: D.Type) -> Set {
+ public func updatedObjects(_ entity: O.Type) -> Set {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -332,7 +339,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter entity: the `DynamicObject` subclass to filter
- returns: a `Set` of pending `NSManagedObjectID`s of the specified type that were updated in the transaction.
*/
- public func updatedObjectIDs(_ entity: D.Type) -> Set {
+ public func updatedObjectIDs(_ entity: O.Type) -> Set {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -351,7 +358,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter entity: the `DynamicObject` subclass to filter
- returns: a `Set` of pending `DynamicObject`s of the specified type that were deleted from the transaction.
*/
- public func deletedObjects(_ entity: D.Type) -> Set {
+ public func deletedObjects(_ entity: O.Type) -> Set {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -389,7 +396,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter entity: the `DynamicObject` subclass to filter
- returns: a `Set` of pending `NSManagedObjectID`s of the specified type that were deleted from the transaction.
*/
- public func deletedObjectIDs(_ entity: D.Type) -> Set {
+ public func deletedObjectIDs(_ entity: O.Type) -> Set {
Internals.assert(
self.isRunningInAllowedQueue(),
diff --git a/Sources/CSCoreStore+Setup.swift b/Sources/CSCoreStore+Setup.swift
index 0028a94..6665205 100644
--- a/Sources/CSCoreStore+Setup.swift
+++ b/Sources/CSCoreStore+Setup.swift
@@ -38,7 +38,7 @@ extension CSCoreStore {
@objc
public static var modelVersion: String {
- return CoreStore.modelVersion
+ return self.defaultStack.modelVersion
}
/**
@@ -47,7 +47,7 @@ extension CSCoreStore {
@objc
public static func entityTypesByNameForType(_ type: NSManagedObject.Type) -> [EntityName: NSManagedObject.Type] {
- return CoreStore.entityTypesByName(for: type)
+ return self.defaultStack.bridgeToSwift.entityTypesByName(for: type)
}
/**
@@ -56,7 +56,7 @@ extension CSCoreStore {
@objc
public static func entityDescriptionForClass(_ type: NSManagedObject.Type) -> NSEntityDescription? {
- return CoreStore.entityDescription(for: type)
+ return self.defaultStack.bridgeToSwift.entityDescription(for: type)
}
/**
diff --git a/Sources/CSCoreStore+Transaction.swift b/Sources/CSCoreStore+Transaction.swift
index bda8282..53a4f9f 100644
--- a/Sources/CSCoreStore+Transaction.swift
+++ b/Sources/CSCoreStore+Transaction.swift
@@ -65,7 +65,7 @@ extension CSCoreStore {
return bridge {
- CoreStore.beginUnsafe()
+ self.defaultStack.bridgeToSwift.beginUnsafe()
}
}
@@ -80,7 +80,7 @@ extension CSCoreStore {
return bridge {
- CoreStore.beginUnsafe(supportsUndo: supportsUndo)
+ self.defaultStack.bridgeToSwift.beginUnsafe(supportsUndo: supportsUndo)
}
}
@@ -90,6 +90,6 @@ extension CSCoreStore {
@objc
public static func refreshAndMergeAllObjects() {
- CoreStore.refreshAndMergeAllObjects()
+ self.defaultStack.refreshAndMergeAllObjects()
}
}
diff --git a/Sources/CSCoreStore.swift b/Sources/CSCoreStore.swift
index ef5709b..c07e675 100644
--- a/Sources/CSCoreStore.swift
+++ b/Sources/CSCoreStore.swift
@@ -46,8 +46,8 @@ public final class CSCoreStore: NSObject {
@objc
public static var defaultStack: CSDataStack {
- get { return Shared.defaultStack.bridgeToObjectiveC }
- set { Shared.defaultStack = newValue.bridgeToSwift }
+ get { return CoreStoreDefaults.dataStack.bridgeToObjectiveC }
+ set { CoreStoreDefaults.dataStack = newValue.bridgeToSwift }
}
diff --git a/Sources/CSDataStack+Observing.swift b/Sources/CSDataStack+Observing.swift
index f7f9c4a..7f97735 100644
--- a/Sources/CSDataStack+Observing.swift
+++ b/Sources/CSDataStack+Observing.swift
@@ -36,7 +36,7 @@ extension CSDataStack {
Creates a `CSObjectMonitor` for the specified `NSManagedObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`.
- parameter object: the `NSManagedObject` to observe changes from
- - returns: a `ObjectMonitor` that monitors changes to `object`
+ - returns: an `ObjectMonitor` that monitors changes to `object`
*/
@objc
public func monitorObject(_ object: NSManagedObject) -> CSObjectMonitor {
diff --git a/Sources/CSFrom.swift b/Sources/CSFrom.swift
index ab42aa8..ced12dd 100644
--- a/Sources/CSFrom.swift
+++ b/Sources/CSFrom.swift
@@ -145,7 +145,7 @@ public final class CSFrom: NSObject {
public let bridgeToSwift: From
- public init