mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-02-19 14:17:56 +01:00
SwiftUI utilities done (for now)
This commit is contained in:
275
Sources/DataStack+Reactive.swift
Normal file
275
Sources/DataStack+Reactive.swift
Normal file
@@ -0,0 +1,275 @@
|
||||
//
|
||||
// DataStack+Reactive.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2021 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#if canImport(Combine)
|
||||
|
||||
import Combine
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
extension DataStack: PublisherCompatible {}
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
extension PublisherProvider where Base == DataStack {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Reactive extension for `CoreStore.DataStack`'s `importObject(...)` API. Creates an `ImportableObject` by importing from the specified import source. The event value will be the object instance correctly associated for the `DataStack`.
|
||||
```
|
||||
dataStack.reactive
|
||||
.importObject(
|
||||
Into<Person>(),
|
||||
source: ["name": "John"]
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { result in
|
||||
// ...
|
||||
},
|
||||
receiveValue: { (person) in
|
||||
XCTAssertNotNil(person)
|
||||
// ...
|
||||
}
|
||||
)
|
||||
.store(in: &cancellables)
|
||||
```
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter source: the object to import values from
|
||||
- returns: A `Future` that publishes the imported object. The event value, if not `nil`, will be the object instance correctly associated for the `DataStack`.
|
||||
*/
|
||||
public func importObject<O: DynamicObject & ImportableObject>(
|
||||
_ into: Into<O>,
|
||||
source: O.ImportSource
|
||||
) -> Future<O?, CoreStoreError> {
|
||||
|
||||
return .init { (promise) in
|
||||
|
||||
self.base.perform(
|
||||
asynchronous: { (transaction) -> O? in
|
||||
|
||||
return try transaction.importObject(
|
||||
into,
|
||||
source: source
|
||||
)
|
||||
},
|
||||
success: { promise(.success($0.flatMap(self.base.fetchExisting))) },
|
||||
failure: { promise(.failure($0)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Reactive extension for `CoreStore.DataStack`'s `importObject(...)` API. Updates an existing `ImportableObject` by importing values from the specified import source. The event value will be the object instance correctly associated for the `DataStack`.
|
||||
```
|
||||
let existingPerson: Person = // ...
|
||||
dataStack.reactive
|
||||
.importObject(
|
||||
existingPerson,
|
||||
source: ["name": "John", "age": 30]
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { result in
|
||||
// ...
|
||||
},
|
||||
receiveValue: { (person) in
|
||||
XCTAssertEqual(person?.age, 30)
|
||||
// ...
|
||||
}
|
||||
)
|
||||
.store(in: &cancellables)
|
||||
```
|
||||
- parameter object: the object to update
|
||||
- parameter source: the object to import values from
|
||||
- returns: A `Future` that publishes the imported object. The event value, if not `nil`, will be the object instance correctly associated for the `DataStack`.
|
||||
*/
|
||||
public func importObject<O: DynamicObject & ImportableObject>(
|
||||
_ object: O,
|
||||
source: O.ImportSource
|
||||
) -> Future<O?, CoreStoreError> {
|
||||
|
||||
return .init { (promise) in
|
||||
|
||||
self.base.perform(
|
||||
asynchronous: { (transaction) -> O? in
|
||||
|
||||
guard let object = transaction.edit(object) else {
|
||||
|
||||
try transaction.cancel()
|
||||
}
|
||||
try transaction.importObject(
|
||||
object,
|
||||
source: source
|
||||
)
|
||||
return object
|
||||
},
|
||||
success: { promise(.success($0.flatMap(self.base.fetchExisting))) },
|
||||
failure: { promise(.failure($0)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Reactive extension for `CoreStore.DataStack`'s `importUniqueObject(...)` API. Updates an existing `ImportableUniqueObject` or creates a new instance by importing from the specified import source. The event value will be the object instance correctly associated for the `DataStack`.
|
||||
```
|
||||
dataStack.reactive
|
||||
.importUniqueObject(
|
||||
Into<Person>(),
|
||||
source: ["name": "John", "age": 30]
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { result in
|
||||
// ...
|
||||
},
|
||||
receiveValue: { (person) in
|
||||
XCTAssertEqual(person?.age, 30)
|
||||
// ...
|
||||
}
|
||||
)
|
||||
.store(in: &cancellables)
|
||||
```
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter source: the object to import values from
|
||||
- returns: A `Future` for the imported object. The event value, if not `nil`, will be the object instance correctly associated for the `DataStack`.
|
||||
*/
|
||||
public func importUniqueObject<O: DynamicObject & ImportableUniqueObject>(
|
||||
_ into: Into<O>,
|
||||
source: O.ImportSource
|
||||
) -> Future<O?, CoreStoreError> {
|
||||
|
||||
return .init { (promise) in
|
||||
|
||||
self.base.perform(
|
||||
asynchronous: { (transaction) -> O? in
|
||||
|
||||
return try transaction.importUniqueObject(
|
||||
into,
|
||||
source: source
|
||||
)
|
||||
},
|
||||
success: { promise(.success($0.flatMap(self.base.fetchExisting))) },
|
||||
failure: { promise(.failure($0)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Reactive extension for `CoreStore.DataStack`'s `importUniqueObjects(...)` API. Updates existing `ImportableUniqueObject`s or creates them by importing from the specified array of import sources. `ImportableUniqueObject` methods are called on the objects in the same order as they are in the `sourceArray`, and are returned in an array with that same order. The event values will be object instances correctly associated for the `DataStack`.
|
||||
```
|
||||
dataStack.reactive
|
||||
.importUniqueObjects(
|
||||
Into<Person>(),
|
||||
sourceArray: [
|
||||
["name": "John"],
|
||||
["name": "Bob"],
|
||||
["name": "Joe"]
|
||||
]
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { result in
|
||||
// ...
|
||||
},
|
||||
receiveValue: { (people) in
|
||||
XCTAssertEqual(people?.count, 3)
|
||||
// ...
|
||||
}
|
||||
)
|
||||
.store(in: &cancellables)
|
||||
```
|
||||
- Warning: If `sourceArray` contains multiple import sources with same ID, no merging will occur and ONLY THE LAST duplicate will be imported.
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter sourceArray: the array of objects to import values from
|
||||
- parameter preProcess: a closure that lets the caller tweak the internal `UniqueIDType`-to-`ImportSource` mapping to be used for importing. Callers can remove from/add to/update `mapping` and return the updated array from the closure.
|
||||
- returns: A `Future` for the imported objects. The event values will be the object instances correctly associated for the `DataStack`.
|
||||
*/
|
||||
public func importUniqueObjects<O: DynamicObject & ImportableUniqueObject, S: Sequence>(
|
||||
_ into: Into<O>,
|
||||
sourceArray: S,
|
||||
preProcess: @escaping (_ mapping: [O.UniqueIDType: O.ImportSource]) throws -> [O.UniqueIDType: O.ImportSource] = { $0 }
|
||||
) -> Future<[O], CoreStoreError> where S.Iterator.Element == O.ImportSource {
|
||||
|
||||
return .init { (promise) in
|
||||
|
||||
self.base.perform(
|
||||
asynchronous: { (transaction) -> [O] in
|
||||
|
||||
return try transaction.importUniqueObjects(
|
||||
into,
|
||||
sourceArray: sourceArray,
|
||||
preProcess: preProcess
|
||||
)
|
||||
},
|
||||
success: { promise(.success(self.base.fetchExisting($0))) },
|
||||
failure: { promise(.failure($0)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Reactive extension for `CoreStore.DataStack`'s `perform(asynchronous:...)` API. Performs a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. The event value will be the value returned from the `task` closure. Any errors thrown from inside the `task` will be wrapped in a `CoreStoreError` and reported to the completion `.failure`. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
|
||||
```
|
||||
dataStack.reactive
|
||||
.perform(
|
||||
asynchronous: { (transaction) -> (inserted: Set<NSManagedObject>, deleted: Set<NSManagedObject>) in
|
||||
|
||||
// ...
|
||||
return (
|
||||
transaction.insertedObjects(),
|
||||
transaction.deletedObjects()
|
||||
)
|
||||
}
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { result in
|
||||
// ...
|
||||
},
|
||||
receiveValue: { value in
|
||||
let inserted = dataStack.fetchExisting(value0.inserted)
|
||||
let deleted = dataStack.fetchExisting(value0.deleted)
|
||||
// ...
|
||||
}
|
||||
)
|
||||
.store(in: &cancellables)
|
||||
```
|
||||
- parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
- returns: A `Future` whose event value be the value returned from the `task` closure.
|
||||
*/
|
||||
public func perform<Output>(
|
||||
_ asynchronous: @escaping (AsynchronousDataTransaction) throws -> Output
|
||||
) -> Future<Output, CoreStoreError> {
|
||||
|
||||
return .init { (promise) in
|
||||
|
||||
self.base.perform(
|
||||
asynchronous: asynchronous,
|
||||
success: { promise(.success($0)) },
|
||||
failure: { promise(.failure($0)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user