mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-03-19 07:54:26 +01:00
Created Saving and processing transactions (markdown)
174
Saving-and-processing-transactions.md
Normal file
174
Saving-and-processing-transactions.md
Normal file
@@ -0,0 +1,174 @@
|
||||
|
||||
To ensure deterministic state for objects in the read-only `NSManagedObjectContext`, CoreStore does not expose API's for updating and saving directly from the main context (or any other context for that matter.) Instead, you spawn *transactions* from `DataStack` instances:
|
||||
```swift
|
||||
let dataStack = self.dataStack
|
||||
dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
// make changes
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
or for the default stack, directly from `CoreStore`:
|
||||
```swift
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
// make changes
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
The `commit()` method saves the changes to the persistent store. If `commit()` is not called when the transaction block completes, all changes within the transaction is discarded.
|
||||
|
||||
The examples above use `beginAsynchronous(...)`, but there are actually 3 types of transactions at you disposal: *asynchronous*, *synchronous*, and *detached*.
|
||||
|
||||
**Asynchronous transactions** are spawned from `beginAsynchronous(...)`. This method returns immediately and executes its closure from a background serial queue:
|
||||
```swift
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
// make changes
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
`transaction`'s created from `beginAsynchronous(...)` are instances of `AsynchronousDataTransaction`.
|
||||
|
||||
**Synchronous transactions** are created from `beginSynchronous(...)`. While the syntax is similar to its asynchronous counterpart, `beginSynchronous(...)` waits for its transaction block to complete before returning:
|
||||
```swift
|
||||
CoreStore.beginSynchronous { (transaction) -> Void in
|
||||
// make changes
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
`transaction` above is a `SynchronousDataTransaction` instance.
|
||||
|
||||
Since `beginSynchronous(...)` technically blocks two queues (the caller's queue and the transaction's background queue), it is considered less safe as it's more prone to deadlock. Take special care that the closure does not block on any other external queues.
|
||||
|
||||
**Detached transactions** are special in that they do not enclose updates within a closure:
|
||||
```swift
|
||||
let transaction = CoreStore.beginDetached()
|
||||
// make changes
|
||||
downloadJSONWithCompletion({ (json) -> Void in
|
||||
|
||||
// make other changes
|
||||
transaction.commit()
|
||||
})
|
||||
downloadAnotherJSONWithCompletion({ (json) -> Void in
|
||||
|
||||
// make some other changes
|
||||
transaction.commit()
|
||||
})
|
||||
```
|
||||
This allows for non-contiguous updates. Do note that this flexibility comes with a price: you are now responsible for managing concurrency for the transaction. As uncle Ben said, "with great power comes great race conditions."
|
||||
|
||||
As the above example also shows, only detached transactions are allowed to call `commit()` multiple times; doing so with synchronous and asynchronous transactions will trigger an assert.
|
||||
|
||||
|
||||
You've seen how to create transactions, but we have yet to see how to make *creates*, *updates*, and *deletes*. The 3 types of transactions above are all subclasses of `BaseDataTransaction`, which implements the methods shown below.
|
||||
|
||||
### Creating objects
|
||||
The `create(...)` method accepts an `Into` clause which specifies the entity for the object you want to create:
|
||||
```swift
|
||||
let person = transaction.create(Into(MyPersonEntity))
|
||||
```
|
||||
While the syntax is straightforward, CoreStore does not just naively insert a new object. This single line does the following:
|
||||
- Checks that the entity type exists in any of the transaction's parent persistent store
|
||||
- If the entity belongs to only one persistent store, a new object is inserted into that store and returned from `create(...)`
|
||||
- If the entity does not belong to any store, an assert will be triggered. **This is a programmer error and should never occur in production code.**
|
||||
- If the entity belongs to multiple stores, an assert will be triggered. **This is also a programmer error and should never occur in production code.** Normally, with Core Data you can insert an object in this state but saving the `NSManagedObjectContext` will always fail. CoreStore checks this for you at creation time where it makes sense (not during save).
|
||||
|
||||
If the entity exists in multiple configurations, you need to provide the configuration name for the destination persistent store:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>("Config1"))
|
||||
|
||||
or if the persistent store is the auto-generated "Default" configuration, specify `nil`:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>(nil))
|
||||
|
||||
Note that if you do explicitly specify the configuration name, CoreStore will only try to insert the created object to that particular store and will fail if that store is not found; it will not fall back to any other store the entity belongs to.
|
||||
|
||||
### Updating objects
|
||||
|
||||
After creating an object from the transaction, you can simply update it's properties as normal:
|
||||
```swift
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
let person = transaction.create(Into(MyPersonEntity))
|
||||
person.name = "John Smith"
|
||||
person.age = 30
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
To update an existing object, fetch the object's instance from the transaction:
|
||||
```swift
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
let person = transaction.fetchOne(
|
||||
From(MyPersonEntity),
|
||||
Where("name", isEqualTo: "Jane Smith")
|
||||
)
|
||||
person.age = person.age + 1
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
*(For more about fetching, read [Fetching and querying](#fetch_query))*
|
||||
|
||||
**Do not update an instance that was not created/fetched from the transaction.** If you have a reference to the object already, use the transaction's `edit(...)` method to get an editable proxy instance for that object:
|
||||
```swift
|
||||
let jane: MyPersonEntity = // ...
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
// WRONG: jane.age = jane.age + 1
|
||||
// RIGHT:
|
||||
let jane = transaction.edit(jane) // using the same variable name protects us from misusing the non-transaction instance
|
||||
jane.age = jane.age + 1
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
This is also true when updating an object's relationships. Make sure that the object assigned to the relationship is also created/fetched from the transaction:
|
||||
```swift
|
||||
let jane: MyPersonEntity = // ...
|
||||
let john: MyPersonEntity = // ...
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
// WRONG: jane.friends = [john]
|
||||
// RIGHT:
|
||||
let jane = transaction.edit(jane)
|
||||
let john = transaction.edit(john)
|
||||
jane.friends = [john]
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
### Deleting objects
|
||||
|
||||
Deleting an object is simpler as you can tell a transaction to delete an object directly without fetching an editable proxy (CoreStore does that for you):
|
||||
```swift
|
||||
let john: MyPersonEntity = // ...
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
transaction.delete(john)
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
or several objects at once:
|
||||
```swift
|
||||
let john: MyPersonEntity = // ...
|
||||
let jane: MyPersonEntity = // ...
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
transaction.delete(john, jane)
|
||||
// transaction.delete([john, jane]) is also allowed
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
If you do not have references yet to the objects to be deleted, transactions have a `deleteAll(...)` method you can pass a query to:
|
||||
```swift
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
transaction.deleteAll(
|
||||
From(MyPersonEntity)
|
||||
Where("age > 30")
|
||||
)
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Contents
|
||||
- [[Architecture]]
|
||||
- [[Setting up]]
|
||||
- [[Saving and processing transactions]]
|
||||
- [[Fetching and querying]]
|
||||
- [[Logging and error handling]]
|
||||
- [[Observing changes and notifications]]
|
||||
Reference in New Issue
Block a user