async API prototypes

This commit is contained in:
John Estropia
2022-07-10 10:39:45 +09:00
parent e9219682b5
commit da4ac192db
6 changed files with 581 additions and 104 deletions

View File

@@ -783,6 +783,14 @@
B5F8496D234898240029D57B /* ListSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F8496B234898240029D57B /* ListSnapshot.swift */; };
B5F8496E234898240029D57B /* ListSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F8496B234898240029D57B /* ListSnapshot.swift */; };
B5F8496F234898240029D57B /* ListSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F8496B234898240029D57B /* ListSnapshot.swift */; };
B5F9C093287849E0007AAD2E /* DataStack+Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F9C092287849E0007AAD2E /* DataStack+Concurrency.swift */; };
B5F9C094287849E0007AAD2E /* DataStack+Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F9C092287849E0007AAD2E /* DataStack+Concurrency.swift */; };
B5F9C095287849E0007AAD2E /* DataStack+Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F9C092287849E0007AAD2E /* DataStack+Concurrency.swift */; };
B5F9C096287849E0007AAD2E /* DataStack+Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F9C092287849E0007AAD2E /* DataStack+Concurrency.swift */; };
B5F9C098287850D6007AAD2E /* MigrationProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F9C097287850D6007AAD2E /* MigrationProgress.swift */; };
B5F9C099287850D6007AAD2E /* MigrationProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F9C097287850D6007AAD2E /* MigrationProgress.swift */; };
B5F9C09A287850D6007AAD2E /* MigrationProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F9C097287850D6007AAD2E /* MigrationProgress.swift */; };
B5F9C09B287850D6007AAD2E /* MigrationProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F9C097287850D6007AAD2E /* MigrationProgress.swift */; };
B5FAD6A91B50A4B400714891 /* Progress+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FAD6A81B50A4B300714891 /* Progress+Convenience.swift */; };
B5FAD6AC1B51285300714891 /* Internals.MigrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FAD6AB1B51285300714891 /* Internals.MigrationManager.swift */; };
B5FE4DA21C8481E100FA6A91 /* StorageInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FE4DA11C8481E100FA6A91 /* StorageInterface.swift */; };
@@ -1061,6 +1069,8 @@
B5F5848628633741001F57ED /* Module.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Module.swift; sourceTree = "<group>"; };
B5F8496B234898240029D57B /* ListSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListSnapshot.swift; sourceTree = "<group>"; };
B5F849702348A6690029D57B /* EnvironmentValues+DataSources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentValues+DataSources.swift"; sourceTree = "<group>"; };
B5F9C092287849E0007AAD2E /* DataStack+Concurrency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataStack+Concurrency.swift"; sourceTree = "<group>"; };
B5F9C097287850D6007AAD2E /* MigrationProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationProgress.swift; sourceTree = "<group>"; };
B5FAD6A81B50A4B300714891 /* Progress+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Progress+Convenience.swift"; sourceTree = "<group>"; };
B5FAD6AB1B51285300714891 /* Internals.MigrationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Internals.MigrationManager.swift; sourceTree = "<group>"; };
B5FE4DA11C8481E100FA6A91 /* StorageInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageInterface.swift; sourceTree = "<group>"; };
@@ -1430,6 +1440,7 @@
B5D1E22B19FA9FBC003B2874 /* CoreStoreError.swift */,
B549F6721E56A92800FBAB2D /* CoreDataNativeType.swift */,
B5D339F01E94AF5800C880DE /* CoreStoreStrings.swift */,
B5F9C091287849CB007AAD2E /* Swift Concurrency */,
B5C795BE25D933C200BDACC1 /* Reactive Programming */,
B52FEC722596DB6400368BFB /* SwiftUI */,
B5E84EDA1AFF84500064E85B /* Setup */,
@@ -1500,6 +1511,7 @@
B5B866EC25F4800800335476 /* DataStack.AddStoragePublisher.swift */,
B5944EFA25E8E8DA001D1D81 /* ListPublisher.SnapshotPublisher.swift */,
B5B866DF25E9048000335476 /* ObjectPublisher.SnapshotPublisher.swift */,
B5F9C097287850D6007AAD2E /* MigrationProgress.swift */,
);
name = "Reactive Programming";
sourceTree = "<group>";
@@ -1679,6 +1691,14 @@
name = Internals;
sourceTree = "<group>";
};
B5F9C091287849CB007AAD2E /* Swift Concurrency */ = {
isa = PBXGroup;
children = (
B5F9C092287849E0007AAD2E /* DataStack+Concurrency.swift */,
);
name = "Swift Concurrency";
sourceTree = "<group>";
};
B5FE4DA01C84818B00FA6A91 /* StorageInterfaces */ = {
isa = PBXGroup;
children = (
@@ -2052,6 +2072,7 @@
B5F1DA8D1B9AA97D007C5CBB /* ImportableObject.swift in Sources */,
B56965241B356B820075EE4A /* MigrationResult.swift in Sources */,
B5C7958F25D7D18000BDACC1 /* ListState.swift in Sources */,
B5F9C098287850D6007AAD2E /* MigrationProgress.swift in Sources */,
B5FE4DAC1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */,
B5E41EC01EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */,
B5A1DAC81F111BFA003CF369 /* KeyPath+Querying.swift in Sources */,
@@ -2073,6 +2094,7 @@
B50E175723517DE4004F033C /* Differentiable.swift in Sources */,
B59AFF411C6593E400C0ABE2 /* NSPersistentStoreCoordinator+Setup.swift in Sources */,
B5E84F231AFF84860064E85B /* ListMonitor.swift in Sources */,
B5F9C093287849E0007AAD2E /* DataStack+Concurrency.swift in Sources */,
B5BF7FC6234D7E460070E741 /* ObjectSnapshot.swift in Sources */,
B5E84EF71AFF846E0064E85B /* UnsafeDataTransaction.swift in Sources */,
B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */,
@@ -2266,6 +2288,7 @@
B56923C51EB823B4007C4DC9 /* NSEntityDescription+Migration.swift in Sources */,
82BA18C91C4BBD5900A0916E /* MigrationType.swift in Sources */,
B5D8CA772346EAEE0055D7D1 /* DataStack+DataSources.swift in Sources */,
B5F9C099287850D6007AAD2E /* MigrationProgress.swift in Sources */,
82BA18D01C4BBD7100A0916E /* Internals.MigrationManager.swift in Sources */,
B5DE5231230BDA1300A22534 /* CoreStoreDefaults.swift in Sources */,
B56E4ED523CDB54A00E1708C /* FieldProtocol.swift in Sources */,
@@ -2287,6 +2310,7 @@
82BA18BF1C4BBD5300A0916E /* SectionBy.swift in Sources */,
B5D339ED1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */,
B509D7D423C84E1900F42824 /* Transformable.Required.swift in Sources */,
B5F9C094287849E0007AAD2E /* DataStack+Concurrency.swift in Sources */,
82BA18AC1C4BBD3100A0916E /* SynchronousDataTransaction.swift in Sources */,
B50C3EE623D153EA00B29880 /* Field.Coded.swift in Sources */,
82BA18C41C4BBD5300A0916E /* ListMonitor.swift in Sources */,
@@ -2480,6 +2504,7 @@
B5D8CA792346EAEF0055D7D1 /* DataStack+DataSources.swift in Sources */,
B56E4ED723CDB54A00E1708C /* FieldProtocol.swift in Sources */,
B56923C71EB823B4007C4DC9 /* NSEntityDescription+Migration.swift in Sources */,
B5F9C09B287850D6007AAD2E /* MigrationProgress.swift in Sources */,
B5944EFE25E8E8DA001D1D81 /* ListPublisher.SnapshotPublisher.swift in Sources */,
B5DE5233230BDA1300A22534 /* CoreStoreDefaults.swift in Sources */,
B52DD1A51BE1F92F00949AFE /* ImportableUniqueObject.swift in Sources */,
@@ -2501,6 +2526,7 @@
B52F74321E9B50D0005F3DAC /* SchemaHistory.swift in Sources */,
B509D7D623C84E1900F42824 /* Transformable.Required.swift in Sources */,
B50C3EE823D153EA00B29880 /* Field.Coded.swift in Sources */,
B5F9C096287849E0007AAD2E /* DataStack+Concurrency.swift in Sources */,
B5D339EF1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */,
B52DD19F1BE1F92C00949AFE /* SynchronousDataTransaction.swift in Sources */,
B52DD1CB1BE1F94600949AFE /* Internals.WeakObject.swift in Sources */,
@@ -2694,6 +2720,7 @@
B56321931BD65216006C9394 /* DataStack+Querying.swift in Sources */,
B5D8CA782346EAEF0055D7D1 /* DataStack+DataSources.swift in Sources */,
B56923C61EB823B4007C4DC9 /* NSEntityDescription+Migration.swift in Sources */,
B5F9C09A287850D6007AAD2E /* MigrationProgress.swift in Sources */,
B56321A71BD65216006C9394 /* MigrationResult.swift in Sources */,
B56E4ED623CDB54A00E1708C /* FieldProtocol.swift in Sources */,
B5DE5232230BDA1300A22534 /* CoreStoreDefaults.swift in Sources */,
@@ -2715,6 +2742,7 @@
B52F74311E9B50D0005F3DAC /* SchemaHistory.swift in Sources */,
B563218F1BD65216006C9394 /* ImportableObject.swift in Sources */,
B509D7D523C84E1900F42824 /* Transformable.Required.swift in Sources */,
B5F9C095287849E0007AAD2E /* DataStack+Concurrency.swift in Sources */,
B56321991BD65216006C9394 /* OrderBy.swift in Sources */,
B50C3EE723D153EA00B29880 /* Field.Coded.swift in Sources */,
B5D339EE1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */,

View File

@@ -0,0 +1,367 @@
//
// DataStack+Concurrency.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.
//
import Foundation
import CoreData
// MARK: - DataStack
extension DataStack {
// MARK: Public
/**
Swift concurrency utilities for the `DataStack` are exposed through this namespace
*/
public var async: DataStack.AsyncNamespace {
return .init(self)
}
// MARK: - ReactiveNamespace
/**
Swift concurrency for the `DataStack` are exposed through this namespace. Extend this type if you need to add other `async` utilities for `DataStack`.
*/
public struct AsyncNamespace {
// MARK: Public
/**
The `DataStack` instance
*/
public let base: DataStack
// MARK: Internal
internal init(_ base: DataStack) {
self.base = base
}
}
}
// MARK: - DataStack.AsyncNamespace
extension DataStack.AsyncNamespace {
// MARK: Public
/**
Swift concurrency extension for `CoreStore.DataStack`'s `addStorage(...)` API. Asynchronously adds a `StorageInterface` to the stack.
```
let storage = try await dataStack.async.addStorage(
InMemoryStore(configuration: "Config1")
)
```
- parameter storage: the storage
- returns: The `StorageInterface` instance added to the `DataStack`. Note that the `StorageInterface` event value may not always be the same instance as the parameter argument if a previous `StorageInterface` was already added at the same URL and with the same configuration.
- throws: A `CoreStoreError` value indicating the failure reason
*/
public func addStorage<T: StorageInterface>(
_ storage: T
) async throws -> T {
return try await withCheckedThrowingContinuation { continuation in
self.base.addStorage(
storage,
completion: continuation.resume(with:)
)
}
}
/**
Swift concurrency extension for `CoreStore.DataStack`'s `addStorage(...)` API. Asynchronously adds a `LocalStorage` to the stack. Migrations are also initiated by default. The event emits `MigrationProgress` `enum` values.
```
for try await migrationProgress in dataStack.async.addStorage(
SQLiteStore(
fileName: "core_data.sqlite",
configuration: "Config1"
)
) {
print("\(round(migrationProgress.fractionCompleted * 100)) %") // 0.0 ~ 1.0
}
```
- parameter storage: the local storage
- returns: An `AsyncThrowingStream` that emits a `MigrationProgress` value with metadata for migration progress. Note that the `LocalStorage` event value may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration.
- throws: A `CoreStoreError` value indicating the failure reason
*/
public func addStorage<T>(
_ storage: T
) -> AsyncThrowingStream<MigrationProgress<T>, Swift.Error> {
return .init(
bufferingPolicy: .unbounded,
{ continuation in
var progress: Progress? = nil
progress = self.base.addStorage(
storage,
completion: { result in
progress?.setProgressHandler(nil)
switch result {
case .success(let storage):
continuation.yield(
.finished(
storage: storage,
migrationRequired: progress != nil
)
)
continuation.finish()
case .failure(let error):
continuation.finish(
throwing: error
)
}
}
)
if let progress = progress {
progress.setProgressHandler { progress in
continuation.yield(
.migrating(
storage: storage,
progressObject: progress
)
)
}
}
}
)
}
/**
Swift concurrency 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`.
```
let object = try await dataStack.async.importObject(
Into<Person>(),
source: ["name": "John"]
)
```
- parameter into: an `Into` clause specifying the entity type
- parameter source: the object to import values from
- returns: The object instance correctly associated for the `DataStack` if the object was imported successfully, or `nil` if the `ImportableObject` ignored the `source`.
- throws: A `CoreStoreError` value indicating the failure reason
*/
public func importObject<O: DynamicObject & ImportableObject>(
_ into: Into<O>,
source: O.ImportSource
) async throws -> O? {
return try await withCheckedThrowingContinuation { continuation in
self.base.perform(
asynchronous: { (transaction) -> O? in
return try transaction.importObject(
into,
source: source
)
},
success: {
continuation.resume(
with: .success($0.flatMap(self.base.fetchExisting))
)
},
failure: continuation.resume(throwing:)
)
}
}
/**
Swift concurrency 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 importedPerson = try await dataStack.async.importObject(
existingPerson,
source: ["name": "John", "age": 30]
)
```
- parameter object: the object to update
- parameter source: the object to import values from
- returns: The object instance correctly associated for the `DataStack` if the object was imported successfully, or `nil` if the `ImportableObject` ignored the `source`.
- throws: A `CoreStoreError` value indicating the failure reason
*/
public func importObject<O: DynamicObject & ImportableObject>(
_ object: O,
source: O.ImportSource
) async throws -> O? {
return try await withCheckedThrowingContinuation { continuation 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: {
continuation.resume(
with: .success($0.flatMap(self.base.fetchExisting))
)
},
failure: continuation.resume(throwing:)
)
}
}
/**
Swift concurrency 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`.
```
let person = try await dataStack.async.importUniqueObject(
Into<Person>(),
source: ["name": "John", "age": 30]
)
```
- parameter into: an `Into` clause specifying the entity type
- parameter source: the object to import values from
- returns: The object instance correctly associated for the `DataStack` if the object was imported successfully, or `nil` if the `ImportableUniqueObject` ignored the `source`.
- throws: A `CoreStoreError` value indicating the failure reason
*/
public func importUniqueObject<O: DynamicObject & ImportableUniqueObject>(
_ into: Into<O>,
source: O.ImportSource
) async throws -> O? {
return try await withCheckedThrowingContinuation { continuation in
self.base.perform(
asynchronous: { (transaction) -> O? in
return try transaction.importUniqueObject(
into,
source: source
)
},
success: {
continuation.resume(
with: .success($0.flatMap(self.base.fetchExisting))
)
},
failure: continuation.resume(throwing:)
)
}
}
/**
Swift concurrency 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`.
```
let people = try await dataStack.async.importUniqueObjects(
Into<Person>(),
sourceArray: [
["name": "John"],
["name": "Bob"],
["name": "Joe"]
]
)
```
- 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: The imported objects correctly associated for the `DataStack`.
- throws: A `CoreStoreError` value indicating the failure reason
*/
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 }
) async throws -> [O]
where S.Iterator.Element == O.ImportSource {
return try await withCheckedThrowingContinuation { continuation in
self.base.perform(
asynchronous: { (transaction) -> [O] in
return try transaction.importUniqueObjects(
into,
sourceArray: sourceArray,
preProcess: preProcess
)
},
success: {
continuation.resume(
with: .success(self.base.fetchExisting($0))
)
},
failure: continuation.resume(throwing:)
)
}
}
/**
Swift concurrency 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` before being thrown from the `async` method. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
```
let result = try await dataStack.async.perform(
asynchronous: { (transaction) -> (inserted: Set<NSManagedObject>, deleted: Set<NSManagedObject>) in
// ...
return (
transaction.insertedObjects(),
transaction.deletedObjects()
)
}
)
let inserted = dataStack.fetchExisting(result.inserted)
let deleted = dataStack.fetchExisting(result.deleted)
```
- 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: The value returned from the `task` closure.
- throws: A `CoreStoreError` value indicating the failure reason
*/
public func perform<Output>(
_ asynchronous: @escaping (AsynchronousDataTransaction) throws -> Output
) async throws -> Output {
return try await withCheckedThrowingContinuation { continuation in
self.base.perform(
asynchronous: asynchronous,
completion: continuation.resume(with:)
)
}
}
}

