mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-03-20 16:44:08 +01:00
updated read
This commit is contained in:
442
README.md
442
README.md
@@ -30,16 +30,16 @@ CoreStore.addSQLiteStore("MyStore.sqlite")
|
|||||||
Simple transactions:
|
Simple transactions:
|
||||||
```swift
|
```swift
|
||||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||||
let object = transaction.create(Into(MyEntity))
|
let object = transaction.create(Into(MyEntity))
|
||||||
object.entityID = 1
|
object.entityID = 1
|
||||||
object.name = "test entity"
|
object.name = "test entity"
|
||||||
|
|
||||||
transaction.commit { (result) -> Void in
|
transaction.commit { (result) -> Void in
|
||||||
switch result {
|
switch result {
|
||||||
case .Success(let hasChanges): println("success!")
|
case .Success(let hasChanges): println("success!")
|
||||||
case .Failure(let error): println(error)
|
case .Failure(let error): println(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -49,20 +49,20 @@ let objects = CoreStore.fetchAll(From(MyEntity))
|
|||||||
```
|
```
|
||||||
```swift
|
```swift
|
||||||
let objects = CoreStore.fetchAll(
|
let objects = CoreStore.fetchAll(
|
||||||
From(MyEntity),
|
From(MyEntity),
|
||||||
Where("entityID", isEqualTo: 1),
|
Where("entityID", isEqualTo: 1),
|
||||||
OrderBy(.Ascending("entityID"), .Descending("name")),
|
OrderBy(.Ascending("entityID"), .Descending("name")),
|
||||||
Tweak { (fetchRequest) -> Void in
|
Tweak { (fetchRequest) -> Void in
|
||||||
fetchRequest.includesPendingChanges = true
|
fetchRequest.includesPendingChanges = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Simple queries:
|
Simple queries:
|
||||||
```swift
|
```swift
|
||||||
let count = CoreStore.queryValue(
|
let count = CoreStore.queryValue(
|
||||||
From(MyEntity),
|
From(MyEntity),
|
||||||
Select<Int>(.Count("entityID"))
|
Select<Int>(.Count("entityID"))
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -118,20 +118,20 @@ let dataStack = DataStack(modelName: "MyModel") // loads from the "MyModel.xcdat
|
|||||||
|
|
||||||
switch dataStack.addInMemoryStore(configuration: "Config1") { // creates an in-memory store with entities from the "Config1" configuration in the .xcdatamodeld file
|
switch dataStack.addInMemoryStore(configuration: "Config1") { // creates an in-memory store with entities from the "Config1" configuration in the .xcdatamodeld file
|
||||||
case .Success(let persistentStore): // persistentStore is an NSPersistentStore instance
|
case .Success(let persistentStore): // persistentStore is an NSPersistentStore instance
|
||||||
println("Successfully created an in-memory store: \(persistentStore)"
|
println("Successfully created an in-memory store: \(persistentStore)"
|
||||||
case .Failure(let error): // error is an NSError instance
|
case .Failure(let error): // error is an NSError instance
|
||||||
println("Failed creating an in-memory store with error: \(error.description)"
|
println("Failed creating an in-memory store with error: \(error.description)"
|
||||||
}
|
}
|
||||||
|
|
||||||
switch dataStack.addSQLiteStore(
|
switch dataStack.addSQLiteStore(
|
||||||
fileURL: sqliteFileURL, // set the target file URL for the sqlite file
|
fileURL: sqliteFileURL, // set the target file URL for the sqlite file
|
||||||
configuration: "Config2", // use entities from the "Config2" configuration in the .xcdatamodeld file
|
configuration: "Config2", // use entities from the "Config2" configuration in the .xcdatamodeld file
|
||||||
automigrating: true, // automatically run lightweight migrations or entity policy migrations when needed
|
automigrating: true, // automatically run lightweight migrations or entity policy migrations when needed
|
||||||
resetStoreOnMigrationFailure: true) { // delete and recreate the sqlite file when migration conflicts occur (useful when debugging)
|
resetStoreOnMigrationFailure: true) { // delete and recreate the sqlite file when migration conflicts occur (useful when debugging)
|
||||||
case .Success(let persistentStore): // persistentStore is an NSPersistentStore instance
|
case .Success(let persistentStore): // persistentStore is an NSPersistentStore instance
|
||||||
println("Successfully created an sqlite store: \(persistentStore)"
|
println("Successfully created an sqlite store: \(persistentStore)"
|
||||||
case .Failure(let error): // error is an NSError instance
|
case .Failure(let error): // error is an NSError instance
|
||||||
println("Failed creating an sqlite store with error: \(error.description)"
|
println("Failed creating an sqlite store with error: \(error.description)"
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier access later on
|
CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier access later on
|
||||||
@@ -140,28 +140,28 @@ CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier
|
|||||||
Note that you dont need to do the `CoreStore.defaultStack = dataStack` line. You can just as well hold a stack like below and call all methods directly from the `DataStack` instance:
|
Note that you dont need to do the `CoreStore.defaultStack = dataStack` line. You can just as well hold a stack like below and call all methods directly from the `DataStack` instance:
|
||||||
```swift
|
```swift
|
||||||
class MyViewController: UIViewController {
|
class MyViewController: UIViewController {
|
||||||
let dataStack = DataStack(modelName: "MyModel")
|
let dataStack = DataStack(modelName: "MyModel")
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
self.dataStack.addSQLiteStore()
|
self.dataStack.addSQLiteStore()
|
||||||
}
|
}
|
||||||
func methodToBeCalledLaterOn() {
|
func methodToBeCalledLaterOn() {
|
||||||
let objects = self.dataStack.fetchAll(From(MyEntity))
|
let objects = self.dataStack.fetchAll(From(MyEntity))
|
||||||
println(objects)
|
println(objects)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
The difference is when you set the stack as the `CoreStore.defaultStack`, you can call the stack's methods directly from `CoreStore` itself:
|
The difference is when you set the stack as the `CoreStore.defaultStack`, you can call the stack's methods directly from `CoreStore` itself:
|
||||||
```swift
|
```swift
|
||||||
class MyViewController: UIViewController {
|
class MyViewController: UIViewController {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
CoreStore.addSQLiteStore()
|
CoreStore.addSQLiteStore()
|
||||||
}
|
}
|
||||||
func methodToBeCalledLaterOn() {
|
func methodToBeCalledLaterOn() {
|
||||||
let objects = CoreStore.fetchAll(From(MyEntity))
|
let objects = CoreStore.fetchAll(From(MyEntity))
|
||||||
println(objects)
|
println(objects)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -172,15 +172,15 @@ To ensure deterministic state for objects in the read-only `NSManagedObjectConte
|
|||||||
```swift
|
```swift
|
||||||
let dataStack = self.dataStack
|
let dataStack = self.dataStack
|
||||||
dataStack.beginAsynchronous { (transaction) -> Void in
|
dataStack.beginAsynchronous { (transaction) -> Void in
|
||||||
// make changes
|
// make changes
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
or for the default stack, directly from `CoreStore`:
|
or for the default stack, directly from `CoreStore`:
|
||||||
```swift
|
```swift
|
||||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||||
// make changes
|
// make changes
|
||||||
transaction.commit()
|
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 `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.
|
||||||
@@ -190,8 +190,8 @@ The examples above use `beginAsynchronous(...)`, but there are actually 3 types
|
|||||||
**Asynchronous transactions** are spawned from `beginAsynchronous(...)`. This method returns immediately and executes its closure from a background serial queue:
|
**Asynchronous transactions** are spawned from `beginAsynchronous(...)`. This method returns immediately and executes its closure from a background serial queue:
|
||||||
```swift
|
```swift
|
||||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||||
// make changes
|
// make changes
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Transactions created from `beginAsynchronous(...)` are instances of `AsynchronousDataTransaction`.
|
Transactions created from `beginAsynchronous(...)` are instances of `AsynchronousDataTransaction`.
|
||||||
@@ -199,8 +199,8 @@ Transactions created from `beginAsynchronous(...)` are instances of `Asynchronou
|
|||||||
**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:
|
**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
|
```swift
|
||||||
CoreStore.beginSynchronous { (transaction) -> Void in
|
CoreStore.beginSynchronous { (transaction) -> Void in
|
||||||
// make changes
|
// make changes
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
`transaction` above is a `SynchronousDataTransaction` instance.
|
`transaction` above is a `SynchronousDataTransaction` instance.
|
||||||
@@ -213,13 +213,13 @@ let transaction = CoreStore.beginDetached()
|
|||||||
// make changes
|
// make changes
|
||||||
downloadJSONWithCompletion({ (json) -> Void in
|
downloadJSONWithCompletion({ (json) -> Void in
|
||||||
|
|
||||||
// make other changes
|
// make other changes
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
})
|
})
|
||||||
downloadAnotherJSONWithCompletion({ (json) -> Void in
|
downloadAnotherJSONWithCompletion({ (json) -> Void in
|
||||||
|
|
||||||
// make some other changes
|
// make some other changes
|
||||||
transaction.commit()
|
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."
|
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."
|
||||||
@@ -242,11 +242,11 @@ While the syntax is straightforward, CoreStore does not just naively insert a ne
|
|||||||
|
|
||||||
If the entity exists in multiple configurations, you need to provide the configuration name for the destination persistent store:
|
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"))
|
let person = transaction.create(Into<MyPersonEntity>("Config1"))
|
||||||
|
|
||||||
or if the persistent store is the auto-generated "Default" configuration, specify `nil`:
|
or if the persistent store is the auto-generated "Default" configuration, specify `nil`:
|
||||||
|
|
||||||
let person = transaction.create(Into<MyPersonEntity>(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.
|
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.
|
||||||
|
|
||||||
@@ -255,21 +255,21 @@ 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:
|
After creating an object from the transaction, you can simply update its properties as normal:
|
||||||
```swift
|
```swift
|
||||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||||
let person = transaction.create(Into(MyPersonEntity))
|
let person = transaction.create(Into(MyPersonEntity))
|
||||||
person.name = "John Smith"
|
person.name = "John Smith"
|
||||||
person.age = 30
|
person.age = 30
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
To update an existing object, fetch the object's instance from the transaction:
|
To update an existing object, fetch the object's instance from the transaction:
|
||||||
```swift
|
```swift
|
||||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||||
let person = transaction.fetchOne(
|
let person = transaction.fetchOne(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
Where("name", isEqualTo: "Jane Smith")
|
Where("name", isEqualTo: "Jane Smith")
|
||||||
)
|
)
|
||||||
person.age = person.age + 1
|
person.age = person.age + 1
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
*(For more about fetching, read [Fetching and querying](#fetch_query))*
|
*(For more about fetching, read [Fetching and querying](#fetch_query))*
|
||||||
@@ -279,11 +279,11 @@ transaction.commit()
|
|||||||
let jane: MyPersonEntity = // ...
|
let jane: MyPersonEntity = // ...
|
||||||
|
|
||||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||||
// WRONG: jane.age = jane.age + 1
|
// WRONG: jane.age = jane.age + 1
|
||||||
// RIGHT:
|
// RIGHT:
|
||||||
let jane = transaction.edit(jane) // using the same variable name protects us from misusing the non-transaction instance
|
let jane = transaction.edit(jane) // using the same variable name protects us from misusing the non-transaction instance
|
||||||
jane.age = jane.age + 1
|
jane.age = jane.age + 1
|
||||||
transaction.commit()
|
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:
|
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:
|
||||||
@@ -292,12 +292,12 @@ let jane: MyPersonEntity = // ...
|
|||||||
let john: MyPersonEntity = // ...
|
let john: MyPersonEntity = // ...
|
||||||
|
|
||||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||||
// WRONG: jane.friends = [john]
|
// WRONG: jane.friends = [john]
|
||||||
// RIGHT:
|
// RIGHT:
|
||||||
let jane = transaction.edit(jane)
|
let jane = transaction.edit(jane)
|
||||||
let john = transaction.edit(john)
|
let john = transaction.edit(john)
|
||||||
jane.friends = [john]
|
jane.friends = [john]
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### Deleting objects
|
### Deleting objects
|
||||||
@@ -307,8 +307,8 @@ Deleting an object is simpler as you can tell a transaction to delete an object
|
|||||||
let john: MyPersonEntity = // ...
|
let john: MyPersonEntity = // ...
|
||||||
|
|
||||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||||
transaction.delete(john)
|
transaction.delete(john)
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
or several objects at once:
|
or several objects at once:
|
||||||
@@ -317,31 +317,31 @@ let john: MyPersonEntity = // ...
|
|||||||
let jane: MyPersonEntity = // ...
|
let jane: MyPersonEntity = // ...
|
||||||
|
|
||||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||||
transaction.delete(john, jane)
|
transaction.delete(john, jane)
|
||||||
// transaction.delete([john, jane]) is also allowed
|
// transaction.delete([john, jane]) is also allowed
|
||||||
transaction.commit()
|
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:
|
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
|
```swift
|
||||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||||
transaction.deleteAll(
|
transaction.deleteAll(
|
||||||
From(MyPersonEntity)
|
From(MyPersonEntity)
|
||||||
Where("age > 30")
|
Where("age > 30")
|
||||||
)
|
)
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
## <a id="fetch_query"></a>Fetching and querying
|
## <a id="fetch_query"></a>Fetching and querying
|
||||||
Before we dive in, be aware that CoreStore distinguishes between *fetching* and *querying*:
|
Before we dive in, be aware that CoreStore distinguishes between *fetching* and *querying*:
|
||||||
- A *fetch* executes searches from a specific *transaction* or *data stack*. This means fetches can include pending objects (i.e. before a transaction calls on `commit()`.) Use fetches when:
|
- A *fetch* executes searches from a specific *transaction* or *data stack*. This means fetches can include pending objects (i.e. before a transaction calls on `commit()`.) Use fetches when:
|
||||||
- results need to be `NSManagedObject` instances
|
- results need to be `NSManagedObject` instances
|
||||||
- unsaved objects should be included in the search (though fetches can be configured to exclude unsaved ones)
|
- unsaved objects should be included in the search (though fetches can be configured to exclude unsaved ones)
|
||||||
- A *query* pulls data straight from the persistent store. This means faster searches when computing aggregates such as *count*, *min*, *max*, etc. Use queries when:
|
- A *query* pulls data straight from the persistent store. This means faster searches when computing aggregates such as *count*, *min*, *max*, etc. Use queries when:
|
||||||
- you need to compute aggregate functions (see below for a list of supported functions)
|
- you need to compute aggregate functions (see below for a list of supported functions)
|
||||||
- results can be raw values like `NSString`s, `NSNumber`s, `Int`s, `NSDate`s, an `NSDictionary` of key-values, etc.
|
- results can be raw values like `NSString`s, `NSNumber`s, `Int`s, `NSDate`s, an `NSDictionary` of key-values, etc.
|
||||||
- only specific attribute keys need to be included in the results
|
- only specific attribute keys need to be included in the results
|
||||||
- unsaved objects should be ignored
|
- unsaved objects should be ignored
|
||||||
|
|
||||||
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:
|
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
|
```swift
|
||||||
@@ -377,27 +377,27 @@ 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):
|
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
|
```swift
|
||||||
var people = CoreStore.fetchAll(
|
var people = CoreStore.fetchAll(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
Where("%K > %d", "age", 30) // string format initializer
|
Where("%K > %d", "age", 30) // string format initializer
|
||||||
)
|
)
|
||||||
people = CoreStore.fetchAll(
|
people = CoreStore.fetchAll(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
Where(true) // boolean initializer
|
Where(true) // boolean initializer
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
If you do have an existing `NSPredicate` instance already, you can pass that to `Where` as well:
|
If you do have an existing `NSPredicate` instance already, you can pass that to `Where` as well:
|
||||||
```swift
|
```swift
|
||||||
let predicate = NSPredicate(...)
|
let predicate = NSPredicate(...)
|
||||||
var people = CoreStore.fetchAll(
|
var people = CoreStore.fetchAll(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
Where(predicate) // predicate initializer
|
Where(predicate) // predicate initializer
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
`Where` clauses also implement the `&&`, `||`, and `!` logic operators, so you can provide logical conditions without writing too much `AND`, `OR`, and `NOT` strings in the conditions:
|
`Where` clauses also implement the `&&`, `||`, and `!` logic operators, so you can provide logical conditions without writing too much `AND`, `OR`, and `NOT` strings in the conditions:
|
||||||
```swift
|
```swift
|
||||||
var people = CoreStore.fetchAll(
|
var people = CoreStore.fetchAll(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
Where("age > %d", 30) && Where("gender == %@", "M")
|
Where("age > %d", 30) && Where("gender == %@", "M")
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
If you do not provide a `Where` clause, all objects that belong to the specified `From` will be returned.
|
If you do not provide a `Where` clause, all objects that belong to the specified `From` will be returned.
|
||||||
@@ -407,8 +407,8 @@ 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.
|
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
|
```swift
|
||||||
var mostValuablePeople = CoreStore.fetchAll(
|
var mostValuablePeople = CoreStore.fetchAll(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
OrderBy(.Descending("rating"), .Ascending("surname"))
|
OrderBy(.Descending("rating"), .Ascending("surname"))
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
As seen above, `OrderBy` accepts a list of `SortKey` enumeration values, which can be either `.Ascending` or `.Descending`. The associated value for the `SortKey` enumeration is the attribute key string.
|
As seen above, `OrderBy` accepts a list of `SortKey` enumeration values, which can be either `.Ascending` or `.Descending`. The associated value for the `SortKey` enumeration is the attribute key string.
|
||||||
@@ -417,11 +417,11 @@ You can use the `+` and `+=` operator to append `OrderBy`s together. This is use
|
|||||||
```swift
|
```swift
|
||||||
var orderBy = OrderBy(.Descending("rating"))
|
var orderBy = OrderBy(.Descending("rating"))
|
||||||
if sortFromYoungest {
|
if sortFromYoungest {
|
||||||
orderBy += OrderBy(.Ascending("age"))
|
orderBy += OrderBy(.Ascending("age"))
|
||||||
}
|
}
|
||||||
var mostValuablePeople = CoreStore.fetchAll(
|
var mostValuablePeople = CoreStore.fetchAll(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
orderBy
|
orderBy
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -430,14 +430,14 @@ orderBy
|
|||||||
The `Tweak` clause lets you, well, *tweak* the fetch (or query). `Tweak` exposes the `NSFetchRequest` in a closure where you can make changes to its properties:
|
The `Tweak` clause lets you, well, *tweak* the fetch (or query). `Tweak` exposes the `NSFetchRequest` in a closure where you can make changes to its properties:
|
||||||
```swift
|
```swift
|
||||||
var people = CoreStore.fetchAll(
|
var people = CoreStore.fetchAll(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
Where("age > %d", 30),
|
Where("age > %d", 30),
|
||||||
OrderBy(.Ascending("surname")),
|
OrderBy(.Ascending("surname")),
|
||||||
Tweak { (fetchRequest) -> Void in
|
Tweak { (fetchRequest) -> Void in
|
||||||
fetchRequest.includesPendingChanges = false
|
fetchRequest.includesPendingChanges = false
|
||||||
fetchRequest.returnsObjectsAsFaults = false
|
fetchRequest.returnsObjectsAsFaults = false
|
||||||
fetchRequest.includesSubentities = false
|
fetchRequest.includesSubentities = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
The clauses are evaluated the order they appear in the fetch/query, so you typically need to set `Tweak` as the last clause.
|
The clauses are evaluated the order they appear in the fetch/query, so you typically need to set `Tweak` as the last clause.
|
||||||
@@ -460,9 +460,9 @@ Setting up the `From`, `Where`, `OrderBy`, and `Tweak` clauses is similar to how
|
|||||||
The `Select<T>` clause specifies the target attribute/aggregate key and the return type:
|
The `Select<T>` clause specifies the target attribute/aggregate key and the return type:
|
||||||
```swift
|
```swift
|
||||||
let johnsAge = CoreStore.queryValue(
|
let johnsAge = CoreStore.queryValue(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
Select<Int>("age"),
|
Select<Int>("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<Int>` generic type. For `queryValue(...)`, the following are allowed as the return type (and as the generic type for `Select<T>`):
|
The example above queries the "age" property for the first object that matches the `Where` condition. `johnsAge` will be bound to type `Int?`, as indicated by the `Select<Int>` generic type. For `queryValue(...)`, the following are allowed as the return type (and as the generic type for `Select<T>`):
|
||||||
@@ -485,8 +485,8 @@ The example above queries the "age" property for the first object that matches t
|
|||||||
For `queryAttributes(...)`, only `NSDictionary` is valid for `Select`, thus you are allowed omit the generic type:
|
For `queryAttributes(...)`, only `NSDictionary` is valid for `Select`, thus you are allowed omit the generic type:
|
||||||
```swift
|
```swift
|
||||||
let allAges = CoreStore.queryAttributes(
|
let allAges = CoreStore.queryAttributes(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
Select("age")
|
Select("age")
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -501,69 +501,69 @@ If you only need a value for a particular attribute, you can just specify the ke
|
|||||||
|
|
||||||
```swift
|
```swift
|
||||||
let oldestAge = CoreStore.queryValue(
|
let oldestAge = CoreStore.queryValue(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
Select<Int>(.Maximum("age"))
|
Select<Int>(.Maximum("age"))
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
For `queryAttributes(...)` which returns an array of dictionaries, you can specify multiple attributes/aggregates to `Select`:
|
For `queryAttributes(...)` which returns an array of dictionaries, you can specify multiple attributes/aggregates to `Select`:
|
||||||
```swift
|
```swift
|
||||||
let personJSON = CoreStore.queryAttributes(
|
let personJSON = CoreStore.queryAttributes(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
Select("name", "age")
|
Select("name", "age")
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
`personJSON` will then have the value:
|
`personJSON` will then have the value:
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
"name": "John Smith",
|
"name": "John Smith",
|
||||||
"age": 30
|
"age": 30
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"name": "Jane Doe",
|
"name": "Jane Doe",
|
||||||
"age": 22
|
"age": 22
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
You can also include an aggregate as well:
|
You can also include an aggregate as well:
|
||||||
```swift
|
```swift
|
||||||
let personJSON = CoreStore.queryAttributes(
|
let personJSON = CoreStore.queryAttributes(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
Select("name", .Count("friends"))
|
Select("name", .Count("friends"))
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
which returns:
|
which returns:
|
||||||
```swift
|
```swift
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
"name": "John Smith",
|
"name": "John Smith",
|
||||||
"count(friends)": 42
|
"count(friends)": 42
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"name": "Jane Doe",
|
"name": "Jane Doe",
|
||||||
"count(friends)": 231
|
"count(friends)": 231
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
The `"count(friends)"` key name was automatically used by CoreStore, but you can specify your own key alias if you need:
|
The `"count(friends)"` key name was automatically used by CoreStore, but you can specify your own key alias if you need:
|
||||||
```swift
|
```swift
|
||||||
let personJSON = CoreStore.queryAttributes(
|
let personJSON = CoreStore.queryAttributes(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
Select("name", .Count("friends", As: "friendsCount"))
|
Select("name", .Count("friends", As: "friendsCount"))
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
which now returns:
|
which now returns:
|
||||||
```swift
|
```swift
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
"name": "John Smith",
|
"name": "John Smith",
|
||||||
"friendsCount": 42
|
"friendsCount": 42
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"name": "Jane Doe",
|
"name": "Jane Doe",
|
||||||
"friendsCount": 231
|
"friendsCount": 231
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -572,22 +572,22 @@ which now returns:
|
|||||||
The `GroupBy` clause lets you group results by a specified attribute/aggregate. This is only useful only for `queryAttributes(...)` since `queryValue(...)` just returns the first value anyway.
|
The `GroupBy` clause lets you group results by a specified attribute/aggregate. This is only useful only for `queryAttributes(...)` since `queryValue(...)` just returns the first value anyway.
|
||||||
```swift
|
```swift
|
||||||
let personJSON = CoreStore.queryAttributes(
|
let personJSON = CoreStore.queryAttributes(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
Select("age", .Count("age", As: "count")),
|
Select("age", .Count("age", As: "count")),
|
||||||
GroupBy("age")
|
GroupBy("age")
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
this returns dictionaries that shows the count for each `"age"`:
|
this returns dictionaries that shows the count for each `"age"`:
|
||||||
```swift
|
```swift
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
"age": 42,
|
"age": 42,
|
||||||
"count": 1
|
"count": 1
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"age": 22,
|
"age": 22,
|
||||||
"count": 1
|
"count": 1
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -595,17 +595,17 @@ this returns dictionaries that shows the count for each `"age"`:
|
|||||||
One unfortunate thing when using some third-party libraries is that they usually pollute the console with their own logging mechanisms. CoreStore provides its own default logging class, but you can plug-in your own favorite logger by implementing the `CoreStoreLogger` protocol.
|
One unfortunate thing when using some third-party libraries is that they usually pollute the console with their own logging mechanisms. CoreStore provides its own default logging class, but you can plug-in your own favorite logger by implementing the `CoreStoreLogger` protocol.
|
||||||
```swift
|
```swift
|
||||||
final class MyLogger: CoreStoreLogger {
|
final class MyLogger: CoreStoreLogger {
|
||||||
func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||||
// pass to your logger
|
// pass to your logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||||
// pass to your logger
|
// pass to your logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||||
// pass to your logger
|
// pass to your logger
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Then pass an instance of this class to `CoreStore`:
|
Then pass an instance of this class to `CoreStore`:
|
||||||
@@ -627,17 +627,17 @@ CoreStore provides type-safe wrappers for observing managed objects:
|
|||||||
To observe an object, implement the `ManagedObjectObserver` protocol and specify the `EntityType`:
|
To observe an object, implement the `ManagedObjectObserver` protocol and specify the `EntityType`:
|
||||||
```swift
|
```swift
|
||||||
class MyViewController: UIViewController, ManagedObjectObserver {
|
class MyViewController: UIViewController, ManagedObjectObserver {
|
||||||
func managedObjectWillUpdate(objectController: ManagedObjectController<MyPersonEntity>, object: MyPersonEntity) {
|
func managedObjectWillUpdate(objectController: ManagedObjectController<MyPersonEntity>, object: MyPersonEntity) {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
func managedObjectWasUpdated(objectController: ManagedObjectController<MyPersonEntity>, object: MyPersonEntity, changedPersistentKeys: Set<KeyPath>) {
|
func managedObjectWasUpdated(objectController: ManagedObjectController<MyPersonEntity>, object: MyPersonEntity, changedPersistentKeys: Set<KeyPath>) {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
func managedObjectWasDeleted(objectController: ManagedObjectController<MyPersonEntity>, object: MyPersonEntity) {
|
func managedObjectWasDeleted(objectController: ManagedObjectController<MyPersonEntity>, object: MyPersonEntity) {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
We then need to keep a `ManagedObjectController` instance and register our `ManagedObjectObserver` as an observer:
|
We then need to keep a `ManagedObjectController` instance and register our `ManagedObjectObserver` as an observer:
|
||||||
@@ -656,37 +656,37 @@ While `ManagedObjectController` exposes `removeObserver(...)` as well, it only s
|
|||||||
To observe a list of objects, implement one of the `ManagedObjectListChangeObserver` protocols and specify the `EntityType`:
|
To observe a list of objects, implement one of the `ManagedObjectListChangeObserver` protocols and specify the `EntityType`:
|
||||||
```swift
|
```swift
|
||||||
class MyViewController: UIViewController, ManagedObjectListChangeObserver {
|
class MyViewController: UIViewController, ManagedObjectListChangeObserver {
|
||||||
func managedObjectListWillChange(listController: ManagedObjectListController<MyPersonEntity>) {
|
func managedObjectListWillChange(listController: ManagedObjectListController<MyPersonEntity>) {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
func managedObjectListDidChange(listController: ManagedObjectListController<MyPersonEntity>) {
|
func managedObjectListDidChange(listController: ManagedObjectListController<MyPersonEntity>) {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Including `ManagedObjectListChangeObserver`, there are 3 observer protocols you can implement depending on how detailed you need to handle a change notification:
|
Including `ManagedObjectListChangeObserver`, there are 3 observer protocols you can implement depending on how detailed you need to handle a change notification:
|
||||||
- `ManagedObjectListChangeObserver`: lets you handle these callback methods:
|
- `ManagedObjectListChangeObserver`: lets you handle these callback methods:
|
||||||
- `func managedObjectListWillChange(listController: ManagedObjectListController<T>)`
|
- `func managedObjectListWillChange(listController: ManagedObjectListController<T>)`
|
||||||
- `func managedObjectListDidChange(listController: ManagedObjectListController<T>)`
|
- `func managedObjectListDidChange(listController: ManagedObjectListController<T>)`
|
||||||
- `ManagedObjectListObjectObserver`: in addition to `ManagedObjectListChangeObserver` methods, also lets you handle object inserts, updates, and deletes:
|
- `ManagedObjectListObjectObserver`: in addition to `ManagedObjectListChangeObserver` methods, also lets you handle object inserts, updates, and deletes:
|
||||||
- `func managedObjectList(listController: ManagedObjectListController<T>, didInsertObject object: T, toIndexPath indexPath: NSIndexPath)`
|
- `func managedObjectList(listController: ManagedObjectListController<T>, didInsertObject object: T, toIndexPath indexPath: NSIndexPath)`
|
||||||
- `func managedObjectList(listController: ManagedObjectListController<T>, didDeleteObject object: T, fromIndexPath indexPath: NSIndexPath)`
|
- `func managedObjectList(listController: ManagedObjectListController<T>, didDeleteObject object: T, fromIndexPath indexPath: NSIndexPath)`
|
||||||
- `func managedObjectList(listController: ManagedObjectListController<T>, didUpdateObject object: T, atIndexPath indexPath: NSIndexPath)`
|
- `func managedObjectList(listController: ManagedObjectListController<T>, didUpdateObject object: T, atIndexPath indexPath: NSIndexPath)`
|
||||||
- `func managedObjectList(listController: ManagedObjectListController<T>, didMoveObject object: T, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)`
|
- `func managedObjectList(listController: ManagedObjectListController<T>, didMoveObject object: T, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)`
|
||||||
- `ManagedObjectListSectionObserver`: in addition to `ManagedObjectListObjectObserver` methods, also lets you handle section inserts and deletes:
|
- `ManagedObjectListSectionObserver`: in addition to `ManagedObjectListObjectObserver` methods, also lets you handle section inserts and deletes:
|
||||||
- `func managedObjectList(listController: ManagedObjectListController<T>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)`
|
- `func managedObjectList(listController: ManagedObjectListController<T>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)`
|
||||||
- `func managedObjectList(listController: ManagedObjectListController<T>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)`
|
- `func managedObjectList(listController: ManagedObjectListController<T>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)`
|
||||||
|
|
||||||
We then need to create a `ManagedObjectListController` instance and register our `ManagedObjectListChangeObserver` as an observer:
|
We then need to create a `ManagedObjectListController` instance and register our `ManagedObjectListChangeObserver` as an observer:
|
||||||
```swift
|
```swift
|
||||||
self.listController = CoreStore.observeObjectList(
|
self.listController = CoreStore.observeObjectList(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
Where("age > 30"),
|
Where("age > 30"),
|
||||||
OrderBy(.Ascending("name")),
|
OrderBy(.Ascending("name")),
|
||||||
Tweak { (fetchRequest) -> Void in
|
Tweak { (fetchRequest) -> Void in
|
||||||
fetchRequest.fetchBatchSize = 20
|
fetchRequest.fetchBatchSize = 20
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.listController.addObserver(self)
|
self.listController.addObserver(self)
|
||||||
```
|
```
|
||||||
@@ -702,13 +702,13 @@ let firstPerson = self.listController[0]
|
|||||||
If the list needs to be grouped into sections, create the `ManagedObjectListController` instance with the `observeSectionedList(...)` method and a `SectionedBy` clause:
|
If the list needs to be grouped into sections, create the `ManagedObjectListController` instance with the `observeSectionedList(...)` method and a `SectionedBy` clause:
|
||||||
```swift
|
```swift
|
||||||
self.listController = CoreStore.observeSectionedList(
|
self.listController = CoreStore.observeSectionedList(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
SectionedBy("age"),
|
SectionedBy("age"),
|
||||||
Where("gender", isEqualTo: "M"),
|
Where("gender", isEqualTo: "M"),
|
||||||
OrderBy(.Ascending("age"), .Ascending("name")),
|
OrderBy(.Ascending("age"), .Ascending("name")),
|
||||||
Tweak { (fetchRequest) -> Void in
|
Tweak { (fetchRequest) -> Void in
|
||||||
fetchRequest.fetchBatchSize = 20
|
fetchRequest.fetchBatchSize = 20
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
A list controller created this way will group the objects by the attribute key indicated by the `SectionedBy` clause. One more thing to remember is that the `OrderBy` clause should sort the list in such a way that the `SectionedBy` attribute would be sorted together (a requirement shared by `NSFetchedResultsController`.)
|
A list controller created this way will group the objects by the attribute key indicated by the `SectionedBy` clause. One more thing to remember is that the `OrderBy` clause should sort the list in such a way that the `SectionedBy` attribute would be sorted together (a requirement shared by `NSFetchedResultsController`.)
|
||||||
@@ -716,19 +716,19 @@ A list controller created this way will group the objects by the attribute key i
|
|||||||
The `SectionedBy` clause can also be passed a closure to transform the section name into a displayable string:
|
The `SectionedBy` clause can also be passed a closure to transform the section name into a displayable string:
|
||||||
```swift
|
```swift
|
||||||
self.listController = CoreStore.observeSectionedList(
|
self.listController = CoreStore.observeSectionedList(
|
||||||
From(MyPersonEntity),
|
From(MyPersonEntity),
|
||||||
SectionedBy("age") { (sectionName) -> String? in
|
SectionedBy("age") { (sectionName) -> String? in
|
||||||
"\(sectionName) years old"
|
"\(sectionName) years old"
|
||||||
},
|
},
|
||||||
OrderBy(.Ascending("age"), .Ascending("name"))
|
OrderBy(.Ascending("age"), .Ascending("name"))
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
This is useful when implementing a `UITableViewDelegate`'s section header:
|
This is useful when implementing a `UITableViewDelegate`'s section header:
|
||||||
```swift
|
```swift
|
||||||
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||||
let sectionInfo = self.listController.sectionInfoAtIndex(section)
|
let sectionInfo = self.listController.sectionInfoAtIndex(section)
|
||||||
// sectionInfo is an NSFetchedResultsSectionInfo instance
|
// sectionInfo is an NSFetchedResultsSectionInfo instance
|
||||||
return sectionInfo.name
|
return sectionInfo.name
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -767,9 +767,7 @@ Add all *.swift* files to your project.
|
|||||||
|
|
||||||
# Contributions
|
# Contributions
|
||||||
While CoreStore's design is pretty solid and the unit test and demo app work well, CoreStore is pretty much still in its early stage. With more exposure to production code usage and criticisms from the developer community, CoreStore hopes to mature as well.
|
While CoreStore's design is pretty solid and the unit test and demo app work well, CoreStore is pretty much still in its early stage. With more exposure to production code usage and criticisms from the developer community, CoreStore hopes to mature as well.
|
||||||
|
|
||||||
Please feel free to report any issues, suggestions, or criticisms!
|
Please feel free to report any issues, suggestions, or criticisms!
|
||||||
|
|
||||||
日本語で連絡していただいても構いません!
|
日本語で連絡していただいても構いません!
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
Reference in New Issue
Block a user