View File

@@ -93,7 +93,9 @@ extension DataStack.ReactiveNamespace {
- parameter storage: the storage
- returns: A `Future` that emits a `StorageInterface` instance added to the `DataStack`. Note that the `StorageInterface` event value may not always be the same instance as the parameter argument if a previous `StorageInterface` was already added at the same URL and with the same configuration.
*/
public func addStorage<T: StorageInterface>(_ storage: T) -> Future<T, CoreStoreError> {
public func addStorage<T: StorageInterface>(
_ storage: T
) -> Future<T, CoreStoreError> {
return .init { (promise) in
@@ -115,7 +117,7 @@ extension DataStack.ReactiveNamespace {
}
/**
Reactive extension for `CoreStore.DataStack`'s `addStorage(...)` API. Asynchronously adds a `LocalStorage` to the stack. Migrations are also initiated by default. The event emits `DataStack.AddStoragePublisher.MigrationProgress` `enum` values.
Reactive extension for `CoreStore.DataStack`'s `addStorage(...)` API. Asynchronously adds a `LocalStorage` to the stack. Migrations are also initiated by default. The event emits `MigrationProgress` `enum` values.
```
dataStack.reactive
.addStorage(
@@ -135,7 +137,7 @@ extension DataStack.ReactiveNamespace {
.store(in: &cancellables)
```
- parameter storage: the local storage
- returns: A `DataStack.AddStoragePublisher` that emits a `DataStack.AddStoragePublisher.MigrationProgress` value with metadata for migration progress. Note that the `LocalStorage` event value may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration.
- returns: A `DataStack.AddStoragePublisher` that emits a `MigrationProgress` value with metadata for migration progress. Note that the `LocalStorage` event value may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration.
*/
public func addStorage<T: LocalStorage>(_ storage: T) -> DataStack.AddStoragePublisher<T> {

View File

@@ -49,7 +49,7 @@ extension DataStack {
// MARK: Publisher
public typealias Output = MigrationProgress
public typealias Output = CoreStore.MigrationProgress<Storage>
public typealias Failure = CoreStoreError
public func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
@@ -63,56 +63,6 @@ extension DataStack {
)
}
// MARK: - MigrationProgress
/**
A `MigrationProgress` contains info on a `LocalStorage`'s setup progress.
- SeeAlso: DataStack.reactive.addStorage(_:)
*/
public enum MigrationProgress {
/**
The `LocalStorage` is currently being migrated
*/
case migrating(storage: Storage, progressObject: Progress)
/**
The `LocalStorage` has been added to the `DataStack` and is ready for reading and writing
*/
case finished(storage: Storage, migrationRequired: Bool)
/**
The fraction of the overall work completed by the migration. Returns a value between 0.0 and 1.0, inclusive.
*/
public var fractionCompleted: Double {
switch self {
case .migrating(_, let progressObject):
return progressObject.fractionCompleted
case .finished:
return 1
}
}
/**
Returns `true` if the storage was successfully added to the stack, `false` otherwise.
*/
public var isCompleted: Bool {
switch self {
case .migrating:
return false
case .finished:
return true
}
}
}
// MARK: - AddStorageSubscriber
@@ -232,6 +182,12 @@ extension DataStack {
private let storage: Storage
private var subscriber: S?
}
// MARK: Deprecated
@available(*, deprecated, renamed: "MigrationProgress")
public typealias MigrationProgress = CoreStore.MigrationProgress<Storage>
}
}

View File

@@ -46,7 +46,11 @@ public final class DataStack: Equatable {
- parameter bundle: an optional bundle to load .xcdatamodeld models from. If not specified, the main bundle will be used.
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
*/
public convenience init(xcodeModelName: XcodeDataModelFileName = DataStack.applicationName, bundle: Bundle = Bundle.main, migrationChain: MigrationChain = nil) {
public convenience init(
xcodeModelName: XcodeDataModelFileName = DataStack.applicationName,
bundle: Bundle = Bundle.main,
migrationChain: MigrationChain = nil
) {
self.init(
schemaHistory: SchemaHistory(
@@ -79,7 +83,11 @@ public final class DataStack: Equatable {
- parameter otherSchema: a list of other `DynamicSchema` instances that represent present/previous/future model versions, in any order
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
*/
public convenience init(_ schema: DynamicSchema, _ otherSchema: DynamicSchema..., migrationChain: MigrationChain = nil) {
public convenience init(
_ schema: DynamicSchema,
_ otherSchema: DynamicSchema...,
migrationChain: MigrationChain = nil
) {
self.init(
schemaHistory: SchemaHistory(
@@ -108,7 +116,9 @@ public final class DataStack: Equatable {
```
- parameter schemaHistory: the `SchemaHistory` for the stack
*/
public required init(schemaHistory: SchemaHistory) {
public required init(
schemaHistory: SchemaHistory
) {
self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: schemaHistory.rawModel)
self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator)
@@ -139,7 +149,9 @@ public final class DataStack: Equatable {
/**
Returns the entity name-to-class type mapping from the `DataStack`'s model.
*/
public func entityTypesByName(for type: NSManagedObject.Type) -> [EntityName: NSManagedObject.Type] {
public func entityTypesByName(
for type: NSManagedObject.Type
) -> [EntityName: NSManagedObject.Type] {
var entityTypesByName: [EntityName: NSManagedObject.Type] = [:]
for (entityIdentifier, entityDescription) in self.schemaHistory.entityDescriptionsByEntityIdentifier {
@@ -163,7 +175,9 @@ public final class DataStack: Equatable {
/**
Returns the entity name-to-class type mapping from the `DataStack`'s model.
*/
public func entityTypesByName(for type: CoreStoreObject.Type) -> [EntityName: CoreStoreObject.Type] {
public func entityTypesByName(
for type: CoreStoreObject.Type
) -> [EntityName: CoreStoreObject.Type] {
var entityTypesByName: [EntityName: CoreStoreObject.Type] = [:]
for (entityIdentifier, entityDescription) in self.schemaHistory.entityDescriptionsByEntityIdentifier {
@@ -191,7 +205,9 @@ public final class DataStack: Equatable {
/**
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass.
*/
public func entityDescription(for type: NSManagedObject.Type) -> NSEntityDescription? {
public func entityDescription(
for type: NSManagedObject.Type
) -> NSEntityDescription? {
return self.entityDescription(for: Internals.EntityIdentifier(type))
}
@@ -199,7 +215,9 @@ public final class DataStack: Equatable {
/**
Returns the `NSEntityDescription` for the specified `CoreStoreObject` subclass.
*/
public func entityDescription(for type: CoreStoreObject.Type) -> NSEntityDescription? {
public func entityDescription(
for type: CoreStoreObject.Type
) -> NSEntityDescription? {
return self.entityDescription(for: Internals.EntityIdentifier(type))
}
@@ -207,7 +225,9 @@ public final class DataStack: Equatable {
/**
Returns the `NSManagedObjectID` for the specified object URI if it exists in the persistent store.
*/
public func objectID(forURIRepresentation url: URL) -> NSManagedObjectID? {
public func objectID(
forURIRepresentation url: URL
) -> NSManagedObjectID? {
return self.coordinator.managedObjectID(forURIRepresentation: url)
}
@@ -236,7 +256,9 @@ public final class DataStack: Equatable {
- returns: the `StorageInterface` added to the stack
*/
@discardableResult
public func addStorageAndWait<T: StorageInterface>(_ storage: T) throws -> T {
public func addStorageAndWait<T: StorageInterface>(
_ storage: T
) throws -> T {
do {
@@ -275,7 +297,9 @@ public final class DataStack: Equatable {
- returns: the local storage added to the stack. Note that this may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration.
*/
@discardableResult
public func addStorageAndWait<T: LocalStorage>(_ storage: T) throws -> T {
public func addStorageAndWait<T: LocalStorage>(
_ storage: T
) throws -> T {
return try self.coordinator.performSynchronously {
@@ -374,7 +398,9 @@ public final class DataStack: Equatable {
Prepares deinitializing the `DataStack` by removing all persistent stores. This is not necessary, but can help silence SQLite warnings when actively releasing and recreating `DataStack`s.
- parameter completion: the closure to execute after all persistent stores are removed
*/
public func unsafeRemoveAllPersistentStores(completion: @escaping () -> Void = {}) {
public func unsafeRemoveAllPersistentStores(
completion: @escaping () -> Void = {}
) {
let coordinator = self.coordinator
coordinator.performAsynchronously {
@@ -441,7 +467,7 @@ public final class DataStack: Equatable {
internal let mainContext: NSManagedObjectContext
internal let schemaHistory: SchemaHistory
internal let childTransactionQueue = DispatchQueue.serial("com.coreStore.dataStack.childTransactionQueue", qos: .utility)
internal let storeMetadataUpdateQueue = DispatchQueue.concurrent("com.coreStore.persistentStoreBarrierQueue", qos: .userInteractive)
internal let storeMetadataLock: NSRecursiveLock = .init()
internal let migrationQueue: OperationQueue = Internals.with {
let migrationQueue = OperationQueue()
@@ -452,56 +478,68 @@ public final class DataStack: Equatable {
return migrationQueue
}
internal func persistentStoreForStorage(_ storage: StorageInterface) -> NSPersistentStore? {
internal func persistentStoreForStorage(
_ storage: StorageInterface
) -> NSPersistentStore? {
return self.coordinator.persistentStores
.filter { $0.storageInterface === storage }
.first
}
internal func persistentStores(for entityIdentifier: Internals.EntityIdentifier) -> [NSPersistentStore]? {
var returnValue: [NSPersistentStore]? = nil
self.storeMetadataUpdateQueue.sync(flags: .barrier) {
returnValue = self.finalConfigurationsByEntityIdentifier[entityIdentifier]?
.map({ self.persistentStoresByFinalConfiguration[$0]! }) ?? []
internal func persistentStores(
for entityIdentifier: Internals.EntityIdentifier
) -> [NSPersistentStore]? {
self.storeMetadataLock.lock()
defer {
self.storeMetadataLock.unlock()
}
return returnValue
return self.finalConfigurationsByEntityIdentifier[entityIdentifier]?
.map({ self.persistentStoresByFinalConfiguration[$0]! }) ?? []
}
internal func persistentStore(for entityIdentifier: Internals.EntityIdentifier, configuration: ModelConfiguration, inferStoreIfPossible: Bool) -> (store: NSPersistentStore?, isAmbiguous: Bool) {
return self.storeMetadataUpdateQueue.sync(flags: .barrier) { () -> (store: NSPersistentStore?, isAmbiguous: Bool) in
let configurationsForEntity = self.finalConfigurationsByEntityIdentifier[entityIdentifier] ?? []
if let configuration = configuration {
if configurationsForEntity.contains(configuration) {
return (store: self.persistentStoresByFinalConfiguration[configuration], isAmbiguous: false)
}
else if !inferStoreIfPossible {
return (store: nil, isAmbiguous: false)
}
internal func persistentStore(
for entityIdentifier: Internals.EntityIdentifier,
configuration: ModelConfiguration,
inferStoreIfPossible: Bool
) -> (store: NSPersistentStore?, isAmbiguous: Bool) {
self.storeMetadataLock.lock()
defer {
self.storeMetadataLock.unlock()
}
let configurationsForEntity = self.finalConfigurationsByEntityIdentifier[entityIdentifier] ?? []
if let configuration = configuration {
if configurationsForEntity.contains(configuration) {
return (store: self.persistentStoresByFinalConfiguration[configuration], isAmbiguous: false)
}
switch configurationsForEntity.count {
case 0:
else if !inferStoreIfPossible {
return (store: nil, isAmbiguous: false)
case 1 where inferStoreIfPossible:
return (store: self.persistentStoresByFinalConfiguration[configurationsForEntity.first!], isAmbiguous: false)
default:
return (store: nil, isAmbiguous: true)
}
}
switch configurationsForEntity.count {
case 0:
return (store: nil, isAmbiguous: false)
case 1 where inferStoreIfPossible:
return (store: self.persistentStoresByFinalConfiguration[configurationsForEntity.first!], isAmbiguous: false)
default:
return (store: nil, isAmbiguous: true)
}
}
internal func createPersistentStoreFromStorage(_ storage: StorageInterface, finalURL: URL?, finalStoreOptions: [AnyHashable: Any]?) throws -> NSPersistentStore {
internal func createPersistentStoreFromStorage(
_ storage: StorageInterface,
finalURL: URL?,
finalStoreOptions: [AnyHashable: Any]?
) throws -> NSPersistentStore {
let persistentStore = try self.coordinator.addPersistentStore(
ofType: type(of: storage).storeType,
@@ -510,8 +548,13 @@ public final class DataStack: Equatable {
options: finalStoreOptions
)
persistentStore.storageInterface = storage
self.storeMetadataUpdateQueue.async(flags: .barrier) {
do {
self.storeMetadataLock.lock()
defer {
self.storeMetadataLock.unlock()
}
let configurationName = persistentStore.configurationName
self.persistentStoresByFinalConfiguration[configurationName] = persistentStore
@@ -534,7 +577,9 @@ public final class DataStack: Equatable {
return persistentStore
}
internal func entityDescription(for entityIdentifier: Internals.EntityIdentifier) -> NSEntityDescription? {
internal func entityDescription(
for entityIdentifier: Internals.EntityIdentifier
) -> NSEntityDescription? {
return self.schemaHistory.entityDescriptionsByEntityIdentifier[entityIdentifier]
}

View File

@@ -0,0 +1,79 @@
//
// MigrationProgress.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.
//
import CoreData
import Foundation
// MARK: - MigrationProgress
/**
A `MigrationProgress` contains info on a `LocalStorage`'s setup progress.
- SeeAlso: DataStack.reactive.addStorage(_:)
- SeeAlso: DataStack.async.addStorage(_:)
*/
public enum MigrationProgress<T: LocalStorage> {
/**
The `LocalStorage` is currently being migrated
*/
case migrating(storage: T, progressObject: Progress)
/**
The `LocalStorage` has been added to the `DataStack` and is ready for reading and writing
*/
case finished(storage: T, migrationRequired: Bool)
/**
The fraction of the overall work completed by the migration. Returns a value between 0.0 and 1.0, inclusive.
*/
public var fractionCompleted: Double {
switch self {
case .migrating(_, let progressObject):
return progressObject.fractionCompleted
case .finished:
return 1
}
}
/**
Returns `true` if the storage was successfully added to the stack, `false` otherwise.
*/
public var isCompleted: Bool {
switch self {
case .migrating:
return false
case .finished:
return true
}
}
}