mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-09-10 11:51:49 +02:00
WIP
This commit is contained in:
@@ -32,7 +32,8 @@ import CoreData
|
||||
/**
|
||||
The `AsynchronousDataTransaction` provides an interface for `DynamicObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.perform(asynchronous:...)`.
|
||||
*/
|
||||
public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
@_nonSendable
|
||||
public nonisolated final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
|
||||
/**
|
||||
Cancels a transaction by throwing `CoreStoreError.userCancelled`.
|
||||
@@ -78,6 +79,23 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
return super.create(into)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject` or `CoreStoreObject`.
|
||||
|
||||
- parameter persistentID: the `DynamicObjectID` pertaining ot the `NSManagedObject` or `CoreStoreObject` type to be edited
|
||||
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
|
||||
*/
|
||||
public override func edit<O: DynamicObject>(
|
||||
_ persistentID: DynamicObjectID<O>?
|
||||
) -> O? {
|
||||
|
||||
Internals.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to update an entity for \(Internals.typeName(persistentID)) from an already committed \(Internals.typeName(self))."
|
||||
)
|
||||
return super.edit(persistentID)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject` or `CoreStoreObject`.
|
||||
|
||||
@@ -188,7 +206,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
}
|
||||
|
||||
internal func autoCommit(
|
||||
_ completion: @escaping @MainActor (
|
||||
_ completion: @escaping @MainActor @Sendable (
|
||||
_ hasChanges: Bool,
|
||||
_ error: CoreStoreError?
|
||||
) -> Void
|
||||
|
||||
@@ -110,6 +110,19 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
|
||||
return self.context.fetchExisting(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `DynamicObject` instance in the transaction's context from an `NSManagedObjectID`.
|
||||
|
||||
- parameter persistentID: the `DynamicObjectID` for the object
|
||||
- returns: the `DynamicObject` instance if the object exists in the transaction, or `nil` if not found.
|
||||
*/
|
||||
public func fetchExisting<O: DynamicObject>(
|
||||
_ persistentID: DynamicObjectID<O>
|
||||
) -> O? {
|
||||
|
||||
return self.context.fetchExisting(persistentID.managedObjectID)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `DynamicObject` instance in the transaction's context from an `NSManagedObjectID`.
|
||||
|
||||
@@ -136,6 +149,19 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
|
||||
return self.context.fetchExisting(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `DynamicObject` instances in the transaction's context from a list of `DynamicObjectID`.
|
||||
|
||||
- parameter objectIDs: the `DynamicObjectID` array for the objects
|
||||
- returns: the `DynamicObject` array for objects that exists in the transaction
|
||||
*/
|
||||
public func fetchExisting<O: DynamicObject, S: Sequence>(
|
||||
_ objectIDs: S
|
||||
) -> [O] where S.Iterator.Element == DynamicObjectID<O> {
|
||||
|
||||
return self.context.fetchExisting(objectIDs)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `DynamicObject` instances in the transaction's context from a list of `NSManagedObjectID`.
|
||||
|
||||
|
||||
@@ -115,6 +115,27 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject` or `CoreStoreObject`.
|
||||
|
||||
- parameter persistentID: the `DynamicObjectID` pertaining ot the `NSManagedObject` or `CoreStoreObject` type to be edited
|
||||
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
|
||||
*/
|
||||
public func edit<O: DynamicObject>(
|
||||
_ persistentID: DynamicObjectID<O>?
|
||||
) -> O? {
|
||||
|
||||
Internals.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to update an entity for \(Internals.typeName(persistentID)) outside its designated queue."
|
||||
)
|
||||
guard let persistentID = persistentID else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return self.context.fetchExisting(persistentID.managedObjectID)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject` or `CoreStoreObject`.
|
||||
|
||||
|
||||
@@ -652,7 +652,7 @@ extension ObjectPublisher: CustomDebugStringConvertible, CoreStoreDebugStringCon
|
||||
|
||||
return createFormattedString(
|
||||
"(", ")",
|
||||
("objectID", self.objectID()),
|
||||
("managedObjectID", self.cs_id()),
|
||||
("object", self.object as Any)
|
||||
)
|
||||
}
|
||||
@@ -677,7 +677,7 @@ extension ObjectSnapshot: CustomDebugStringConvertible, CoreStoreDebugStringConv
|
||||
|
||||
return createFormattedString(
|
||||
"(", ")",
|
||||
("objectID", self.objectID()),
|
||||
("managedObjectID", self.cs_id()),
|
||||
("dictionaryForValues", self.dictionaryForValues())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os
|
||||
|
||||
|
||||
// MARK: - CoreStoreDefaults
|
||||
@@ -94,6 +93,6 @@ public enum CoreStoreDefaults {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private static let defaultStackInstance: OSAllocatedUnfairLock<DataStack?> = .init(initialState: nil)
|
||||
private static let loggerInstance: OSAllocatedUnfairLock<(any CoreStoreLogger)?> = .init(initialState: nil)
|
||||
private static let defaultStackInstance: Internals.Mutex<DataStack?> = .init(nil)
|
||||
private static let loggerInstance: Internals.Mutex<(any CoreStoreLogger)?> = .init(nil)
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import Foundation
|
||||
/**
|
||||
All errors thrown from CoreStore are expressed in `CoreStoreError` enum values.
|
||||
*/
|
||||
public enum CoreStoreError: Error, CustomNSError, Hashable, @unchecked Sendable {
|
||||
public enum CoreStoreError: Error, CustomNSError, Hashable, Sendable {
|
||||
|
||||
/**
|
||||
A failure occured because of an unknown error.
|
||||
|
||||
@@ -65,6 +65,7 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
|
||||
Do not call this directly. This is exposed as public only as a required initializer.
|
||||
- Important: subclasses that need a custom initializer should override both `init(rawObject:)` and `init(asMeta:)`, and to call their corresponding super implementations.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public required init(rawObject: NSManagedObject) {
|
||||
|
||||
self.isMeta = false
|
||||
@@ -84,6 +85,7 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
|
||||
Do not call this directly. This is exposed as public only as a required initializer.
|
||||
- Important: subclasses that need a custom initializer should override both `init(rawObject:)` and `init(asMeta:)`, and to call their corresponding super implementations.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public required init(asMeta: Void) {
|
||||
|
||||
self.isMeta = true
|
||||
|
||||
@@ -257,20 +257,20 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let entitiesByConfiguration: [String: Set<DynamicEntity>]
|
||||
internal nonisolated(unsafe) let entitiesByConfiguration: [String: Set<DynamicEntity>]
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private static let barrierQueue = DispatchQueue.concurrent("com.coreStore.coreStoreDataModelBarrierQueue", qos: .userInteractive)
|
||||
|
||||
private let allEntities: Set<DynamicEntity>
|
||||
private nonisolated(unsafe) let allEntities: Set<DynamicEntity>
|
||||
|
||||
private var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
|
||||
private var customGettersSettersByEntity: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]] = [:]
|
||||
private var customInitializersByEntity: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomInitializer]] = [:]
|
||||
private var fieldCodersByEntity: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]] = [:]
|
||||
private weak var cachedRawModel: NSManagedObjectModel?
|
||||
private nonisolated(unsafe) var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
|
||||
private nonisolated(unsafe) var customGettersSettersByEntity: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]] = [:]
|
||||
private nonisolated(unsafe) var customInitializersByEntity: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomInitializer]] = [:]
|
||||
private nonisolated(unsafe) var fieldCodersByEntity: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]] = [:]
|
||||
private nonisolated(unsafe) weak var cachedRawModel: NSManagedObjectModel?
|
||||
|
||||
private func entityDescription(
|
||||
for entity: DynamicEntity,
|
||||
|
||||
@@ -32,7 +32,7 @@ import Foundation
|
||||
/**
|
||||
A `SchemaMappingProvider` that accepts custom mappings for some entities. Mappings of entities with no `CustomMapping` provided will be automatically calculated if possible.
|
||||
*/
|
||||
public class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
|
||||
public final class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
|
||||
|
||||
/**
|
||||
The source model version for the mapping.
|
||||
@@ -78,7 +78,7 @@ public class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
|
||||
/**
|
||||
Provides the type of mapping for an entity. Mappings of entities with no `CustomMapping` provided will be automatically calculated if possible. Any conflicts or ambiguity will raise an assertion.
|
||||
*/
|
||||
public enum CustomMapping: Hashable {
|
||||
public enum CustomMapping: Hashable, Sendable {
|
||||
|
||||
/**
|
||||
The `sourceEntity` is meant to be removed from the source `DynamicSchema` and should not be migrated to the destination `DynamicSchema`.
|
||||
@@ -105,7 +105,7 @@ public class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
|
||||
- parameter sourceObject: a proxy object representing the source entity. The properties can be accessed via keyPath.
|
||||
- parameter createDestinationObject: the closure to create the object for the destination entity. The `CustomMapping.inferredTransformation` method can be used directly as the `transformer` if the changes can be inferred (i.e. lightweight). The object is created lazily and executing the closure multiple times will return the same instance. The destination object's properties can be accessed and updated via keyPath.
|
||||
*/
|
||||
public typealias Transformer = (
|
||||
public typealias Transformer = @Sendable (
|
||||
_ sourceObject: UnsafeSourceObject,
|
||||
_ createDestinationObject: () -> UnsafeDestinationObject
|
||||
) throws(any Swift.Error) -> Void
|
||||
@@ -737,7 +737,10 @@ public class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
|
||||
.transformEntity(
|
||||
sourceEntity: sourceEntityName,
|
||||
destinationEntity: destinationEntityName,
|
||||
transformer: CustomMapping.inferredTransformation
|
||||
transformer: {
|
||||
|
||||
return try CustomMapping.inferredTransformation($0, $1)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,8 +42,9 @@ extension DataStack {
|
||||
public func publishObject<O: DynamicObject>(
|
||||
_ object: O
|
||||
) -> ObjectPublisher<O> {
|
||||
|
||||
return self.publishObject(object.cs_id())
|
||||
|
||||
let context = self.unsafeContext()
|
||||
return context.objectPublisher(managedObjectID: object.cs_id())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +58,7 @@ extension DataStack {
|
||||
) -> ObjectPublisher<O> {
|
||||
|
||||
let context = self.unsafeContext()
|
||||
return context.objectPublisher(objectID: objectID)
|
||||
return context.objectPublisher(managedObjectID: objectID.managedObjectID)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@preconcurrency import CoreData
|
||||
import os
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
@@ -50,7 +49,7 @@ extension DataStack {
|
||||
*/
|
||||
public func addStorage<T>(
|
||||
_ storage: T,
|
||||
completion: @escaping @MainActor (SetupResult<T>) -> Void
|
||||
completion: @escaping @MainActor @Sendable (SetupResult<T>) -> Void
|
||||
) {
|
||||
|
||||
self.coordinator.performAsynchronously {
|
||||
@@ -111,7 +110,7 @@ extension DataStack {
|
||||
*/
|
||||
public func addStorage<T: LocalStorage>(
|
||||
_ storage: T,
|
||||
completion: @escaping @MainActor (SetupResult<T>) -> Void
|
||||
completion: @escaping @MainActor @Sendable (SetupResult<T>) -> Void
|
||||
) -> Progress? {
|
||||
|
||||
let fileURL = storage.fileURL
|
||||
@@ -274,7 +273,7 @@ extension DataStack {
|
||||
*/
|
||||
public func upgradeStorageIfNeeded<T: LocalStorage>(
|
||||
_ storage: T,
|
||||
completion: @escaping @MainActor (MigrationResult) -> Void
|
||||
completion: @escaping @MainActor @Sendable (MigrationResult) -> Void
|
||||
) throws(CoreStoreError) -> Progress? {
|
||||
|
||||
return try self.coordinator.performSynchronously {
|
||||
@@ -386,7 +385,7 @@ extension DataStack {
|
||||
private func upgradeStorageIfNeeded<T: LocalStorage>(
|
||||
_ storage: T,
|
||||
metadata: [String: Any],
|
||||
completion: @escaping @MainActor (MigrationResult) -> Void
|
||||
completion: @escaping @MainActor @Sendable (MigrationResult) -> Void
|
||||
) -> Progress? {
|
||||
|
||||
guard let migrationSteps = self.computeMigrationFromStorage(storage, metadata: metadata) else {
|
||||
@@ -433,8 +432,8 @@ extension DataStack {
|
||||
}
|
||||
|
||||
let migrationTypes = migrationSteps.map { $0.migrationType }
|
||||
let migrationState: OSAllocatedUnfairLock<(migrationResult: MigrationResult?, cancelled: Bool)> = .init(
|
||||
initialState: (
|
||||
let migrationState: Internals.Mutex<(migrationResult: MigrationResult?, cancelled: Bool)> = .init(
|
||||
(
|
||||
migrationResult: nil,
|
||||
cancelled: false
|
||||
)
|
||||
@@ -451,6 +450,9 @@ extension DataStack {
|
||||
let childProgress = Progress(parent: progress, userInfo: nil)
|
||||
childProgress.totalUnitCount = 100
|
||||
|
||||
nonisolated(unsafe) let sourceModel = sourceModel
|
||||
nonisolated(unsafe) let destinationModel = destinationModel
|
||||
nonisolated(unsafe) let mappingModel = mappingModel
|
||||
operations.append(
|
||||
BlockOperation { [weak self] in
|
||||
|
||||
@@ -508,13 +510,11 @@ extension DataStack {
|
||||
operations.forEach { migrationOperation.addDependency($0) }
|
||||
migrationOperation.addExecutionBlock { () -> Void in
|
||||
|
||||
let migrationResult = migrationState.withLock { $0.migrationResult }
|
||||
DispatchQueue.main.async {
|
||||
|
||||
progress.setProgressHandler(nil)
|
||||
completion(
|
||||
migrationState.withLock { $0.migrationResult }
|
||||
?? .success(migrationTypes)
|
||||
)
|
||||
completion(migrationResult ?? .success(migrationTypes))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -620,7 +620,7 @@ extension DataStack {
|
||||
let estimatedTime: TimeInterval = 60 * 3 // 3 mins
|
||||
let interval: TimeInterval = 1
|
||||
let fakeTotalUnitCount: Float = 0.9 * Float(progress.totalUnitCount)
|
||||
let fakeProgress: OSAllocatedUnfairLock<Float> = .init(initialState: 0)
|
||||
let fakeProgress: Internals.Mutex<Float> = .init(0)
|
||||
|
||||
@Sendable
|
||||
func recursiveCheck() {
|
||||
@@ -656,9 +656,11 @@ extension DataStack {
|
||||
)
|
||||
)
|
||||
}
|
||||
timerQueue.sync {
|
||||
|
||||
fakeProgress.withLock({ $0 = 1.0 })
|
||||
withoutActuallyEscaping(timerQueue.sync) { escapingClosure in
|
||||
escapingClosure {
|
||||
|
||||
fakeProgress.withLock({ $0 = 1.0 })
|
||||
}
|
||||
}
|
||||
_ = try? storage.cs_finalizeStorageAndWait(soureModelHint: destinationModel)
|
||||
progress.completedUnitCount = progress.totalUnitCount
|
||||
|
||||
@@ -37,6 +37,7 @@ extension DataStack {
|
||||
- parameter object: the `DynamicObject` to observe changes from
|
||||
- returns: an `ObjectMonitor` that monitors changes to `object`
|
||||
*/
|
||||
@MainActor
|
||||
public func monitorObject<O: DynamicObject>(
|
||||
_ object: O
|
||||
) -> ObjectMonitor<O> {
|
||||
@@ -55,6 +56,7 @@ extension DataStack {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@MainActor
|
||||
public func monitorList<O>(
|
||||
_ from: From<O>,
|
||||
_ fetchClauses: FetchClause...
|
||||
@@ -70,6 +72,7 @@ extension DataStack {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@MainActor
|
||||
public func monitorList<O>(
|
||||
_ from: From<O>,
|
||||
_ fetchClauses: [FetchClause]
|
||||
@@ -107,6 +110,7 @@ extension DataStack {
|
||||
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
||||
- returns: a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified `FetchChainableBuilderType`
|
||||
*/
|
||||
@MainActor
|
||||
public func monitorList<B: FetchChainableBuilderType>(
|
||||
_ clauseChain: B
|
||||
) -> ListMonitor<B.ObjectType> {
|
||||
@@ -124,6 +128,7 @@ extension DataStack {
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
@MainActor
|
||||
public func monitorList<O>(
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||
_ from: From<O>,
|
||||
@@ -144,6 +149,7 @@ extension DataStack {
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
@MainActor
|
||||
public func monitorList<O>(
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||
_ from: From<O>,
|
||||
@@ -187,6 +193,7 @@ extension DataStack {
|
||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
||||
*/
|
||||
@MainActor
|
||||
public func monitorList<B: FetchChainableBuilderType>(
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<B.ObjectType>) -> Void,
|
||||
_ clauseChain: B
|
||||
@@ -207,6 +214,7 @@ extension DataStack {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@MainActor
|
||||
public func monitorSectionedList<O>(
|
||||
_ from: From<O>,
|
||||
_ sectionBy: SectionBy<O>,
|
||||
@@ -228,6 +236,7 @@ extension DataStack {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@MainActor
|
||||
public func monitorSectionedList<O>(
|
||||
_ from: From<O>,
|
||||
_ sectionBy: SectionBy<O>,
|
||||
@@ -268,6 +277,7 @@ extension DataStack {
|
||||
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
||||
- returns: a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified `SectionMonitorBuilderType`
|
||||
*/
|
||||
@MainActor
|
||||
public func monitorSectionedList<B: SectionMonitorBuilderType>(
|
||||
_ clauseChain: B
|
||||
) -> ListMonitor<B.ObjectType> {
|
||||
@@ -287,6 +297,7 @@ extension DataStack {
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
@MainActor
|
||||
public func monitorSectionedList<O>(
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||
_ from: From<O>,
|
||||
@@ -310,6 +321,7 @@ extension DataStack {
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
@MainActor
|
||||
public func monitorSectionedList<O>(
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||
_ from: From<O>,
|
||||
@@ -355,6 +367,7 @@ extension DataStack {
|
||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
||||
*/
|
||||
@MainActor
|
||||
public func monitorSectionedList<B: SectionMonitorBuilderType>(
|
||||
createAsynchronously: @escaping @Sendable (ListMonitor<B.ObjectType>) -> Void,
|
||||
_ clauseChain: B
|
||||
|
||||
@@ -46,6 +46,19 @@ extension DataStack: FetchableSource, QueryableSource {
|
||||
return self.mainContext.fetchExisting(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `DynamicObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
|
||||
|
||||
- parameter persistentID: the `DynamicObjectID` for the object
|
||||
- returns: the `DynamicObject` instance if the object exists in the `DataStack`, or `nil` if not found.
|
||||
*/
|
||||
public func fetchExisting<O: DynamicObject>(
|
||||
_ persistentID: DynamicObjectID<O>
|
||||
) -> O? {
|
||||
|
||||
return self.mainContext.fetchExisting(persistentID.managedObjectID)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `DynamicObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
|
||||
|
||||
@@ -72,6 +85,19 @@ extension DataStack: FetchableSource, QueryableSource {
|
||||
return self.mainContext.fetchExisting(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `DynamicObject` instances in the `DataStack`'s context from a list of `DynamicObjectID`.
|
||||
|
||||
- parameter objectIDs: the `DynamicObjectID` array for the objects
|
||||
- returns: the `DynamicObject` array for objects that exists in the `DataStack`
|
||||
*/
|
||||
public func fetchExisting<O: DynamicObject, S: Sequence>(
|
||||
_ objectIDs: S
|
||||
) -> [O] where S.Iterator.Element == DynamicObjectID<O> {
|
||||
|
||||
return self.mainContext.fetchExisting(objectIDs)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `DynamicObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`.
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ extension DataStack {
|
||||
_ transaction: AsynchronousDataTransaction
|
||||
) throws(any Swift.Error) -> T,
|
||||
sourceIdentifier: (any Sendable)? = nil,
|
||||
completion: @escaping @Sendable (AsynchronousDataTransaction.Result<T>) -> Void
|
||||
completion: @escaping @MainActor @Sendable (AsynchronousDataTransaction.Result<T>) -> Void
|
||||
) {
|
||||
|
||||
self.perform(
|
||||
@@ -67,8 +67,8 @@ extension DataStack {
|
||||
_ transaction: AsynchronousDataTransaction
|
||||
) throws(any Swift.Error) -> T,
|
||||
sourceIdentifier: (any Sendable)? = nil,
|
||||
success: @escaping @Sendable (sending T) -> Void,
|
||||
failure: @escaping @Sendable (CoreStoreError) -> Void
|
||||
success: @escaping @MainActor @Sendable (sending T) -> Void,
|
||||
failure: @escaping @MainActor @Sendable (CoreStoreError) -> Void
|
||||
) {
|
||||
|
||||
nonisolated(unsafe) let transaction = AsynchronousDataTransaction(
|
||||
@@ -186,6 +186,7 @@ extension DataStack {
|
||||
/**
|
||||
Refreshes all registered objects `NSManagedObject`s or `CoreStoreObject`s in the `DataStack`.
|
||||
*/
|
||||
@MainActor
|
||||
public func refreshAndMergeAllObjects() {
|
||||
|
||||
Internals.assert(
|
||||
|
||||
@@ -90,7 +90,7 @@ extension DataStack {
|
||||
|
||||
// MARK: - AddStorageSubscription
|
||||
|
||||
fileprivate final class AddStorageSubscription<S: Subscriber>: Subscription, @unchecked Sendable
|
||||
fileprivate final class AddStorageSubscription<S: Subscriber>: Subscription
|
||||
where S.Input == Output, S.Failure == CoreStoreError {
|
||||
|
||||
// MARK: FilePrivate
|
||||
@@ -116,14 +116,15 @@ extension DataStack {
|
||||
return
|
||||
}
|
||||
nonisolated(unsafe) var progress: Progress? = nil
|
||||
nonisolated(unsafe) weak let weakSelf = self as Optional
|
||||
progress = self.dataStack.addStorage(
|
||||
self.storage,
|
||||
completion: { [weak self] result in
|
||||
completion: { result in
|
||||
|
||||
progress?.setProgressHandler(nil)
|
||||
|
||||
guard
|
||||
let self = self,
|
||||
let self = weakSelf,
|
||||
let subscriber = self.subscriber
|
||||
else {
|
||||
|
||||
@@ -151,12 +152,12 @@ extension DataStack {
|
||||
)
|
||||
if let progress = progress {
|
||||
|
||||
Internals.mainActorImmediate { @MainActor [weak self] in
|
||||
Internals.mainActorImmediate { @MainActor in
|
||||
|
||||
progress.setProgressHandler { progress in
|
||||
|
||||
guard
|
||||
let self = self,
|
||||
let self = weakSelf,
|
||||
let subscriber = self.subscriber
|
||||
else {
|
||||
|
||||
|
||||
+48
-49
@@ -32,7 +32,7 @@ import CoreData
|
||||
/**
|
||||
The `DataStack` encapsulates the data model for the Core Data stack. Each `DataStack` can have multiple data stores, usually specified as a "Configuration" in the model editor. Behind the scenes, the DataStack manages its own `NSPersistentStoreCoordinator`, a root `NSManagedObjectContext` for disk saves, and a shared `NSManagedObjectContext` designed as a read-only model interface for `NSManagedObjects`.
|
||||
*/
|
||||
public final class DataStack: Equatable, @unchecked Sendable {
|
||||
public final class DataStack: Equatable, Sendable {
|
||||
|
||||
/**
|
||||
The resolved application name, used by the `DataStack` as the default Xcode model name (.xcdatamodel filename) if not explicitly provided.
|
||||
@@ -398,7 +398,7 @@ public final class DataStack: Equatable, @unchecked Sendable {
|
||||
- parameter completion: the closure to execute after all persistent stores are removed
|
||||
*/
|
||||
public func unsafeRemoveAllPersistentStores(
|
||||
completion: @escaping @MainActor () -> Void = {}
|
||||
completion: @escaping @MainActor @Sendable () -> Void = {}
|
||||
) {
|
||||
|
||||
let coordinator = self.coordinator
|
||||
@@ -466,7 +466,6 @@ public final class DataStack: Equatable, @unchecked Sendable {
|
||||
internal let mainContext: NSManagedObjectContext
|
||||
internal let schemaHistory: SchemaHistory
|
||||
internal let childTransactionQueue = DispatchQueue.serial("com.coreStore.dataStack.childTransactionQueue", qos: .utility)
|
||||
internal let storeMetadataLock: NSRecursiveLock = .init()
|
||||
internal let migrationQueue: OperationQueue = Internals.with {
|
||||
|
||||
let migrationQueue = OperationQueue()
|
||||
@@ -487,15 +486,14 @@ public final class DataStack: Equatable, @unchecked Sendable {
|
||||
}
|
||||
|
||||
internal func persistentStores(
|
||||
for entityIdentifier: Internals.EntityIdentifier
|
||||
for entityIdentifier: sending Internals.EntityIdentifier
|
||||
) -> [NSPersistentStore]? {
|
||||
|
||||
self.storeMetadataLock.lock()
|
||||
defer {
|
||||
self.storeMetadataLock.unlock()
|
||||
return self.storeMetadataLock.withLockUnchecked { metadata in
|
||||
|
||||
return metadata.finalConfigurationsByEntityIdentifier[entityIdentifier]?
|
||||
.map({ metadata.persistentStoresByFinalConfiguration[$0]! }) ?? []
|
||||
}
|
||||
return self.finalConfigurationsByEntityIdentifier[entityIdentifier]?
|
||||
.map({ self.persistentStoresByFinalConfiguration[$0]! }) ?? []
|
||||
}
|
||||
|
||||
internal func persistentStore(
|
||||
@@ -504,34 +502,33 @@ public final class DataStack: Equatable, @unchecked Sendable {
|
||||
inferStoreIfPossible: Bool
|
||||
) -> (store: NSPersistentStore?, isAmbiguous: Bool) {
|
||||
|
||||
self.storeMetadataLock.lock()
|
||||
defer {
|
||||
self.storeMetadataLock.unlock()
|
||||
}
|
||||
let configurationsForEntity = self.finalConfigurationsByEntityIdentifier[entityIdentifier] ?? []
|
||||
if let configuration = configuration {
|
||||
return self.storeMetadataLock.withLockUnchecked { metadata in
|
||||
|
||||
let configurationsForEntity = metadata.finalConfigurationsByEntityIdentifier[entityIdentifier] ?? []
|
||||
if let configuration = configuration {
|
||||
|
||||
if configurationsForEntity.contains(configuration) {
|
||||
if configurationsForEntity.contains(configuration) {
|
||||
|
||||
return (store: self.persistentStoresByFinalConfiguration[configuration], isAmbiguous: false)
|
||||
return (store: metadata.persistentStoresByFinalConfiguration[configuration], isAmbiguous: false)
|
||||
}
|
||||
else if !inferStoreIfPossible {
|
||||
|
||||
return (store: nil, isAmbiguous: false)
|
||||
}
|
||||
}
|
||||
else if !inferStoreIfPossible {
|
||||
|
||||
switch configurationsForEntity.count {
|
||||
|
||||
case 0:
|
||||
return (store: nil, isAmbiguous: false)
|
||||
|
||||
case 1 where inferStoreIfPossible:
|
||||
return (store: metadata.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(
|
||||
@@ -550,26 +547,24 @@ public final class DataStack: Equatable, @unchecked Sendable {
|
||||
|
||||
do {
|
||||
|
||||
self.storeMetadataLock.lock()
|
||||
defer {
|
||||
self.storeMetadataLock.unlock()
|
||||
}
|
||||
|
||||
let configurationName = persistentStore.configurationName
|
||||
self.persistentStoresByFinalConfiguration[configurationName] = persistentStore
|
||||
for entityDescription in (self.coordinator.managedObjectModel.entities(forConfigurationName: configurationName) ?? []) {
|
||||
self.storeMetadataLock.withLock { metadata in
|
||||
|
||||
let managedObjectClassName = entityDescription.managedObjectClassName!
|
||||
Internals.assert(
|
||||
NSClassFromString(managedObjectClassName) != nil,
|
||||
"The class \(Internals.typeName(managedObjectClassName)) for the entity \(Internals.typeName(entityDescription.name)) does not exist. Check if the subclass type and module name are properly configured."
|
||||
)
|
||||
let entityIdentifier = Internals.EntityIdentifier(entityDescription)
|
||||
if self.finalConfigurationsByEntityIdentifier[entityIdentifier] == nil {
|
||||
let configurationName = persistentStore.configurationName
|
||||
metadata.persistentStoresByFinalConfiguration[configurationName] = persistentStore
|
||||
for entityDescription in (self.coordinator.managedObjectModel.entities(forConfigurationName: configurationName) ?? []) {
|
||||
|
||||
self.finalConfigurationsByEntityIdentifier[entityIdentifier] = []
|
||||
let managedObjectClassName = entityDescription.managedObjectClassName!
|
||||
Internals.assert(
|
||||
NSClassFromString(managedObjectClassName) != nil,
|
||||
"The class \(Internals.typeName(managedObjectClassName)) for the entity \(Internals.typeName(entityDescription.name)) does not exist. Check if the subclass type and module name are properly configured."
|
||||
)
|
||||
let entityIdentifier = Internals.EntityIdentifier(entityDescription)
|
||||
if metadata.finalConfigurationsByEntityIdentifier[entityIdentifier] == nil {
|
||||
|
||||
metadata.finalConfigurationsByEntityIdentifier[entityIdentifier] = []
|
||||
}
|
||||
metadata.finalConfigurationsByEntityIdentifier[entityIdentifier]?.insert(configurationName)
|
||||
}
|
||||
self.finalConfigurationsByEntityIdentifier[entityIdentifier]?.insert(configurationName)
|
||||
}
|
||||
}
|
||||
storage.cs_didAddToDataStack(self)
|
||||
@@ -586,8 +581,12 @@ public final class DataStack: Equatable, @unchecked Sendable {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var persistentStoresByFinalConfiguration = [String: NSPersistentStore]()
|
||||
private var finalConfigurationsByEntityIdentifier = [Internals.EntityIdentifier: Set<String>]()
|
||||
private let storeMetadataLock: Internals.Mutex<
|
||||
(
|
||||
persistentStoresByFinalConfiguration: [String: NSPersistentStore],
|
||||
finalConfigurationsByEntityIdentifier: [Internals.EntityIdentifier: Set<String>]
|
||||
)
|
||||
> = .init(([:], [:]))
|
||||
|
||||
deinit {
|
||||
|
||||
|
||||
@@ -218,6 +218,7 @@ extension DiffableDataSource {
|
||||
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
|
||||
|
||||
return self.dispatcher.itemIdentifier(for: indexPath)
|
||||
.map(O.ObjectID.init(managedObjectID:))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,7 +229,7 @@ extension DiffableDataSource {
|
||||
*/
|
||||
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
|
||||
|
||||
return self.dispatcher.indexPath(for: itemID)
|
||||
return self.dispatcher.indexPath(for: itemID.managedObjectID)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,6 +255,11 @@ extension DiffableDataSource {
|
||||
// MARK: Internal
|
||||
|
||||
internal let dispatcher: Internals.DiffableDataUIDispatcher<O>
|
||||
|
||||
internal func itemID(for indexPath: IndexPath) -> NSManagedObjectID? {
|
||||
|
||||
return self.dispatcher.itemIdentifier(for: indexPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ extension DiffableDataSource {
|
||||
cellForItemAt indexPath: IndexPath
|
||||
) -> UICollectionViewCell {
|
||||
|
||||
guard let objectID = self.itemID(for: indexPath) else {
|
||||
guard let objectID: NSManagedObjectID = self.itemID(for: indexPath) else {
|
||||
|
||||
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ extension DiffableDataSource {
|
||||
public init(
|
||||
tableView: UITableView,
|
||||
dataStack: DataStack,
|
||||
cellProvider: @escaping @MainActor (UITableView, IndexPath, O) -> UITableViewCell?
|
||||
cellProvider: @escaping @MainActor @Sendable (UITableView, IndexPath, O) -> UITableViewCell?
|
||||
) {
|
||||
|
||||
self.cellProvider = cellProvider
|
||||
@@ -150,7 +150,7 @@ extension DiffableDataSource {
|
||||
cellForRowAt indexPath: IndexPath
|
||||
) -> UITableViewCell {
|
||||
|
||||
guard let objectID = self.itemID(for: indexPath) else {
|
||||
guard let objectID: NSManagedObjectID = self.itemID(for: indexPath) else {
|
||||
|
||||
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
|
||||
}
|
||||
|
||||
@@ -220,7 +220,10 @@ extension DiffableDataSource.Target {
|
||||
}
|
||||
},
|
||||
animated: animated,
|
||||
completion: group.leave
|
||||
completion: {
|
||||
|
||||
group.leave()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+41
-13
@@ -32,16 +32,19 @@ import CoreData
|
||||
/**
|
||||
All CoreStore's utilities are designed around `DynamicObject` instances. `NSManagedObject` and `CoreStoreObject` instances all conform to `DynamicObject`.
|
||||
*/
|
||||
public protocol DynamicObject: AnyObject {
|
||||
@_nonSendable
|
||||
public nonisolated protocol DynamicObject: AnyObject, SendableMetatype {
|
||||
|
||||
/**
|
||||
The object ID for this instance
|
||||
*/
|
||||
typealias ObjectID = NSManagedObjectID
|
||||
typealias ObjectID = DynamicObjectID<Self>
|
||||
|
||||
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
static func cs_forceCreate(
|
||||
entityDescription: NSEntityDescription,
|
||||
into context: NSManagedObjectContext,
|
||||
@@ -51,14 +54,16 @@ public protocol DynamicObject: AnyObject {
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
static func cs_snapshotDictionary(
|
||||
id: ObjectID,
|
||||
managedObjectID: NSManagedObjectID,
|
||||
context: NSManagedObjectContext
|
||||
) -> [String: Any]?
|
||||
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
static func cs_fromRaw(
|
||||
object: NSManagedObject
|
||||
) -> Self
|
||||
@@ -66,6 +71,7 @@ public protocol DynamicObject: AnyObject {
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
static func cs_matches(
|
||||
object: NSManagedObject
|
||||
) -> Bool
|
||||
@@ -73,16 +79,26 @@ public protocol DynamicObject: AnyObject {
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
func cs_toRaw() -> NSManagedObject
|
||||
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
func cs_id() -> ObjectID
|
||||
@_spi(Internals)
|
||||
func cs_id() -> NSManagedObjectID
|
||||
}
|
||||
|
||||
extension DynamicObject {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
public func persistentID() -> DynamicObjectID<Self> {
|
||||
|
||||
return .init(managedObjectID: self.cs_id())
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func runtimeType() -> Self.Type {
|
||||
@@ -99,6 +115,7 @@ extension NSManagedObject: DynamicObject {
|
||||
|
||||
// MARK: DynamicObject
|
||||
|
||||
@_spi(Internals)
|
||||
public class func cs_forceCreate(
|
||||
entityDescription: NSEntityDescription,
|
||||
into context: NSManagedObjectContext,
|
||||
@@ -112,13 +129,14 @@ extension NSManagedObject: DynamicObject {
|
||||
}
|
||||
return object
|
||||
}
|
||||
|
||||
|
||||
@_spi(Internals)
|
||||
public class func cs_snapshotDictionary(
|
||||
id: ObjectID,
|
||||
managedObjectID: NSManagedObjectID,
|
||||
context: NSManagedObjectContext
|
||||
) -> [String: Any]? {
|
||||
|
||||
guard let object = context.fetchExisting(id) as NSManagedObject? else {
|
||||
guard let object = context.fetchExisting(managedObjectID) as NSManagedObject? else {
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -131,6 +149,7 @@ extension NSManagedObject: DynamicObject {
|
||||
return dictionary
|
||||
}
|
||||
|
||||
@_spi(Internals)
|
||||
public class func cs_fromRaw(object: NSManagedObject) -> Self {
|
||||
|
||||
#if swift(>=5.9)
|
||||
@@ -143,6 +162,7 @@ extension NSManagedObject: DynamicObject {
|
||||
#endif
|
||||
}
|
||||
|
||||
@_spi(Internals)
|
||||
public static func cs_matches(
|
||||
object: NSManagedObject
|
||||
) -> Bool {
|
||||
@@ -150,12 +170,14 @@ extension NSManagedObject: DynamicObject {
|
||||
return object.isKind(of: self)
|
||||
}
|
||||
|
||||
@_spi(Internals)
|
||||
public func cs_toRaw() -> NSManagedObject {
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
public func cs_id() -> ObjectID {
|
||||
@_spi(Internals)
|
||||
public func cs_id() -> NSManagedObjectID {
|
||||
|
||||
return self.objectID
|
||||
}
|
||||
@@ -168,6 +190,7 @@ extension CoreStoreObject {
|
||||
|
||||
// MARK: DynamicObject
|
||||
|
||||
@_spi(Internals)
|
||||
public class func cs_forceCreate(
|
||||
entityDescription: NSEntityDescription,
|
||||
into context: NSManagedObjectContext,
|
||||
@@ -182,9 +205,10 @@ extension CoreStoreObject {
|
||||
}
|
||||
return self.cs_fromRaw(object: object)
|
||||
}
|
||||
|
||||
|
||||
@_spi(Internals)
|
||||
public class func cs_snapshotDictionary(
|
||||
id: ObjectID,
|
||||
managedObjectID: NSManagedObjectID,
|
||||
context: NSManagedObjectContext
|
||||
) -> [String: Any]? {
|
||||
|
||||
@@ -240,7 +264,7 @@ extension CoreStoreObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
guard let object = context.fetchExisting(id) as CoreStoreObject? else {
|
||||
guard let object = context.fetchExisting(managedObjectID) as CoreStoreObject? else {
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -253,7 +277,7 @@ extension CoreStoreObject {
|
||||
else {
|
||||
|
||||
guard
|
||||
let object = context.fetchExisting(id) as CoreStoreObject?,
|
||||
let object = context.fetchExisting(managedObjectID) as CoreStoreObject?,
|
||||
let rawObject = object.rawObject,
|
||||
!rawObject.isDeleted
|
||||
else {
|
||||
@@ -292,6 +316,7 @@ extension CoreStoreObject {
|
||||
return values
|
||||
}
|
||||
|
||||
@_spi(Internals)
|
||||
public class func cs_fromRaw(object: NSManagedObject) -> Self {
|
||||
|
||||
if let coreStoreObject = object.coreStoreObject {
|
||||
@@ -310,6 +335,7 @@ extension CoreStoreObject {
|
||||
return coreStoreObject
|
||||
}
|
||||
|
||||
@_spi(Internals)
|
||||
public static func cs_matches(
|
||||
object: NSManagedObject
|
||||
) -> Bool {
|
||||
@@ -321,12 +347,14 @@ extension CoreStoreObject {
|
||||
return (self as AnyClass).isSubclass(of: type as AnyClass)
|
||||
}
|
||||
|
||||
@_spi(Internals)
|
||||
public func cs_toRaw() -> NSManagedObject {
|
||||
|
||||
return self.rawObject!
|
||||
}
|
||||
|
||||
public func cs_id() -> ObjectID {
|
||||
@_spi(Internals)
|
||||
public func cs_id() -> NSManagedObjectID {
|
||||
|
||||
return self.rawObject!.objectID
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
//
|
||||
// DynamicObject.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2026 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
|
||||
|
||||
|
||||
// MARK: - DynamicObjectID
|
||||
|
||||
public struct DynamicObjectID<O: DynamicObject>: Hashable, ObjectRepresentation, Sendable {
|
||||
|
||||
/**
|
||||
The associated `NSManagedObject` or `CoreStoreObject` entity class
|
||||
*/
|
||||
public let entityClass: O.Type
|
||||
|
||||
public init(
|
||||
managedObjectID: NSManagedObjectID
|
||||
) {
|
||||
|
||||
self.init(
|
||||
entityClass: O.self,
|
||||
managedObjectID: managedObjectID
|
||||
)
|
||||
}
|
||||
|
||||
public init(
|
||||
entityClass: O.Type,
|
||||
managedObjectID: NSManagedObjectID
|
||||
) {
|
||||
|
||||
Internals.assert(
|
||||
Internals.EntityIdentifier(entityClass) == Internals.EntityIdentifier(managedObjectID.entity),
|
||||
"The \(Internals.typeName(managedObjectID)) does not belong to the entity \(Internals.typeName(entityClass))."
|
||||
)
|
||||
|
||||
self.entityClass = entityClass
|
||||
self.managedObjectID = managedObjectID
|
||||
}
|
||||
|
||||
|
||||
// MARK: Equatable
|
||||
|
||||
public static func ==(lhs: Self, rhs: Self) -> Bool {
|
||||
|
||||
return lhs.managedObjectID == rhs.managedObjectID
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
|
||||
hasher.combine(self.managedObjectID)
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyObjectRepresentation
|
||||
|
||||
@_spi(Internals)
|
||||
public func cs_dataStack() -> DataStack? {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@_spi(Internals)
|
||||
public func cs_id() -> NSManagedObjectID {
|
||||
|
||||
return self.managedObjectID
|
||||
}
|
||||
|
||||
|
||||
// MARK: ObjectRepresentation
|
||||
|
||||
public typealias ObjectType = O
|
||||
|
||||
public func asPublisher(in dataStack: DataStack) -> ObjectPublisher<ObjectType> {
|
||||
|
||||
let context = dataStack.unsafeContext()
|
||||
return context.objectPublisher(managedObjectID: self.managedObjectID)
|
||||
}
|
||||
|
||||
public func asReadOnly(in dataStack: DataStack) -> ObjectType? {
|
||||
|
||||
let context = dataStack.unsafeContext()
|
||||
return context.fetchExisting(self.managedObjectID)
|
||||
}
|
||||
|
||||
public func asEditable(in transaction: BaseDataTransaction) -> ObjectType? {
|
||||
|
||||
let context = transaction.unsafeContext()
|
||||
return context.fetchExisting(self.managedObjectID)
|
||||
}
|
||||
|
||||
public func asSnapshot(in dataStack: DataStack) -> ObjectSnapshot<ObjectType>? {
|
||||
|
||||
let context = dataStack.unsafeContext()
|
||||
return ObjectSnapshot<ObjectType>(managedObjectID: self.managedObjectID, context: context)
|
||||
}
|
||||
|
||||
public func asSnapshot(in transaction: BaseDataTransaction) -> ObjectSnapshot<ObjectType>? {
|
||||
|
||||
let context = transaction.unsafeContext()
|
||||
return ObjectSnapshot<ObjectType>(managedObjectID: self.managedObjectID, context: context)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let managedObjectID: NSManagedObjectID
|
||||
|
||||
internal var entityIdentifier: Internals.EntityIdentifier {
|
||||
|
||||
return Internals.EntityIdentifier(self.managedObjectID.entity)
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ import Foundation
|
||||
- `UnsafeDataModelSchema`: describes models loaded directly from an existing `NSManagedObjectModel`. It is not advisable to continue using this model as its metadata are not available to CoreStore.
|
||||
- `CoreStoreSchema`: describes models written for `CoreStoreObject` Swift class declarations.
|
||||
*/
|
||||
public protocol DynamicSchema {
|
||||
public protocol DynamicSchema: Sendable {
|
||||
|
||||
/**
|
||||
The version string for this model schema.
|
||||
@@ -45,5 +45,6 @@ public protocol DynamicSchema {
|
||||
/**
|
||||
Do not call this directly. The `NSManagedObjectModel` for this schema may be created lazily and using this method directly may affect the integrity of the model.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
func rawModel() -> NSManagedObjectModel
|
||||
}
|
||||
|
||||
@@ -203,31 +203,37 @@ public /*abstract*/ class DynamicEntity: Hashable {
|
||||
/**
|
||||
Do not use directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public let type: DynamicObject.Type
|
||||
|
||||
/**
|
||||
Do not use directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public let entityName: EntityName
|
||||
|
||||
/**
|
||||
Do not use directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public let isAbstract: Bool
|
||||
|
||||
/**
|
||||
Do not use directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public let versionHashModifier: String?
|
||||
|
||||
/**
|
||||
Do not use directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public let indexes: [[KeyPathString]]
|
||||
|
||||
/**
|
||||
Do not use directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public let uniqueConstraints: [[KeyPathString]]
|
||||
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ public protocol FieldRelationshipType {
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
static func cs_toReturnType(
|
||||
from value: NativeValueType?
|
||||
) -> Self
|
||||
@@ -64,6 +65,7 @@ public protocol FieldRelationshipType {
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
static func cs_toPublishedType(
|
||||
from value: SnapshotValueType,
|
||||
in context: NSManagedObjectContext
|
||||
@@ -72,6 +74,7 @@ public protocol FieldRelationshipType {
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
static func cs_toNativeType(
|
||||
from value: Self
|
||||
) -> NativeValueType?
|
||||
@@ -79,6 +82,7 @@ public protocol FieldRelationshipType {
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
static func cs_toSnapshotType(
|
||||
from value: PublishedType
|
||||
) -> SnapshotValueType
|
||||
@@ -86,8 +90,9 @@ public protocol FieldRelationshipType {
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
static func cs_valueForSnapshot(
|
||||
from objectIDs: [DestinationObjectType.ObjectID]
|
||||
from objectIDs: [NSManagedObjectID]
|
||||
) -> SnapshotValueType
|
||||
}
|
||||
|
||||
@@ -138,7 +143,7 @@ extension Optional: FieldRelationshipType, FieldRelationshipToOneType where Wrap
|
||||
in context: NSManagedObjectContext
|
||||
) -> PublishedType {
|
||||
|
||||
return value.map(context.objectPublisher(objectID:))
|
||||
return value.map(context.objectPublisher(managedObjectID:))
|
||||
}
|
||||
|
||||
public static func cs_toNativeType(
|
||||
@@ -152,11 +157,11 @@ extension Optional: FieldRelationshipType, FieldRelationshipToOneType where Wrap
|
||||
from value: PublishedType
|
||||
) -> SnapshotValueType {
|
||||
|
||||
return value?.objectID()
|
||||
return value?.cs_id()
|
||||
}
|
||||
|
||||
public static func cs_valueForSnapshot(
|
||||
from objectIDs: [DestinationObjectType.ObjectID]
|
||||
from objectIDs: [NSManagedObjectID]
|
||||
) -> SnapshotValueType {
|
||||
|
||||
return objectIDs.first
|
||||
@@ -194,7 +199,7 @@ extension Array: FieldRelationshipType, FieldRelationshipToManyType, FieldRelati
|
||||
in context: NSManagedObjectContext
|
||||
) -> PublishedType {
|
||||
|
||||
return value.map(context.objectPublisher(objectID:))
|
||||
return value.map(context.objectPublisher(managedObjectID:))
|
||||
}
|
||||
|
||||
public static func cs_toNativeType(
|
||||
@@ -208,11 +213,11 @@ extension Array: FieldRelationshipType, FieldRelationshipToManyType, FieldRelati
|
||||
from value: PublishedType
|
||||
) -> SnapshotValueType {
|
||||
|
||||
return value.map({ $0.objectID() })
|
||||
return value.map({ $0.cs_id() })
|
||||
}
|
||||
|
||||
public static func cs_valueForSnapshot(
|
||||
from objectIDs: [DestinationObjectType.ObjectID]
|
||||
from objectIDs: [NSManagedObjectID]
|
||||
) -> SnapshotValueType {
|
||||
|
||||
return objectIDs
|
||||
@@ -250,7 +255,7 @@ extension Set: FieldRelationshipType, FieldRelationshipToManyType, FieldRelation
|
||||
in context: NSManagedObjectContext
|
||||
) -> PublishedType {
|
||||
|
||||
return PublishedType(value.map(context.objectPublisher(objectID:)))
|
||||
return PublishedType(value.map(context.objectPublisher(managedObjectID:)))
|
||||
}
|
||||
|
||||
public static func cs_toNativeType(
|
||||
@@ -264,11 +269,11 @@ extension Set: FieldRelationshipType, FieldRelationshipToManyType, FieldRelation
|
||||
from value: PublishedType
|
||||
) -> SnapshotValueType {
|
||||
|
||||
return SnapshotValueType(value.map({ $0.objectID() }))
|
||||
return SnapshotValueType(value.map({ $0.cs_id() }))
|
||||
}
|
||||
|
||||
public static func cs_valueForSnapshot(
|
||||
from objectIDs: [DestinationObjectType.ObjectID]
|
||||
from objectIDs: [NSManagedObjectID]
|
||||
) -> SnapshotValueType {
|
||||
|
||||
return .init(objectIDs)
|
||||
|
||||
@@ -41,6 +41,7 @@ public protocol FieldOptionalType: ExpressibleByNilLiteral {
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
var cs_wrappedValue: Wrapped? { get }
|
||||
}
|
||||
|
||||
|
||||
@@ -59,9 +59,9 @@ extension ForEach where Content: View {
|
||||
public init<O: DynamicObject>(
|
||||
_ objectSnapshots: Data,
|
||||
@ViewBuilder content: @escaping (ObjectSnapshot<O>) -> Content
|
||||
) where Data.Element == ObjectSnapshot<O>, ID == O.ObjectID {
|
||||
) where Data.Element == ObjectSnapshot<O>, ID == NSManagedObjectID {
|
||||
|
||||
self.init(objectSnapshots, id: \.cs_objectID, content: content)
|
||||
self.init(objectSnapshots, id: \.managedObjectID, content: content)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,9 +89,9 @@ extension ForEach where Content: View {
|
||||
public init<O: DynamicObject>(
|
||||
objectIn listSnapshot: Data,
|
||||
@ViewBuilder content: @escaping (ObjectPublisher<O>) -> Content
|
||||
) where Data == ListSnapshot<O>, ID == O.ObjectID {
|
||||
) where Data == ListSnapshot<O>, ID == NSManagedObjectID {
|
||||
|
||||
self.init(listSnapshot, id: \.cs_objectID, content: content)
|
||||
self.init(listSnapshot, id: \.managedObjectID, content: content)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,9 +118,9 @@ extension ForEach where Content: View {
|
||||
public init<O: DynamicObject>(
|
||||
objectIn objectPublishers: Data,
|
||||
@ViewBuilder content: @escaping (ObjectPublisher<O>) -> Content
|
||||
) where Data.Element == ObjectPublisher<O>, ID == O.ObjectID {
|
||||
) where Data.Element == ObjectPublisher<O>, ID == NSManagedObjectID {
|
||||
|
||||
self.init(objectPublishers, id: \.cs_objectID, content: content)
|
||||
self.init(objectPublishers, id: \.managedObjectID, content: content)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,9 +191,9 @@ extension ForEach where Content: View {
|
||||
public init<O: DynamicObject>(
|
||||
objectIn sectionInfo: Data,
|
||||
@ViewBuilder content: @escaping (ObjectPublisher<O>) -> Content
|
||||
) where Data == ListSnapshot<O>.SectionInfo, ID == O.ObjectID {
|
||||
) where Data == ListSnapshot<O>.SectionInfo, ID == NSManagedObjectID {
|
||||
|
||||
self.init(sectionInfo, id: \.cs_objectID, content: content)
|
||||
self.init(sectionInfo, id: \.managedObjectID, content: content)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -148,7 +148,7 @@ extension From {
|
||||
- returns: a `FetchChainBuilder` with closure where the `NSFetchRequest` may be configured
|
||||
*/
|
||||
public func tweak(
|
||||
_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void
|
||||
_ fetchRequest: @escaping @Sendable (NSFetchRequest<NSFetchRequestResult>) -> Void
|
||||
) -> FetchChainBuilder<O> {
|
||||
|
||||
return self.fetchChain(appending: Tweak(fetchRequest))
|
||||
@@ -273,7 +273,7 @@ extension From {
|
||||
*/
|
||||
public func sectionBy(
|
||||
_ sectionKeyPath: KeyPathString,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return .init(
|
||||
@@ -348,7 +348,7 @@ extension From where O: NSManagedObject {
|
||||
*/
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, T>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -434,7 +434,7 @@ extension From where O: CoreStoreObject {
|
||||
*/
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -453,7 +453,7 @@ extension From where O: CoreStoreObject {
|
||||
*/
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -472,7 +472,7 @@ extension From where O: CoreStoreObject {
|
||||
*/
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -604,7 +604,7 @@ extension FetchChainBuilder {
|
||||
- returns: a new `FetchChainBuilder` containing the `Tweak` clause
|
||||
*/
|
||||
public func tweak(
|
||||
_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void
|
||||
_ fetchRequest: @escaping @Sendable (NSFetchRequest<NSFetchRequestResult>) -> Void
|
||||
) -> FetchChainBuilder<O> {
|
||||
|
||||
return self.fetchChain(appending: Tweak(fetchRequest))
|
||||
@@ -795,7 +795,7 @@ extension QueryChainBuilder {
|
||||
- returns: a new `QueryChainBuilder` containing the `Tweak` clause
|
||||
*/
|
||||
public func tweak(
|
||||
_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void
|
||||
_ fetchRequest: @escaping @Sendable (NSFetchRequest<NSFetchRequestResult>) -> Void
|
||||
) -> QueryChainBuilder<O, R> {
|
||||
|
||||
return self.queryChain(appending: Tweak(fetchRequest))
|
||||
@@ -1093,7 +1093,7 @@ extension SectionMonitorChainBuilder {
|
||||
- returns: a new `SectionMonitorChainBuilder` containing the `Tweak` clause
|
||||
*/
|
||||
public func tweak(
|
||||
_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void
|
||||
_ fetchRequest: @escaping @Sendable (NSFetchRequest<NSFetchRequestResult>) -> Void
|
||||
) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionMonitorChain(appending: Tweak(fetchRequest))
|
||||
@@ -1232,7 +1232,7 @@ extension From where O: CoreStoreObject {
|
||||
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -1243,7 +1243,7 @@ extension From where O: CoreStoreObject {
|
||||
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -1254,7 +1254,7 @@ extension From where O: CoreStoreObject {
|
||||
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -1265,7 +1265,7 @@ extension From where O: CoreStoreObject {
|
||||
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
|
||||
+3
-3
@@ -39,7 +39,7 @@ import CoreData
|
||||
let person = transaction.fetchOne(From<Person>("Configuration1"))
|
||||
```
|
||||
*/
|
||||
public struct From<O: DynamicObject> {
|
||||
public struct From<O: DynamicObject>: Sendable {
|
||||
|
||||
/**
|
||||
The associated `NSManagedObject` or `CoreStoreObject` entity class
|
||||
@@ -162,12 +162,12 @@ public struct From<O: DynamicObject> {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let findPersistentStores: (_ context: NSManagedObjectContext) -> [NSPersistentStore]?
|
||||
internal let findPersistentStores: @Sendable (_ context: NSManagedObjectContext) -> [NSPersistentStore]?
|
||||
|
||||
internal init(
|
||||
entityClass: O.Type,
|
||||
configurations: [ModelConfiguration]?,
|
||||
findPersistentStores: @escaping (
|
||||
findPersistentStores: @escaping @Sendable (
|
||||
_ context: NSManagedObjectContext
|
||||
) -> [NSPersistentStore]?
|
||||
) {
|
||||
|
||||
@@ -32,7 +32,7 @@ import CoreData
|
||||
/**
|
||||
The `GroupBy` clause specifies that the result of a query be grouped accoording to the specified key path.
|
||||
*/
|
||||
public struct GroupBy<O: DynamicObject>: GroupByClause, QueryClause, Hashable {
|
||||
public struct GroupBy<O: DynamicObject>: GroupByClause, QueryClause, Hashable, Sendable {
|
||||
|
||||
/**
|
||||
Initializes a `GroupBy` clause with an empty list of key path strings
|
||||
|
||||
@@ -31,7 +31,7 @@ import CoreData
|
||||
/**
|
||||
A storage interface that is backed only in memory.
|
||||
*/
|
||||
public final class InMemoryStore: StorageInterface, @unchecked Sendable {
|
||||
public final class InMemoryStore: StorageInterface {
|
||||
|
||||
/**
|
||||
Initializes an `InMemoryStore` for the specified configuration
|
||||
@@ -74,6 +74,7 @@ public final class InMemoryStore: StorageInterface, @unchecked Sendable {
|
||||
/**
|
||||
Do not call directly. Used by the `DataStack` internally.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public func cs_didAddToDataStack(_ dataStack: DataStack) {
|
||||
|
||||
self.dataStack = dataStack
|
||||
@@ -82,6 +83,7 @@ public final class InMemoryStore: StorageInterface, @unchecked Sendable {
|
||||
/**
|
||||
Do not call directly. Used by the `DataStack` internally.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public func cs_didRemoveFromDataStack(_ dataStack: DataStack) {
|
||||
|
||||
self.dataStack = nil
|
||||
@@ -90,5 +92,5 @@ public final class InMemoryStore: StorageInterface, @unchecked Sendable {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private weak var dataStack: DataStack?
|
||||
private nonisolated(unsafe) weak var dataStack: DataStack?
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ extension Internals {
|
||||
internal typealias Arguments = T
|
||||
internal typealias Result = U
|
||||
|
||||
internal init(_ closure: @escaping (T) -> U) {
|
||||
internal init(_ closure: @escaping @Sendable (T) -> U) {
|
||||
|
||||
self.closure = closure
|
||||
}
|
||||
@@ -52,6 +52,6 @@ extension Internals {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let closure: (T) -> U
|
||||
private let closure: @Sendable (T) -> U
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,6 @@ import QuartzCore
|
||||
|
||||
#endif
|
||||
|
||||
import os
|
||||
|
||||
|
||||
// MARK: - Internals
|
||||
|
||||
@@ -99,7 +97,7 @@ extension Internals {
|
||||
return
|
||||
}
|
||||
|
||||
let performDiffingUpdates: @MainActor () -> Void = {
|
||||
let performDiffingUpdates: @MainActor @Sendable () -> Void = {
|
||||
|
||||
let changeset = StagedChangeset(source: self.sections, target: newSections)
|
||||
performUpdates(
|
||||
@@ -148,7 +146,7 @@ extension Internals {
|
||||
return self.sections[section].differenceIdentifier
|
||||
}
|
||||
|
||||
func itemIdentifier(for indexPath: IndexPath) -> O.ObjectID? {
|
||||
func itemIdentifier(for indexPath: IndexPath) -> NSManagedObjectID? {
|
||||
|
||||
guard self.sections.indices.contains(indexPath.section) else {
|
||||
|
||||
@@ -162,9 +160,9 @@ extension Internals {
|
||||
return items[indexPath.item].differenceIdentifier
|
||||
}
|
||||
|
||||
func indexPath(for itemIdentifier: O.ObjectID) -> IndexPath? {
|
||||
func indexPath(for itemIdentifier: NSManagedObjectID) -> IndexPath? {
|
||||
|
||||
let indexPathMap: [O.ObjectID: IndexPath] = self.sections.enumerated().reduce(into: [:]) { result, section in
|
||||
let indexPathMap: [NSManagedObjectID: IndexPath] = self.sections.enumerated().reduce(into: [:]) { result, section in
|
||||
|
||||
for (itemIndex, item) in section.element.elements.enumerated() {
|
||||
|
||||
@@ -243,7 +241,7 @@ extension Internals {
|
||||
|
||||
fileprivate init() {}
|
||||
|
||||
fileprivate func dispatch(_ action: @escaping @MainActor () -> Void) {
|
||||
fileprivate func dispatch(_ action: @escaping @MainActor @Sendable () -> Void) {
|
||||
|
||||
let count = self.executingCount.incrementAndGet()
|
||||
if Thread.isMainThread && count == 1 {
|
||||
@@ -300,7 +298,7 @@ extension Internals {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let value: OSAllocatedUnfairLock<Int> = .init(initialState: 0)
|
||||
private let value: Internals.Mutex<Int> = .init(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ extension Internals {
|
||||
|
||||
// MARK: - EntityIdentifier
|
||||
|
||||
internal struct EntityIdentifier: Hashable {
|
||||
internal struct EntityIdentifier: Hashable, Sendable {
|
||||
|
||||
// MARK: - Category
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// Internals.Mutex.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2026 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 Synchronization
|
||||
import os
|
||||
|
||||
|
||||
// MARK: - Internals
|
||||
|
||||
extension Internals {
|
||||
|
||||
// MARK: - Mutex
|
||||
|
||||
internal struct Mutex<Value: ~Copyable>: ~Copyable, @unchecked Sendable {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(_ initialValue: consuming sending Value) {
|
||||
|
||||
self.storage = .init(initialValue)
|
||||
}
|
||||
|
||||
borrowing func withLock<Result, E>(
|
||||
_ body: (inout sending Value) throws(E) -> sending Result
|
||||
) throws(E) -> sending Result
|
||||
where E: Error, Result: ~Copyable {
|
||||
|
||||
let storage = self.storage
|
||||
storage.lock()
|
||||
defer {
|
||||
|
||||
storage.unlock()
|
||||
}
|
||||
return try body(&storage.value)
|
||||
}
|
||||
|
||||
borrowing func withLockUnchecked<Result, E>(
|
||||
_ body: (inout sending Value) throws(E) -> Result
|
||||
) throws(E) -> sending Result
|
||||
where E: Error {
|
||||
|
||||
let storage = self.storage
|
||||
storage.lock()
|
||||
defer {
|
||||
|
||||
storage.unlock()
|
||||
}
|
||||
return try body(&storage.value)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let storage: Storage
|
||||
|
||||
|
||||
// MARK: - Storage
|
||||
|
||||
fileprivate final class Storage {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
var value: Value
|
||||
|
||||
init(_ initialValue: consuming sending Value) {
|
||||
|
||||
self.unfairLock = .allocate(capacity: 1)
|
||||
self.unfairLock.initialize(to: os_unfair_lock())
|
||||
self.value = initialValue
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
self.unfairLock.deinitialize(count: 1)
|
||||
self.unfairLock.deallocate()
|
||||
}
|
||||
|
||||
func lock() {
|
||||
os_unfair_lock_lock(self.unfairLock)
|
||||
}
|
||||
|
||||
func unlock() {
|
||||
os_unfair_lock_unlock(self.unfairLock)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let unfairLock: os_unfair_lock_t
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,27 @@ extension Internals {
|
||||
using: closure
|
||||
)
|
||||
}
|
||||
|
||||
init(
|
||||
notificationName: Notification.Name,
|
||||
object: Any?,
|
||||
closure: @escaping @MainActor (_ note: Notification) -> Void
|
||||
) {
|
||||
|
||||
self.observer = NotificationCenter.default.addObserver(
|
||||
forName: notificationName,
|
||||
object: object,
|
||||
queue: .main,
|
||||
using: { note in
|
||||
|
||||
nonisolated(unsafe) let note = note
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
closure(note)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ extension Internals {
|
||||
|
||||
internal func addObserver<U: AnyObject>(
|
||||
_ observer: U,
|
||||
closure: @escaping (T) -> Void
|
||||
closure: @escaping @Sendable (T) -> Void
|
||||
) {
|
||||
|
||||
self.observers.setObject(
|
||||
|
||||
+14
-10
@@ -139,15 +139,6 @@ internal enum Internals {
|
||||
return try ObjectiveC.autoreleasepool(invoking: closure)
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
internal static func autoreleasepool<T, E>(
|
||||
_ closure: () throws(E) -> T
|
||||
) throws(E) -> T {
|
||||
|
||||
return try ObjectiveC.autoreleasepool(invoking: closure)
|
||||
}
|
||||
|
||||
|
||||
@inline(__always)
|
||||
internal static func withCheckedThrowingContinuation<T>(
|
||||
function: String = #function,
|
||||
@@ -160,6 +151,17 @@ internal enum Internals {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#if compiler(>=27)
|
||||
|
||||
@inline(__always)
|
||||
internal static func autoreleasepool<T, E>(
|
||||
_ closure: () throws(E) -> T
|
||||
) throws(E) -> T {
|
||||
|
||||
return try ObjectiveC.autoreleasepool(invoking: closure)
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
internal static func withCheckedThrowingContinuation<T, E>(
|
||||
function: String = #function,
|
||||
@@ -172,10 +174,12 @@ internal enum Internals {
|
||||
)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@inline(__always)
|
||||
internal static func mainActorImmediate(
|
||||
_ body: @escaping @MainActor () -> Void
|
||||
_ body: @escaping @MainActor @Sendable () -> Void
|
||||
) {
|
||||
|
||||
if #available(iOS 26.0, macOS 26.0, watchOS 26.0, tvOS 26.0, *) {
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ import CoreData
|
||||
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
|
||||
```
|
||||
*/
|
||||
public struct Into<O: DynamicObject>: Hashable, @unchecked Sendable {
|
||||
public struct Into<O: DynamicObject>: Hashable, Sendable {
|
||||
|
||||
/**
|
||||
The associated `NSManagedObject` or `CoreStoreObject` entity class
|
||||
|
||||
@@ -836,7 +836,7 @@ public func == <O, D: FieldRelationshipToOneType, R: ObjectRepresentation>(
|
||||
|
||||
return Where<O>(
|
||||
O.meta[keyPath: keyPath].keyPath,
|
||||
isEqualTo: object?.objectID()
|
||||
isEqualTo: object?.cs_id()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -870,7 +870,7 @@ public func != <O, D: FieldRelationshipToOneType, R: ObjectRepresentation>(
|
||||
|
||||
return !Where<O>(
|
||||
O.meta[keyPath: keyPath].keyPath,
|
||||
isEqualTo: object?.objectID()
|
||||
isEqualTo: object?.cs_id()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+76
-56
@@ -66,7 +66,7 @@ import CoreData
|
||||
```
|
||||
In the example above, both `person1` and `person2` will contain the object at section=2, index=3.
|
||||
*/
|
||||
public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable {
|
||||
public final class ListMonitor<O: DynamicObject>: Hashable, Sendable {
|
||||
|
||||
// MARK: Public (Accessors)
|
||||
|
||||
@@ -386,8 +386,10 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
|
||||
- parameter observer: a `ListObserver` to send change notifications to
|
||||
*/
|
||||
@MainActor
|
||||
public func addObserver<U: ListObserver>(_ observer: U) where U.ListEntityType == O {
|
||||
|
||||
let managedObjectContext = self.managedObjectContext
|
||||
self.unregisterObserver(observer)
|
||||
self.registerObserver(
|
||||
observer,
|
||||
@@ -395,28 +397,28 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
|
||||
observer.listMonitorWillChange(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didChange: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorDidChange(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
willRefetch: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorWillRefetch(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didRefetch: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorDidRefetch(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -433,8 +435,10 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
|
||||
- parameter observer: a `ListObjectObserver` to send change notifications to
|
||||
*/
|
||||
@MainActor
|
||||
public func addObserver<U: ListObjectObserver>(_ observer: U) where U.ListEntityType == O {
|
||||
|
||||
let managedObjectContext = self.managedObjectContext
|
||||
self.unregisterObserver(observer)
|
||||
self.registerObserver(
|
||||
observer,
|
||||
@@ -442,28 +446,28 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
|
||||
observer.listMonitorWillChange(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didChange: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorDidChange(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
willRefetch: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorWillRefetch(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didRefetch: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorDidRefetch(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -475,7 +479,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
monitor,
|
||||
didInsertObject: object,
|
||||
toIndexPath: toIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didDeleteObject: { (observer, monitor, object, fromIndexPath) in
|
||||
@@ -484,7 +488,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
monitor,
|
||||
didDeleteObject: object,
|
||||
fromIndexPath: fromIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didUpdateObject: { (observer, monitor, object, atIndexPath) in
|
||||
@@ -493,7 +497,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
monitor,
|
||||
didUpdateObject: object,
|
||||
atIndexPath: atIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didMoveObject: { (observer, monitor, object, fromIndexPath, toIndexPath) in
|
||||
@@ -503,7 +507,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
didMoveObject: object,
|
||||
fromIndexPath: fromIndexPath,
|
||||
toIndexPath: toIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -520,8 +524,10 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
|
||||
- parameter observer: a `ListSectionObserver` to send change notifications to
|
||||
*/
|
||||
@MainActor
|
||||
public func addObserver<U: ListSectionObserver>(_ observer: U) where U.ListEntityType == O {
|
||||
|
||||
let managedObjectContext = self.managedObjectContext
|
||||
self.unregisterObserver(observer)
|
||||
self.registerObserver(
|
||||
observer,
|
||||
@@ -529,28 +535,28 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
|
||||
observer.listMonitorWillChange(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didChange: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorDidChange(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
willRefetch: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorWillRefetch(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didRefetch: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorDidRefetch(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -562,7 +568,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
monitor,
|
||||
didInsertObject: object,
|
||||
toIndexPath: toIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didDeleteObject: { (observer, monitor, object, fromIndexPath) in
|
||||
@@ -571,7 +577,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
monitor,
|
||||
didDeleteObject: object,
|
||||
fromIndexPath: fromIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didUpdateObject: { (observer, monitor, object, atIndexPath) in
|
||||
@@ -580,7 +586,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
monitor,
|
||||
didUpdateObject: object,
|
||||
atIndexPath: atIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didMoveObject: { (observer, monitor, object, fromIndexPath, toIndexPath) in
|
||||
@@ -590,7 +596,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
didMoveObject: object,
|
||||
fromIndexPath: fromIndexPath,
|
||||
toIndexPath: toIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -602,7 +608,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
monitor,
|
||||
didInsertSection: sectionInfo,
|
||||
toSectionIndex: toIndex,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didDeleteSection: { (observer, monitor, sectionInfo, fromIndex) in
|
||||
@@ -611,7 +617,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
monitor,
|
||||
didDeleteSection: sectionInfo,
|
||||
fromSectionIndex: fromIndex,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -624,6 +630,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
|
||||
- parameter observer: a `ListObserver` to unregister notifications to
|
||||
*/
|
||||
@MainActor
|
||||
public func removeObserver<U: ListObserver>(_ observer: U) where U.ListEntityType == O {
|
||||
|
||||
self.unregisterObserver(observer)
|
||||
@@ -635,7 +642,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
/**
|
||||
Returns `true` if a call to `refetch(...)` was made to the `ListMonitor` and is currently waiting for the fetching to complete. Returns `false` otherwise.
|
||||
*/
|
||||
public private(set) var isPendingRefetch = false
|
||||
public nonisolated(unsafe) private(set) var isPendingRefetch = false
|
||||
|
||||
/**
|
||||
Asks the `ListMonitor` to refetch its objects using the specified series of `FetchClause`s. Note that this method does not execute the fetch immediately; the actual fetching will happen after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes.
|
||||
@@ -646,6 +653,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
||||
- Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`.
|
||||
*/
|
||||
@MainActor
|
||||
public func refetch(
|
||||
_ fetchClauses: FetchClause...,
|
||||
sourceIdentifier: (any Sendable)? = nil
|
||||
@@ -666,6 +674,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
||||
- Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`.
|
||||
*/
|
||||
@MainActor
|
||||
public func refetch(
|
||||
_ fetchClauses: [FetchClause],
|
||||
sourceIdentifier: (any Sendable)? = nil
|
||||
@@ -892,6 +901,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
internal func registerObserver<U: AnyObject & Sendable>(
|
||||
_ observer: U,
|
||||
willChange: @escaping @Sendable (
|
||||
@@ -969,6 +979,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
internal func registerObserver<U: AnyObject & Sendable>(
|
||||
_ observer: U,
|
||||
didInsertObject: @escaping @Sendable (
|
||||
@@ -1056,6 +1067,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
internal func registerObserver<U: AnyObject & Sendable>(
|
||||
_ observer: U,
|
||||
didInsertSection: @escaping @Sendable (
|
||||
@@ -1104,6 +1116,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
internal func unregisterObserver(_ observer: AnyObject) {
|
||||
|
||||
Internals.assert(
|
||||
@@ -1125,6 +1138,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
Internals.setAssociatedRetainedObject(nilValue, forKey: &self.didDeleteSectionKey, inObject: observer)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
internal func refetch(
|
||||
_ applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
sourceIdentifier: (any Sendable)?
|
||||
@@ -1154,7 +1168,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
}
|
||||
|
||||
let (newFetchedResultsController, newFetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
|
||||
context: self.fetchedResultsController.managedObjectContext,
|
||||
context: self.managedObjectContext,
|
||||
from: self.from,
|
||||
sectionBy: self.sectionBy,
|
||||
applyFetchClauses: self.applyFetchClauses
|
||||
@@ -1216,32 +1230,34 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
|
||||
// MARK: Private
|
||||
|
||||
fileprivate var fetchedResultsController: Internals.CoreStoreFetchedResultsController
|
||||
fileprivate let taskGroup = DispatchGroup()
|
||||
internal let sectionByIndexTransformer: (_ sectionName: KeyPathString?) -> String?
|
||||
|
||||
private let managedObjectContext: NSManagedObjectContext
|
||||
private let taskGroup = DispatchGroup()
|
||||
private let sectionByIndexTransformer: @Sendable (_ sectionName: KeyPathString?) -> String?
|
||||
private let isSectioned: Bool
|
||||
|
||||
private var willChangeListKey: Void?
|
||||
private var didChangeListKey: Void?
|
||||
private var willRefetchListKey: Void?
|
||||
private var didRefetchListKey: Void?
|
||||
|
||||
private var didInsertObjectKey: Void?
|
||||
private var didDeleteObjectKey: Void?
|
||||
private var didUpdateObjectKey: Void?
|
||||
private var didMoveObjectKey: Void?
|
||||
|
||||
private var didInsertSectionKey: Void?
|
||||
private var didDeleteSectionKey: Void?
|
||||
|
||||
private var fetchedResultsControllerDelegate: Internals.FetchedResultsControllerDelegate
|
||||
private var observerForWillChangePersistentStore: Internals.NotificationObserver!
|
||||
private var observerForDidChangePersistentStore: Internals.NotificationObserver!
|
||||
private let transactionQueue: DispatchQueue
|
||||
private var applyFetchClauses: (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
|
||||
|
||||
private var isPersistentStoreChanging: Bool = false {
|
||||
private nonisolated(unsafe) var applyFetchClauses: (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
|
||||
|
||||
private nonisolated(unsafe) var willChangeListKey: Void?
|
||||
private nonisolated(unsafe) var didChangeListKey: Void?
|
||||
private nonisolated(unsafe) var willRefetchListKey: Void?
|
||||
private nonisolated(unsafe) var didRefetchListKey: Void?
|
||||
|
||||
private nonisolated(unsafe) var didInsertObjectKey: Void?
|
||||
private nonisolated(unsafe) var didDeleteObjectKey: Void?
|
||||
private nonisolated(unsafe) var didUpdateObjectKey: Void?
|
||||
private nonisolated(unsafe) var didMoveObjectKey: Void?
|
||||
|
||||
private nonisolated(unsafe) var didInsertSectionKey: Void?
|
||||
private nonisolated(unsafe) var didDeleteSectionKey: Void?
|
||||
|
||||
private nonisolated(unsafe) var observerForWillChangePersistentStore: Internals.NotificationObserver!
|
||||
private nonisolated(unsafe) var observerForDidChangePersistentStore: Internals.NotificationObserver!
|
||||
|
||||
private nonisolated(unsafe) var fetchedResultsController: Internals.CoreStoreFetchedResultsController
|
||||
private nonisolated(unsafe) var fetchedResultsControllerDelegate: Internals.FetchedResultsControllerDelegate
|
||||
|
||||
private nonisolated(unsafe) var isPersistentStoreChanging: Bool = false {
|
||||
|
||||
didSet {
|
||||
|
||||
@@ -1302,18 +1318,21 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
from: From<O>,
|
||||
sectionBy: SectionBy<O>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
createAsynchronously: (@Sendable (ListMonitor<O>) -> Void)?
|
||||
createAsynchronously: (@MainActor (ListMonitor<O>) -> Void)?
|
||||
) {
|
||||
|
||||
self.managedObjectContext = context
|
||||
self.isSectioned = (sectionBy != nil)
|
||||
self.from = from
|
||||
self.sectionBy = sectionBy
|
||||
(self.fetchedResultsController, self.fetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
|
||||
let (fetchedResultsController, fetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
|
||||
context: context,
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
applyFetchClauses: applyFetchClauses
|
||||
)
|
||||
self.fetchedResultsController = fetchedResultsController
|
||||
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
|
||||
|
||||
if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
|
||||
|
||||
@@ -1325,7 +1344,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
}
|
||||
self.transactionQueue = transactionQueue
|
||||
self.applyFetchClauses = applyFetchClauses
|
||||
self.fetchedResultsControllerDelegate.handler = self
|
||||
fetchedResultsControllerDelegate.handler = self
|
||||
|
||||
guard let coordinator = context.parentStack?.coordinator else {
|
||||
|
||||
@@ -1335,8 +1354,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
self.observerForWillChangePersistentStore = Internals.NotificationObserver(
|
||||
notificationName: NSNotification.Name.NSPersistentStoreCoordinatorStoresWillChange,
|
||||
object: coordinator,
|
||||
queue: OperationQueue.main,
|
||||
closure: { [weak self] (note) -> Void in
|
||||
closure: { @MainActor [weak self] (note) -> Void in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
@@ -1357,8 +1375,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
self.observerForDidChangePersistentStore = Internals.NotificationObserver(
|
||||
notificationName: NSNotification.Name.NSPersistentStoreCoordinatorStoresDidChange,
|
||||
object: coordinator,
|
||||
queue: OperationQueue.main,
|
||||
closure: { [weak self] (note) -> Void in
|
||||
closure: { @MainActor [weak self] (note) -> Void in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
@@ -1389,7 +1406,10 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
||||
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
|
||||
self.taskGroup.notify(queue: .main) {
|
||||
|
||||
createAsynchronously(self)
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
createAsynchronously(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ extension ListPublisher {
|
||||
public typealias Output = ListSnapshot<O>
|
||||
public typealias Failure = Never
|
||||
|
||||
public func receive<S: Subscriber>(
|
||||
public func receive<S: Subscriber & SendableMetatype>(
|
||||
subscriber: S
|
||||
) where S.Input == Output, S.Failure == Failure {
|
||||
|
||||
@@ -90,7 +90,7 @@ extension ListPublisher {
|
||||
|
||||
// MARK: - ListSnapshotSubscription
|
||||
|
||||
fileprivate final class ListSnapshotSubscription<S: Subscriber>: Subscription, @unchecked Sendable
|
||||
fileprivate final class ListSnapshotSubscription<S: Subscriber & SendableMetatype>: Subscription
|
||||
where S.Input == Output, S.Failure == Never {
|
||||
|
||||
// MARK: FilePrivate
|
||||
@@ -115,15 +115,17 @@ extension ListPublisher {
|
||||
|
||||
return
|
||||
}
|
||||
Internals.mainActorImmediate { [self] in
|
||||
nonisolated(unsafe) let strongSelf = self
|
||||
Internals.mainActorImmediate {
|
||||
|
||||
self.publisher.addObserver(
|
||||
self,
|
||||
notifyInitial: self.emitInitialValue,
|
||||
{ [weak self] (publisher) in
|
||||
nonisolated(unsafe) weak let weakSelf = strongSelf as Optional
|
||||
strongSelf.publisher.addObserver(
|
||||
strongSelf,
|
||||
notifyInitial: strongSelf.emitInitialValue,
|
||||
{ (publisher) in
|
||||
|
||||
guard
|
||||
let self = self,
|
||||
let self = weakSelf,
|
||||
let subscriber = self.subscriber
|
||||
else {
|
||||
|
||||
@@ -142,9 +144,10 @@ extension ListPublisher {
|
||||
|
||||
self.subscriber = nil
|
||||
|
||||
nonisolated(unsafe) let strongSelf = self
|
||||
Internals.mainActorImmediate {
|
||||
|
||||
self.publisher.removeObserver(self)
|
||||
strongSelf.publisher.removeObserver(strongSelf)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
public func addObserver<T: AnyObject>(
|
||||
_ observer: T,
|
||||
notifyInitial: Bool = false,
|
||||
_ callback: @escaping (ListPublisher<O>) -> Void
|
||||
_ callback: @escaping @Sendable (ListPublisher<O>) -> Void
|
||||
) {
|
||||
|
||||
Internals.assert(
|
||||
@@ -134,7 +134,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
_ observer: T,
|
||||
notifyInitial: Bool = false,
|
||||
initialSourceIdentifier: Any? = nil,
|
||||
_ callback: @escaping (
|
||||
_ callback: @escaping @Sendable (
|
||||
_ listPublisher: ListPublisher<O>,
|
||||
_ sourceIdentifier: Any?
|
||||
) -> Void
|
||||
@@ -234,6 +234,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public func cs_dataStack() -> DataStack? {
|
||||
|
||||
return self.context.parentStack
|
||||
|
||||
@@ -38,7 +38,7 @@ extension ListSnapshot {
|
||||
|
||||
public let sectionID: SectionID
|
||||
|
||||
public let itemIDs: [ItemID]
|
||||
public let itemIDs: [NSManagedObjectID]
|
||||
|
||||
|
||||
// MARK: RandomAccessCollection
|
||||
@@ -79,7 +79,7 @@ extension ListSnapshot {
|
||||
public subscript(position: Int) -> ObjectPublisher<O> {
|
||||
|
||||
let itemID = self.itemIDs[position]
|
||||
return self.context.objectPublisher(objectID: itemID)
|
||||
return self.context.objectPublisher(managedObjectID: itemID)
|
||||
}
|
||||
|
||||
public func index(_ i: Index, offsetBy distance: Int) -> Index {
|
||||
@@ -100,7 +100,7 @@ extension ListSnapshot {
|
||||
public subscript(bounds: Range<Index>) -> ArraySlice<Element> {
|
||||
|
||||
let itemIDs = self.itemIDs[bounds]
|
||||
return ArraySlice(itemIDs.map(self.context.objectPublisher(objectID:)))
|
||||
return ArraySlice(itemIDs.map(self.context.objectPublisher(managedObjectID:)))
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@ extension ListSnapshot {
|
||||
}
|
||||
self.sectionID = sectionID
|
||||
self.itemIDs = listSnapshot.itemIDs(inSectionWithID: sectionID)
|
||||
.map({ $0.managedObjectID })
|
||||
self.context = context
|
||||
}
|
||||
}
|
||||
|
||||
+27
-25
@@ -43,7 +43,7 @@ import AppKit
|
||||
|
||||
Since `ListSnapshot` is a value type, you can freely modify its items.
|
||||
*/
|
||||
public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable, Sendable {
|
||||
|
||||
// MARK: Public (Accessors)
|
||||
|
||||
@@ -72,7 +72,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
let context = self.context!
|
||||
let itemID = self.diffableSnapshot.itemIdentifier(atAllItemsIndex: index)!
|
||||
return context.objectPublisher(objectID: itemID)
|
||||
return context.objectPublisher(managedObjectID: itemID)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +90,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
return nil
|
||||
}
|
||||
return context.objectPublisher(objectID: itemID)
|
||||
return context.objectPublisher(managedObjectID: itemID)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,7 +109,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
let snapshot = self.diffableSnapshot
|
||||
let sectionID = snapshot.sectionIdentifiers[sectionIndex]
|
||||
let itemID = snapshot.itemIdentifiers(inSection: sectionID)[itemIndex]
|
||||
return context.objectPublisher(objectID: itemID)
|
||||
return context.objectPublisher(managedObjectID: itemID)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +141,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
return nil
|
||||
}
|
||||
let itemID = itemIDs[itemIndex]
|
||||
return context.objectPublisher(objectID: itemID)
|
||||
return context.objectPublisher(managedObjectID: itemID)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,7 +287,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
*/
|
||||
public func sectionID(containingItemWithID itemID: ItemID) -> SectionID? {
|
||||
|
||||
return self.diffableSnapshot.sectionIdentifier(containingItem: itemID)
|
||||
return self.diffableSnapshot.sectionIdentifier(containingItem: itemID.managedObjectID)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,6 +319,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
public var itemIDs: [ItemID] {
|
||||
|
||||
return self.diffableSnapshot.itemIdentifiers
|
||||
.map(ItemID.init(managedObjectID:))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -330,6 +331,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
public func itemIDs(inSectionWithID sectionID: SectionID) -> [ItemID] {
|
||||
|
||||
return self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
|
||||
.map(ItemID.init(managedObjectID:))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,7 +347,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
) -> [ItemID] where S.Element == Int {
|
||||
|
||||
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
|
||||
return indices.map({ itemIDs[$0] })
|
||||
return indices.map({ .init(managedObjectID: itemIDs[$0]) })
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,7 +358,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
*/
|
||||
public func indexOfItem(withID itemID: ItemID) -> Index? {
|
||||
|
||||
return self.diffableSnapshot.indexOfItem(itemID)
|
||||
return self.diffableSnapshot.indexOfItem(itemID.managedObjectID)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -383,7 +385,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
return indices.map { position in
|
||||
|
||||
let itemID = itemIDs[position]
|
||||
return context.objectPublisher(objectID: itemID)
|
||||
return context.objectPublisher(managedObjectID: itemID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,7 +399,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
let context = self.context!
|
||||
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
|
||||
return itemIDs.map(context.objectPublisher(objectID:))
|
||||
return itemIDs.map(context.objectPublisher(managedObjectID:))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -417,7 +419,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
return itemIndices.map { position in
|
||||
|
||||
let itemID = itemIDs[position]
|
||||
return context.objectPublisher(objectID: itemID)
|
||||
return context.objectPublisher(managedObjectID: itemID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,7 +436,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
return indices.lazy.map { position in
|
||||
|
||||
let itemID = itemIDs[position]
|
||||
return context.objectPublisher(objectID: itemID)
|
||||
return context.objectPublisher(managedObjectID: itemID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,7 +450,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
let context = self.context!
|
||||
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
|
||||
return itemIDs.lazy.map(context.objectPublisher(objectID:))
|
||||
return itemIDs.lazy.map(context.objectPublisher(managedObjectID:))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -468,7 +470,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
return itemIndices.lazy.map { position in
|
||||
|
||||
let itemID = itemIDs[position]
|
||||
return context.objectPublisher(objectID: itemID)
|
||||
return context.objectPublisher(managedObjectID: itemID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,8 +489,8 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
) where C.Element == ItemID {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.appendItems(itemIDs, toSection: sectionID)
|
||||
|
||||
$0.appendItems(itemIDs.map({ $0.managedObjectID }), toSection: sectionID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,7 +507,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.unsafeAppendItems(itemIDs, toSectionAt: sectionIndex)
|
||||
$0.unsafeAppendItems(itemIDs.map({ $0.managedObjectID }), toSectionAt: sectionIndex)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,7 +524,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.insertItems(itemIDs, beforeItem: beforeItemID)
|
||||
$0.insertItems(itemIDs.map({ $0.managedObjectID }), beforeItem: beforeItemID.managedObjectID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,7 +541,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.insertItems(itemIDs, afterItem: afterItemID)
|
||||
$0.insertItems(itemIDs.map({ $0.managedObjectID }), afterItem: afterItemID.managedObjectID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,7 +558,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.unsafeInsertItems(itemIDs, at: indexPath)
|
||||
$0.unsafeInsertItems(itemIDs.map({ $0.managedObjectID }), at: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,7 +571,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.deleteItems(itemIDs)
|
||||
$0.deleteItems(itemIDs.map({ $0.managedObjectID }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,7 +612,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.moveItem(itemID, beforeItem: beforeItemID)
|
||||
$0.moveItem(itemID.managedObjectID, beforeItem: beforeItemID.managedObjectID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,7 +629,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.moveItem(itemID, afterItem: afterItemID)
|
||||
$0.moveItem(itemID.managedObjectID, afterItem: afterItemID.managedObjectID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,7 +659,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.reloadItems(itemIDs)
|
||||
$0.reloadItems(itemIDs.map({ $0.managedObjectID }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -906,7 +908,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
return .init()
|
||||
}
|
||||
let itemIDs = self.diffableSnapshot.itemIdentifiers(atAllItemsBounds: bounds)
|
||||
return ArraySlice(itemIDs.map(context.objectPublisher(objectID:)))
|
||||
return ArraySlice(itemIDs.map(context.objectPublisher(managedObjectID:)))
|
||||
}
|
||||
|
||||
|
||||
|
||||
+24
-21
@@ -35,7 +35,7 @@ import SwiftUI
|
||||
A property wrapper type that can read `ListPublisher` changes.
|
||||
*/
|
||||
@propertyWrapper
|
||||
public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
public struct ListState<O: DynamicObject>: DynamicProperty {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@@ -67,7 +67,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
*/
|
||||
@MainActor
|
||||
public init(
|
||||
_ listPublisher: ListPublisher<Object>
|
||||
_ listPublisher: ListPublisher<O>
|
||||
) {
|
||||
|
||||
self._observer = .init(wrappedValue: .init(listPublisher: listPublisher))
|
||||
@@ -103,7 +103,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
public init<B: FetchChainableBuilderType>(
|
||||
_ clauseChain: B,
|
||||
in dataStack: DataStack
|
||||
) where B.ObjectType == Object {
|
||||
) where B.ObjectType == O {
|
||||
|
||||
self.init(dataStack.publishList(clauseChain))
|
||||
}
|
||||
@@ -145,7 +145,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
public init<B: SectionMonitorBuilderType>(
|
||||
_ clauseChain: B,
|
||||
in dataStack: DataStack
|
||||
) where B.ObjectType == Object {
|
||||
) where B.ObjectType == O {
|
||||
|
||||
self.init(dataStack.publishList(clauseChain))
|
||||
}
|
||||
@@ -179,7 +179,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
*/
|
||||
@MainActor
|
||||
public init(
|
||||
_ from: From<Object>,
|
||||
_ from: From<O>,
|
||||
_ fetchClauses: FetchClause...,
|
||||
in dataStack: DataStack
|
||||
) {
|
||||
@@ -218,7 +218,7 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
*/
|
||||
@MainActor
|
||||
public init(
|
||||
_ from: From<Object>,
|
||||
_ from: From<O>,
|
||||
_ fetchClauses: [FetchClause],
|
||||
in dataStack: DataStack
|
||||
) {
|
||||
@@ -263,8 +263,8 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
*/
|
||||
@MainActor
|
||||
public init(
|
||||
_ from: From<Object>,
|
||||
_ sectionBy: SectionBy<Object>,
|
||||
_ from: From<O>,
|
||||
_ sectionBy: SectionBy<O>,
|
||||
_ fetchClauses: FetchClause...,
|
||||
in dataStack: DataStack
|
||||
) {
|
||||
@@ -311,8 +311,8 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
*/
|
||||
@MainActor
|
||||
public init(
|
||||
_ from: From<Object>,
|
||||
_ sectionBy: SectionBy<Object>,
|
||||
_ from: From<O>,
|
||||
_ sectionBy: SectionBy<O>,
|
||||
_ fetchClauses: [FetchClause],
|
||||
in dataStack: DataStack
|
||||
) {
|
||||
@@ -324,13 +324,13 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
// MARK: @propertyWrapper
|
||||
|
||||
@MainActor
|
||||
public var wrappedValue: ListSnapshot<Object> {
|
||||
public var wrappedValue: ListSnapshot<O> {
|
||||
|
||||
return self.observer.items
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public var projectedValue: ListPublisher<Object> {
|
||||
public var projectedValue: ListPublisher<O> {
|
||||
|
||||
return self.observer.listPublisher
|
||||
}
|
||||
@@ -355,31 +355,28 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
@MainActor
|
||||
private final class Observer: Observation.Observable {
|
||||
|
||||
private let registrar = ObservationRegistrar()
|
||||
private var _items: ListSnapshot<Object>
|
||||
let listPublisher: ListPublisher<O>
|
||||
|
||||
let listPublisher: ListPublisher<Object>
|
||||
|
||||
var items: ListSnapshot<Object> {
|
||||
nonisolated var items: ListSnapshot<O> {
|
||||
|
||||
get {
|
||||
|
||||
self.registrar.access(self, keyPath: \.items)
|
||||
return self._items
|
||||
return self.current.withLock({ $0 })
|
||||
}
|
||||
set {
|
||||
|
||||
self.registrar.withMutation(of: self, keyPath: \.items) {
|
||||
|
||||
self._items = newValue
|
||||
self.current.withLock({ $0 = newValue })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(listPublisher: ListPublisher<Object>) {
|
||||
init(listPublisher: ListPublisher<O>) {
|
||||
|
||||
self.listPublisher = listPublisher
|
||||
self._items = listPublisher.snapshot
|
||||
self.current = .init(listPublisher.snapshot)
|
||||
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
|
||||
@@ -395,6 +392,12 @@ public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
|
||||
self.listPublisher.removeObserver(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let registrar = ObservationRegistrar()
|
||||
private let current: Internals.Mutex<ListSnapshot<O>>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ import CoreData
|
||||
- a version appears twice as a key in a dictionary literal
|
||||
- a loop is found in any of the paths
|
||||
*/
|
||||
public struct MigrationChain: ExpressibleByNilLiteral, ExpressibleByStringLiteral, ExpressibleByDictionaryLiteral, ExpressibleByArrayLiteral, Equatable {
|
||||
public struct MigrationChain: ExpressibleByNilLiteral, ExpressibleByStringLiteral, ExpressibleByDictionaryLiteral, ExpressibleByArrayLiteral, Equatable, Sendable {
|
||||
|
||||
/**
|
||||
Initializes the `MigrationChain` with empty values, which instructs the `DataStack` to use the .xcdatamodel's current version as the final version, and to disable progressive migrations.
|
||||
|
||||
@@ -87,7 +87,7 @@ extension NSManagedObjectContext {
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func objectPublisher<O: DynamicObject>(objectID: NSManagedObjectID) -> ObjectPublisher<O> {
|
||||
internal func objectPublisher<O: DynamicObject>(managedObjectID: NSManagedObjectID) -> ObjectPublisher<O> {
|
||||
|
||||
let cache: NSMapTable<NSManagedObjectID, ObjectPublisher<O>> = self.userInfo(for: .objectPublishersCache(O.self)) {
|
||||
|
||||
@@ -95,12 +95,12 @@ extension NSManagedObjectContext {
|
||||
}
|
||||
return Internals.with {
|
||||
|
||||
if let objectPublisher = cache.object(forKey: objectID) {
|
||||
if let objectPublisher = cache.object(forKey: managedObjectID) {
|
||||
|
||||
return objectPublisher
|
||||
}
|
||||
let objectPublisher = ObjectPublisher<O>.createUncached(objectID: objectID, context: self)
|
||||
cache.setObject(objectPublisher, forKey: objectID)
|
||||
let objectPublisher = ObjectPublisher<O>.createUncached(managedObjectID: managedObjectID, context: self)
|
||||
cache.setObject(objectPublisher, forKey: managedObjectID)
|
||||
return objectPublisher
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,14 @@ extension NSManagedObjectContext: FetchableSource, QueryableSource {
|
||||
return objects.compactMap({ self.fetchExisting($0.cs_id()) })
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
public func fetchExisting<O: DynamicObject, S: Sequence>(
|
||||
_ objectIDs: S
|
||||
) -> [O] where S.Iterator.Element == DynamicObjectID<O> {
|
||||
|
||||
return objectIDs.compactMap({ self.fetchExisting($0.managedObjectID) })
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
public func fetchExisting<O: DynamicObject, S: Sequence>(
|
||||
_ objectIDs: S
|
||||
|
||||
@@ -187,7 +187,7 @@ extension NSManagedObjectContext {
|
||||
@nonobjc
|
||||
internal func saveAsynchronously(
|
||||
sourceIdentifier: (any Sendable)?,
|
||||
completion: @escaping @MainActor (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void = { (_, _) in }
|
||||
completion: @escaping @MainActor @Sendable (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void = { (_, _) in }
|
||||
) {
|
||||
|
||||
self.perform {
|
||||
|
||||
+34
-22
@@ -39,7 +39,7 @@ import CoreData
|
||||
|
||||
Observers registered via `addObserver(_:)` are not retained. `ObjectMonitor` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles.
|
||||
*/
|
||||
public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentation, @unchecked Sendable {
|
||||
public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentation, Sendable {
|
||||
|
||||
/**
|
||||
Returns the `DynamicObject` instance being observed, or `nil` if the object was already deleted.
|
||||
@@ -71,6 +71,7 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
|
||||
- parameter observer: an `ObjectObserver` to send change notifications to
|
||||
*/
|
||||
@MainActor
|
||||
public func addObserver<U: ObjectObserver & Sendable>(_ observer: U) where U.ObjectEntityType == O {
|
||||
|
||||
self.unregisterObserver(observer)
|
||||
@@ -111,6 +112,7 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
|
||||
- parameter observer: an `ObjectObserver` to unregister notifications to
|
||||
*/
|
||||
@MainActor
|
||||
public func removeObserver<U: ObjectObserver>(_ observer: U) where U.ObjectEntityType == O {
|
||||
|
||||
self.unregisterObserver(observer)
|
||||
@@ -165,11 +167,13 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
|
||||
// MARK: AnyObjectRepresentation
|
||||
|
||||
public func objectID() -> O.ObjectID {
|
||||
@_spi(Internals)
|
||||
public func cs_id() -> NSManagedObjectID {
|
||||
|
||||
return self.id
|
||||
}
|
||||
|
||||
@_spi(Internals)
|
||||
public func cs_dataStack() -> DataStack? {
|
||||
|
||||
return self.context.parentStack
|
||||
@@ -182,7 +186,7 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
|
||||
public func asPublisher(in dataStack: DataStack) -> ObjectPublisher<O> {
|
||||
|
||||
return dataStack.unsafeContext().objectPublisher(objectID: self.id)
|
||||
return dataStack.unsafeContext().objectPublisher(managedObjectID: self.id)
|
||||
}
|
||||
|
||||
public func asReadOnly(in dataStack: DataStack) -> O? {
|
||||
@@ -198,20 +202,20 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
public func asSnapshot(in dataStack: DataStack) -> ObjectSnapshot<O>? {
|
||||
|
||||
let context = dataStack.unsafeContext()
|
||||
return ObjectSnapshot<O>(objectID: self.id, context: context)
|
||||
return ObjectSnapshot<O>(managedObjectID: self.id, context: context)
|
||||
}
|
||||
|
||||
public func asSnapshot(in transaction: BaseDataTransaction) -> ObjectSnapshot<O>? {
|
||||
|
||||
let context = transaction.unsafeContext()
|
||||
return ObjectSnapshot<O>(objectID: self.id, context: context)
|
||||
return ObjectSnapshot<O>(managedObjectID: self.id, context: context)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(
|
||||
objectID: O.ObjectID,
|
||||
objectID: NSManagedObjectID,
|
||||
context: NSManagedObjectContext
|
||||
) {
|
||||
|
||||
@@ -240,9 +244,13 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
|
||||
try! fetchedResultsController.performFetchFromSpecifiedStores()
|
||||
|
||||
self.lastCommittedAttributes = (self.object?.cs_toRaw().committedValues(forKeys: nil) as? [String: NSObject]) ?? [:]
|
||||
self.lastCommittedAttributes.withLock {
|
||||
|
||||
$0 = (self.object?.cs_toRaw().committedValues(forKeys: nil) as? [String: NSObject]) ?? [:]
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
internal func registerObserver<U: AnyObject & Sendable>(
|
||||
_ observer: U,
|
||||
willChangeObject: @escaping @Sendable (
|
||||
@@ -303,25 +311,29 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let previousCommitedAttributes = self.lastCommittedAttributes
|
||||
let currentCommitedAttributes = object.cs_toRaw().committedValues(forKeys: nil) as! [String: NSObject]
|
||||
|
||||
var changedKeys = Set<String>()
|
||||
for key in currentCommitedAttributes.keys {
|
||||
let changedKeys = self.lastCommittedAttributes.withLock {
|
||||
|
||||
if previousCommitedAttributes[key] != currentCommitedAttributes[key] {
|
||||
let previousCommitedAttributes = $0
|
||||
let currentCommitedAttributes = object.cs_toRaw().committedValues(forKeys: nil) as! [String: NSObject]
|
||||
|
||||
var changedKeys = Set<String>()
|
||||
for key in currentCommitedAttributes.keys {
|
||||
|
||||
changedKeys.insert(key)
|
||||
if previousCommitedAttributes[key] != currentCommitedAttributes[key] {
|
||||
|
||||
changedKeys.insert(key)
|
||||
}
|
||||
}
|
||||
|
||||
$0 = currentCommitedAttributes
|
||||
return changedKeys
|
||||
}
|
||||
|
||||
self.lastCommittedAttributes = currentCommitedAttributes
|
||||
didUpdateObject(observer, monitor, object, changedKeys)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
internal func unregisterObserver(_ observer: AnyObject) {
|
||||
|
||||
Internals.assert(
|
||||
@@ -343,14 +355,14 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let id: O.ObjectID
|
||||
private let id: NSManagedObjectID
|
||||
private let fetchedResultsController: Internals.CoreStoreFetchedResultsController
|
||||
private let fetchedResultsControllerDelegate: Internals.FetchedResultsControllerDelegate
|
||||
private var lastCommittedAttributes = [String: NSObject]()
|
||||
private let lastCommittedAttributes: Internals.Mutex<[String: NSObject]> = .init([:])
|
||||
|
||||
private var willChangeObjectKey: Void?
|
||||
private var didDeleteObjectKey: Void?
|
||||
private var didUpdateObjectKey: Void?
|
||||
private nonisolated(unsafe) var willChangeObjectKey: Void?
|
||||
private nonisolated(unsafe) var didDeleteObjectKey: Void?
|
||||
private nonisolated(unsafe) var didUpdateObjectKey: Void?
|
||||
|
||||
private var context: NSManagedObjectContext {
|
||||
|
||||
|
||||
@@ -36,12 +36,12 @@ import CoreData
|
||||
monitor.addObserver(self)
|
||||
```
|
||||
*/
|
||||
public protocol ObjectObserver: AnyObject & Sendable {
|
||||
public protocol ObjectObserver: AnyObject, Sendable {
|
||||
|
||||
/**
|
||||
The `DynamicObject` type for the observed object
|
||||
*/
|
||||
associatedtype ObjectEntityType: DynamicObject & Sendable
|
||||
associatedtype ObjectEntityType: DynamicObject
|
||||
|
||||
/**
|
||||
Handles processing just before a change to the observed `object` occurs. (Optional)
|
||||
|
||||
@@ -52,7 +52,7 @@ extension ObjectPublisher {
|
||||
public typealias Output = ObjectSnapshot<O>?
|
||||
public typealias Failure = Never
|
||||
|
||||
public func receive<S: Subscriber>(
|
||||
public func receive<S: Subscriber & SendableMetatype>(
|
||||
subscriber: S
|
||||
) where S.Input == Output, S.Failure == Failure {
|
||||
|
||||
@@ -90,7 +90,7 @@ extension ObjectPublisher {
|
||||
|
||||
// MARK: - ObjectSnapshotSubscription
|
||||
|
||||
fileprivate final class ObjectSnapshotSubscription<S: Subscriber>: Subscription, @unchecked Sendable
|
||||
fileprivate final class ObjectSnapshotSubscription<S: Subscriber & SendableMetatype>: Subscription
|
||||
where S.Input == Output, S.Failure == Never {
|
||||
|
||||
// MARK: FilePrivate
|
||||
@@ -115,15 +115,17 @@ extension ObjectPublisher {
|
||||
|
||||
return
|
||||
}
|
||||
Internals.mainActorImmediate { [self] in
|
||||
nonisolated(unsafe) let strongSelf = self
|
||||
Internals.mainActorImmediate {
|
||||
|
||||
self.publisher.addObserver(
|
||||
self,
|
||||
notifyInitial: self.emitInitialValue,
|
||||
{ [weak self] (publisher) in
|
||||
nonisolated(unsafe) weak let weakSelf = strongSelf as Optional
|
||||
strongSelf.publisher.addObserver(
|
||||
strongSelf,
|
||||
notifyInitial: strongSelf.emitInitialValue,
|
||||
{ (publisher) in
|
||||
|
||||
guard
|
||||
let self = self,
|
||||
let self = weakSelf,
|
||||
let subscriber = self.subscriber
|
||||
else {
|
||||
|
||||
@@ -142,9 +144,10 @@ extension ObjectPublisher {
|
||||
|
||||
self.subscriber = nil
|
||||
|
||||
nonisolated(unsafe) let strongSelf = self
|
||||
Internals.mainActorImmediate {
|
||||
|
||||
self.publisher.removeObserver(self)
|
||||
strongSelf.publisher.removeObserver(strongSelf)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
/**
|
||||
The actual `DynamicObject` instance. Becomes `nil` if the object has been deleted.
|
||||
*/
|
||||
public private(set) lazy var object: O? = self.context.fetchExisting(self.id)
|
||||
public private(set) lazy var object: O? = self.context.fetchExisting(self.managedObjectID)
|
||||
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
public func addObserver<T: AnyObject>(
|
||||
_ observer: T,
|
||||
notifyInitial: Bool = false,
|
||||
_ callback: @escaping (ObjectPublisher<O>) -> Void
|
||||
_ callback: @escaping @Sendable (ObjectPublisher<O>) -> Void
|
||||
) {
|
||||
|
||||
Internals.assert(
|
||||
@@ -118,7 +118,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
_ observer: T,
|
||||
notifyInitial: Bool = false,
|
||||
initialSourceIdentifier: Any? = nil,
|
||||
_ callback: @escaping (
|
||||
_ callback: @escaping @Sendable (
|
||||
_ objectPublisher: ObjectPublisher<O>,
|
||||
_ sourceIdentifier: Any?
|
||||
) -> Void
|
||||
@@ -160,11 +160,13 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
// MARK: AnyObjectRepresentation
|
||||
|
||||
public func objectID() -> O.ObjectID {
|
||||
@_spi(Internals)
|
||||
public func cs_id() -> NSManagedObjectID {
|
||||
|
||||
return self.id
|
||||
return self.managedObjectID
|
||||
}
|
||||
|
||||
@_spi(Internals)
|
||||
public func cs_dataStack() -> DataStack? {
|
||||
|
||||
return self.context.parentStack
|
||||
@@ -182,17 +184,17 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
return self
|
||||
}
|
||||
return context.objectPublisher(objectID: self.id)
|
||||
return context.objectPublisher(managedObjectID: self.managedObjectID)
|
||||
}
|
||||
|
||||
public func asReadOnly(in dataStack: DataStack) -> O? {
|
||||
|
||||
return dataStack.unsafeContext().fetchExisting(self.id)
|
||||
return dataStack.unsafeContext().fetchExisting(self.managedObjectID)
|
||||
}
|
||||
|
||||
public func asEditable(in transaction: BaseDataTransaction) -> O? {
|
||||
|
||||
return transaction.unsafeContext().fetchExisting(self.id)
|
||||
return transaction.unsafeContext().fetchExisting(self.managedObjectID)
|
||||
}
|
||||
|
||||
public func asSnapshot(in dataStack: DataStack) -> ObjectSnapshot<O>? {
|
||||
@@ -202,7 +204,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
return self.lazySnapshot
|
||||
}
|
||||
return ObjectSnapshot<O>(objectID: self.id, context: context)
|
||||
return ObjectSnapshot<O>(managedObjectID: self.managedObjectID, context: context)
|
||||
}
|
||||
|
||||
public func asSnapshot(in transaction: BaseDataTransaction) -> ObjectSnapshot<O>? {
|
||||
@@ -212,7 +214,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
return self.lazySnapshot
|
||||
}
|
||||
return ObjectSnapshot<O>(objectID: self.id, context: context)
|
||||
return ObjectSnapshot<O>(managedObjectID: self.managedObjectID, context: context)
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +222,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
public static func == (_ lhs: ObjectPublisher, _ rhs: ObjectPublisher) -> Bool {
|
||||
|
||||
return lhs.id == rhs.id
|
||||
return lhs.managedObjectID == rhs.managedObjectID
|
||||
&& lhs.context == rhs.context
|
||||
}
|
||||
|
||||
@@ -229,24 +231,24 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
|
||||
hasher.combine(self.id)
|
||||
hasher.combine(self.managedObjectID)
|
||||
hasher.combine(self.context)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal var cs_objectID: O.ObjectID {
|
||||
|
||||
return self.objectID()
|
||||
}
|
||||
internal let managedObjectID: NSManagedObjectID
|
||||
|
||||
internal static func createUncached(objectID: O.ObjectID, context: NSManagedObjectContext) -> ObjectPublisher<O> {
|
||||
internal static func createUncached(
|
||||
managedObjectID: NSManagedObjectID,
|
||||
context: NSManagedObjectContext
|
||||
) -> ObjectPublisher<O> {
|
||||
|
||||
return self.init(
|
||||
objectID: objectID,
|
||||
managedObjectID: managedObjectID,
|
||||
context: context,
|
||||
initializer: ObjectSnapshot<O>.init(objectID:context:)
|
||||
initializer: ObjectSnapshot<O>.init(managedObjectID:context:)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -261,43 +263,52 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
fileprivate typealias ObserverClosureType = Internals.Closure<(objectPublisher: ObjectPublisher<O>, sourceIdentifier: Any?), Void>
|
||||
|
||||
fileprivate init(objectID: O.ObjectID, context: NSManagedObjectContext, initializer: @escaping (NSManagedObjectID, NSManagedObjectContext) -> ObjectSnapshot<O>?) {
|
||||
fileprivate init(
|
||||
managedObjectID: NSManagedObjectID,
|
||||
context: NSManagedObjectContext,
|
||||
initializer: @escaping @Sendable (NSManagedObjectID, NSManagedObjectContext) -> ObjectSnapshot<O>?
|
||||
) {
|
||||
|
||||
self.id = objectID
|
||||
self.managedObjectID = managedObjectID
|
||||
self.context = context
|
||||
self.$lazySnapshot.initialize { [weak self] in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return initializer(objectID, context)
|
||||
return initializer(managedObjectID, context)
|
||||
}
|
||||
context.objectsDidChangeObserver(for: self).addObserver(self) { [weak self] (updatedIDs, deletedIDs) in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
if deletedIDs.contains(objectID) {
|
||||
|
||||
self.object = nil
|
||||
|
||||
self.$lazySnapshot.reset({ nil })
|
||||
self.notifyObservers(sourceIdentifier: self.context.saveMetadata)
|
||||
}
|
||||
else if updatedIDs.contains(objectID) {
|
||||
|
||||
self.$lazySnapshot.reset({ initializer(objectID, context) })
|
||||
self.notifyObservers(sourceIdentifier: self.context.saveMetadata)
|
||||
}
|
||||
}
|
||||
return initializer(objectID, context)
|
||||
nonisolated(unsafe) weak let weakSelf = self as Optional
|
||||
context
|
||||
.objectsDidChangeObserver(for: self)
|
||||
.addObserver(
|
||||
self,
|
||||
closure: { (updatedIDs, deletedIDs) in
|
||||
|
||||
guard let self = weakSelf else {
|
||||
|
||||
return
|
||||
}
|
||||
if deletedIDs.contains(managedObjectID) {
|
||||
|
||||
self.object = nil
|
||||
|
||||
self.$lazySnapshot.reset({ nil })
|
||||
self.notifyObservers(sourceIdentifier: self.context.saveMetadata)
|
||||
}
|
||||
else if updatedIDs.contains(managedObjectID) {
|
||||
|
||||
self.$lazySnapshot.reset({ initializer(managedObjectID, context) })
|
||||
self.notifyObservers(sourceIdentifier: self.context.saveMetadata)
|
||||
}
|
||||
}
|
||||
)
|
||||
return initializer(managedObjectID, context)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let id: O.ObjectID
|
||||
private let context: NSManagedObjectContext
|
||||
|
||||
@Internals.LazyNonmutating(uninitialized: ())
|
||||
|
||||
@@ -33,15 +33,17 @@ import CoreData
|
||||
*/
|
||||
public protocol AnyObjectRepresentation {
|
||||
|
||||
/**
|
||||
The internal ID for the object.
|
||||
*/
|
||||
func objectID() -> NSManagedObjectID
|
||||
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
func cs_dataStack() -> DataStack?
|
||||
|
||||
/**
|
||||
The internal `NSManagedObjectID` for the object. Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
func cs_id() -> NSManagedObjectID
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +100,7 @@ extension DynamicObject where Self: ObjectRepresentation {
|
||||
|
||||
return self.cs_toRaw()
|
||||
.managedObjectContext
|
||||
.map({ $0.objectPublisher(objectID: self.cs_id()) })
|
||||
.map({ $0.objectPublisher(managedObjectID: self.cs_id()) })
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,17 +110,19 @@ extension DynamicObject where Self: ObjectRepresentation {
|
||||
|
||||
return self.cs_toRaw()
|
||||
.managedObjectContext
|
||||
.flatMap({ ObjectSnapshot<Self>(objectID: self.cs_id(), context: $0) })
|
||||
.flatMap({ ObjectSnapshot<Self>(managedObjectID: self.cs_id(), context: $0) })
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyObjectRepresentation
|
||||
|
||||
public func objectID() -> Self.ObjectID {
|
||||
@_spi(Internals)
|
||||
public func cs_id() -> NSManagedObjectID {
|
||||
|
||||
return self.cs_id()
|
||||
}
|
||||
|
||||
@_spi(Internals)
|
||||
public func cs_dataStack() -> DataStack? {
|
||||
|
||||
return self.cs_toRaw().managedObjectContext?.parentStack
|
||||
@@ -130,7 +134,7 @@ extension DynamicObject where Self: ObjectRepresentation {
|
||||
public func asPublisher(in dataStack: DataStack) -> ObjectPublisher<Self> {
|
||||
|
||||
let context = dataStack.unsafeContext()
|
||||
return context.objectPublisher(objectID: self.cs_id())
|
||||
return context.objectPublisher(managedObjectID: self.cs_id())
|
||||
}
|
||||
|
||||
public func asReadOnly(in dataStack: DataStack) -> Self? {
|
||||
@@ -156,12 +160,12 @@ extension DynamicObject where Self: ObjectRepresentation {
|
||||
public func asSnapshot(in dataStack: DataStack) -> ObjectSnapshot<Self>? {
|
||||
|
||||
let context = dataStack.unsafeContext()
|
||||
return ObjectSnapshot<Self>(objectID: self.cs_id(), context: context)
|
||||
return ObjectSnapshot<Self>(managedObjectID: self.cs_id(), context: context)
|
||||
}
|
||||
|
||||
public func asSnapshot(in transaction: BaseDataTransaction) -> ObjectSnapshot<Self>? {
|
||||
|
||||
let context = transaction.unsafeContext()
|
||||
return ObjectSnapshot<Self>(objectID: self.cs_id(), context: context)
|
||||
return ObjectSnapshot<Self>(managedObjectID: self.cs_id(), context: context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ import AppKit
|
||||
The `ObjectSnapshot` is a full copy of a `DynamicObject`'s properties at a given point in time. This is useful especially when keeping thread-safe state values, in ViewModels for example. Since this is a value type, any changes in this `struct` does not affect the actual object.
|
||||
*/
|
||||
@dynamicMemberLookup
|
||||
public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable, @unchecked Sendable {
|
||||
public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable, Sendable {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@@ -52,11 +52,13 @@ public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable,
|
||||
|
||||
// MARK: AnyObjectRepresentation
|
||||
|
||||
public func objectID() -> O.ObjectID {
|
||||
@_spi(Internals)
|
||||
public func cs_id() -> NSManagedObjectID {
|
||||
|
||||
return self.id
|
||||
return self.managedObjectID
|
||||
}
|
||||
|
||||
@_spi(Internals)
|
||||
public func cs_dataStack() -> DataStack? {
|
||||
|
||||
return self.context.parentStack
|
||||
@@ -70,29 +72,29 @@ public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable,
|
||||
public func asPublisher(in dataStack: DataStack) -> ObjectPublisher<O> {
|
||||
|
||||
let context = dataStack.unsafeContext()
|
||||
return context.objectPublisher(objectID: self.id)
|
||||
return context.objectPublisher(managedObjectID: self.managedObjectID)
|
||||
}
|
||||
|
||||
public func asReadOnly(in dataStack: DataStack) -> O? {
|
||||
|
||||
return dataStack.unsafeContext().fetchExisting(self.id)
|
||||
return dataStack.unsafeContext().fetchExisting(self.managedObjectID)
|
||||
}
|
||||
|
||||
public func asEditable(in transaction: BaseDataTransaction) -> O? {
|
||||
|
||||
return transaction.unsafeContext().fetchExisting(self.id)
|
||||
return transaction.unsafeContext().fetchExisting(self.managedObjectID)
|
||||
}
|
||||
|
||||
public func asSnapshot(in dataStack: DataStack) -> ObjectSnapshot<O>? {
|
||||
|
||||
let context = dataStack.unsafeContext()
|
||||
return ObjectSnapshot<O>(objectID: self.id, context: context)
|
||||
return ObjectSnapshot<O>(managedObjectID: self.managedObjectID, context: context)
|
||||
}
|
||||
|
||||
public func asSnapshot(in transaction: BaseDataTransaction) -> ObjectSnapshot<O>? {
|
||||
|
||||
let context = transaction.unsafeContext()
|
||||
return ObjectSnapshot<O>(objectID: self.id, context: context)
|
||||
return ObjectSnapshot<O>(managedObjectID: self.managedObjectID, context: context)
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +102,7 @@ public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable,
|
||||
|
||||
public static func == (_ lhs: Self, _ rhs: Self) -> Bool {
|
||||
|
||||
return lhs.id == rhs.id
|
||||
return lhs.managedObjectID == rhs.managedObjectID
|
||||
&& (lhs.generation == rhs.generation || lhs.valuesRef == rhs.valuesRef)
|
||||
}
|
||||
|
||||
@@ -109,34 +111,34 @@ public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable,
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
|
||||
hasher.combine(self.id)
|
||||
hasher.combine(self.managedObjectID)
|
||||
hasher.combine(self.valuesRef)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let managedObjectID: NSManagedObjectID
|
||||
|
||||
internal init?(objectID: O.ObjectID, context: NSManagedObjectContext) {
|
||||
internal init?(
|
||||
managedObjectID: NSManagedObjectID,
|
||||
context: NSManagedObjectContext
|
||||
) {
|
||||
|
||||
guard let values = O.cs_snapshotDictionary(id: objectID, context: context) else {
|
||||
guard let values = O.cs_snapshotDictionary(managedObjectID: managedObjectID, context: context) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
self.id = objectID
|
||||
self.managedObjectID = managedObjectID
|
||||
self.context = context
|
||||
self.values = values
|
||||
self.generation = .init()
|
||||
}
|
||||
|
||||
internal var cs_objectID: O.ObjectID {
|
||||
|
||||
return self.objectID()
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate var values: [String: Any] {
|
||||
fileprivate nonisolated(unsafe) var values: [String: Any] {
|
||||
|
||||
didSet {
|
||||
|
||||
@@ -147,7 +149,6 @@ public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable,
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let id: O.ObjectID
|
||||
private let context: NSManagedObjectContext
|
||||
|
||||
private var generation: UUID
|
||||
@@ -344,16 +345,16 @@ extension ObjectSnapshot where O: CoreStoreObject {
|
||||
get {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
guard let id = self.values[key] as? D.ObjectID else {
|
||||
guard let id = self.values[key] as? NSManagedObjectID else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return self.context.objectPublisher(objectID: id)
|
||||
return self.context.objectPublisher(managedObjectID: id)
|
||||
}
|
||||
set {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
self.values[key] = newValue?.objectID()
|
||||
self.values[key] = newValue?.cs_id()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,13 +366,13 @@ extension ObjectSnapshot where O: CoreStoreObject {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
let context = self.context
|
||||
let ids = self.values[key] as! [D.ObjectID]
|
||||
return ids.map(context.objectPublisher(objectID:))
|
||||
let ids = self.values[key] as! [NSManagedObjectID]
|
||||
return ids.map(context.objectPublisher(managedObjectID:))
|
||||
}
|
||||
set {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
self.values[key] = newValue.map({ $0.objectID() })
|
||||
self.values[key] = newValue.map({ $0.cs_id() })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,13 +384,13 @@ extension ObjectSnapshot where O: CoreStoreObject {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
let context = self.context
|
||||
let ids = self.values[key] as! Set<D.ObjectID>
|
||||
return Set(ids.map(context.objectPublisher(objectID:)))
|
||||
let ids = self.values[key] as! Set<NSManagedObjectID>
|
||||
return Set(ids.map(context.objectPublisher(managedObjectID:)))
|
||||
}
|
||||
set {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
self.values[key] = Set(newValue.map({ $0.objectID() }))
|
||||
self.values[key] = Set(newValue.map({ $0.cs_id() }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,23 +103,20 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
||||
@MainActor
|
||||
private final class Observer: Observation.Observable {
|
||||
|
||||
private let registrar = ObservationRegistrar()
|
||||
private var _item: ObjectSnapshot<O>?
|
||||
|
||||
let objectPublisher: ObjectPublisher<O>?
|
||||
|
||||
var item: ObjectSnapshot<O>? {
|
||||
nonisolated var item: ObjectSnapshot<O>? {
|
||||
|
||||
get {
|
||||
|
||||
self.registrar.access(self, keyPath: \.item)
|
||||
return self._item
|
||||
return self.current.withLock({ $0 })
|
||||
}
|
||||
set {
|
||||
|
||||
self.registrar.withMutation(of: self, keyPath: \.item) {
|
||||
|
||||
self._item = newValue
|
||||
self.current.withLock({ $0 = newValue })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,12 +129,12 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
||||
else {
|
||||
|
||||
self.objectPublisher = nil
|
||||
self._item = nil
|
||||
self.current = .init(nil)
|
||||
return
|
||||
}
|
||||
|
||||
self.objectPublisher = objectPublisher
|
||||
self._item = objectPublisher.snapshot
|
||||
self.current = .init(objectPublisher.snapshot)
|
||||
|
||||
objectPublisher.addObserver(self) { [weak self] (objectPublisher) in
|
||||
|
||||
@@ -153,6 +150,12 @@ public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
||||
|
||||
self.objectPublisher?.removeObserver(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let registrar = ObservationRegistrar()
|
||||
private let current: Internals.Mutex<ObjectSnapshot<O>?>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import CoreData
|
||||
/**
|
||||
The `OrderBy` clause specifies the sort order for results for a fetch or a query.
|
||||
*/
|
||||
public struct OrderBy<O: DynamicObject>: OrderByClause, FetchClause, QueryClause, DeleteClause, Hashable {
|
||||
public struct OrderBy<O: DynamicObject>: OrderByClause, FetchClause, QueryClause, DeleteClause, Hashable, Sendable {
|
||||
|
||||
/**
|
||||
Combines two `OrderBy` sort descriptors together
|
||||
@@ -104,7 +104,7 @@ public struct OrderBy<O: DynamicObject>: OrderByClause, FetchClause, QueryClause
|
||||
|
||||
public typealias ObjectType = O
|
||||
|
||||
public let sortDescriptors: [NSSortDescriptor]
|
||||
public nonisolated(unsafe) let sortDescriptors: [NSSortDescriptor]
|
||||
|
||||
|
||||
// MARK: FetchClause, QueryClause, DeleteClause
|
||||
|
||||
@@ -37,7 +37,7 @@ extension Progress {
|
||||
*/
|
||||
@nonobjc
|
||||
@MainActor
|
||||
public func setProgressHandler(_ closure: (@MainActor (_ progress: Progress) -> Void)?) {
|
||||
public func setProgressHandler(_ closure: (@MainActor @Sendable (_ progress: Progress) -> Void)?) {
|
||||
|
||||
self.progressObserver.progressHandler = closure
|
||||
}
|
||||
@@ -81,12 +81,12 @@ extension Progress {
|
||||
// MARK: - ProgressObserver
|
||||
|
||||
@objc
|
||||
private final class ProgressObserver: NSObject, @unchecked Sendable {
|
||||
private final class ProgressObserver: NSObject, Sendable {
|
||||
|
||||
private unowned let progress: Progress
|
||||
|
||||
@MainActor
|
||||
fileprivate var progressHandler: (@MainActor (_ progress: Progress) -> Void)? {
|
||||
fileprivate var progressHandler: (@MainActor @Sendable (_ progress: Progress) -> Void)? {
|
||||
|
||||
didSet {
|
||||
|
||||
@@ -127,7 +127,12 @@ private final class ProgressObserver: NSObject, @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||
override func observeValue(
|
||||
forKeyPath keyPath: String?,
|
||||
of object: Any?,
|
||||
change: [NSKeyValueChangeKey : Any]?,
|
||||
context: UnsafeMutableRawPointer?
|
||||
) {
|
||||
|
||||
guard let progress = object as? Progress,
|
||||
progress == self.progress,
|
||||
|
||||
@@ -196,7 +196,7 @@ extension RelationshipContainer {
|
||||
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value.map({ $0.objectID() })
|
||||
return self.value.map({ $0.cs_id() })
|
||||
}
|
||||
|
||||
private init(keyPath: String, minCount: Int, maxCount: Int, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set<String>) {
|
||||
|
||||
@@ -196,7 +196,7 @@ extension RelationshipContainer {
|
||||
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return Set(self.value.map({ $0.objectID() }))
|
||||
return Set(self.value.map({ $0.cs_id() }))
|
||||
}
|
||||
|
||||
private init(keyPath: KeyPathString, inverseKeyPath: @escaping () -> KeyPathString?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set<String>) {
|
||||
|
||||
@@ -180,7 +180,7 @@ extension RelationshipContainer {
|
||||
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value?.objectID()
|
||||
return self.value?.cs_id()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import CoreData
|
||||
|
||||
- Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was depending on CoreStore's default directories prior to 2.0.0, make sure to use the `SQLiteStore.legacy(...)` factory methods to create the `SQLiteStore` instead of using initializers directly.
|
||||
*/
|
||||
public final class SQLiteStore: LocalStorage, @unchecked Sendable {
|
||||
public final class SQLiteStore: LocalStorage {
|
||||
|
||||
/**
|
||||
Initializes an SQLite store interface from the given SQLite file URL. When this instance is passed to the `DataStack`'s `addStorage()` methods, a new SQLite file will be created if it does not exist.
|
||||
@@ -176,6 +176,7 @@ public final class SQLiteStore: LocalStorage, @unchecked Sendable {
|
||||
/**
|
||||
Do not call directly. Used by the `DataStack` internally.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public func cs_didAddToDataStack(_ dataStack: DataStack) {
|
||||
|
||||
self.dataStack = dataStack
|
||||
@@ -184,6 +185,7 @@ public final class SQLiteStore: LocalStorage, @unchecked Sendable {
|
||||
/**
|
||||
Do not call directly. Used by the `DataStack` internally.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public func cs_didRemoveFromDataStack(_ dataStack: DataStack) {
|
||||
|
||||
self.dataStack = nil
|
||||
@@ -205,7 +207,7 @@ public final class SQLiteStore: LocalStorage, @unchecked Sendable {
|
||||
/**
|
||||
Options that tell the `DataStack` how to setup the persistent store
|
||||
*/
|
||||
public var localStorageOptions: LocalStorageOptions
|
||||
public let localStorageOptions: LocalStorageOptions
|
||||
|
||||
/**
|
||||
The options dictionary for the specified `LocalStorageOptions`
|
||||
@@ -231,6 +233,7 @@ public final class SQLiteStore: LocalStorage, @unchecked Sendable {
|
||||
/**
|
||||
Called by the `DataStack` to perform checkpoint operations on the storage. For `SQLiteStore`, this converts the database's WAL journaling mode to DELETE to force a checkpoint.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public func cs_finalizeStorageAndWait(
|
||||
soureModelHint: NSManagedObjectModel
|
||||
) throws(any Swift.Error) {
|
||||
@@ -252,6 +255,7 @@ public final class SQLiteStore: LocalStorage, @unchecked Sendable {
|
||||
/**
|
||||
Called by the `DataStack` to perform actual deletion of the store file from disk. Do not call directly! The `sourceModel` argument is a hint for the existing store's model version. For `SQLiteStore`, this converts the database's WAL journaling mode to DELETE before deleting the file.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
public func cs_eraseStorageAndWait(
|
||||
metadata: [String: Any],
|
||||
soureModelHint: NSManagedObjectModel?
|
||||
@@ -375,5 +379,5 @@ public final class SQLiteStore: LocalStorage, @unchecked Sendable {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private weak var dataStack: DataStack?
|
||||
private nonisolated(unsafe) weak var dataStack: DataStack?
|
||||
}
|
||||
|
||||
+13
-15
@@ -23,7 +23,7 @@
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
@preconcurrency import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import Foundation
|
||||
/**
|
||||
The `SchemaHistory` encapsulates multiple `DynamicSchema` across multiple model versions. It contains all model history and is used by the `DataStack` to
|
||||
*/
|
||||
public final class SchemaHistory: ExpressibleByArrayLiteral {
|
||||
public final class SchemaHistory: ExpressibleByArrayLiteral, Sendable {
|
||||
|
||||
/**
|
||||
The version string for the current model version. The `DataStack` will try to migrate all `StorageInterface`s added to itself to this version, following the version steps provided by the `migrationChain`.
|
||||
@@ -161,6 +161,14 @@ public final class SchemaHistory: ExpressibleByArrayLiteral {
|
||||
self.migrationChain = migrationChain
|
||||
self.currentModelVersion = currentModelVersion
|
||||
self.rawModel = schemaByVersion[currentModelVersion]!.rawModel()
|
||||
self.entityDescriptionsByEntityIdentifier = self.rawModel.entities.reduce(
|
||||
into: [:],
|
||||
{ mapping, entityDescription in
|
||||
|
||||
let entityIdentifier = Internals.EntityIdentifier(entityDescription)
|
||||
mapping[entityIdentifier] = entityDescription
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -168,7 +176,7 @@ public final class SchemaHistory: ExpressibleByArrayLiteral {
|
||||
|
||||
public typealias Element = DynamicSchema
|
||||
|
||||
public convenience init(arrayLiteral elements: DynamicSchema...) {
|
||||
public convenience init(arrayLiteral elements: any DynamicSchema...) {
|
||||
|
||||
self.init(
|
||||
allSchema: elements,
|
||||
@@ -180,19 +188,9 @@ public final class SchemaHistory: ExpressibleByArrayLiteral {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let schemaByVersion: [ModelVersion: DynamicSchema]
|
||||
internal let schemaByVersion: [ModelVersion: any DynamicSchema]
|
||||
internal let rawModel: NSManagedObjectModel
|
||||
|
||||
internal private(set) lazy var entityDescriptionsByEntityIdentifier: [Internals.EntityIdentifier: NSEntityDescription] = Internals.with { [unowned self] in
|
||||
|
||||
var mapping: [Internals.EntityIdentifier: NSEntityDescription] = [:]
|
||||
self.rawModel.entities.forEach { (entityDescription) in
|
||||
|
||||
let entityIdentifier = Internals.EntityIdentifier(entityDescription)
|
||||
mapping[entityIdentifier] = entityDescription
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
internal let entityDescriptionsByEntityIdentifier: [Internals.EntityIdentifier: NSEntityDescription]
|
||||
|
||||
internal func rawModel(for modelVersion: ModelVersion) -> NSManagedObjectModel? {
|
||||
|
||||
|
||||
@@ -32,11 +32,12 @@ import Foundation
|
||||
/**
|
||||
The `SchemaMappingProvider` provides migration mapping information between two `DynamicSchema` versions.
|
||||
*/
|
||||
public protocol SchemaMappingProvider {
|
||||
public protocol SchemaMappingProvider: Sendable {
|
||||
|
||||
/**
|
||||
Do not call directly.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
func cs_createMappingModel(
|
||||
from sourceSchema: DynamicSchema,
|
||||
to destinationSchema: DynamicSchema,
|
||||
|
||||
+11
-11
@@ -39,7 +39,7 @@ import CoreData
|
||||
)
|
||||
```
|
||||
*/
|
||||
public struct SectionBy<O: DynamicObject> {
|
||||
public struct SectionBy<O: DynamicObject>: Sendable {
|
||||
|
||||
/**
|
||||
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections
|
||||
@@ -63,7 +63,7 @@ public struct SectionBy<O: DynamicObject> {
|
||||
*/
|
||||
public init(
|
||||
_ sectionKeyPath: KeyPathString,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) {
|
||||
|
||||
self.sectionKeyPath = sectionKeyPath
|
||||
@@ -74,7 +74,7 @@ public struct SectionBy<O: DynamicObject> {
|
||||
// MARK: Internal
|
||||
|
||||
internal let sectionKeyPath: KeyPathString
|
||||
internal let sectionIndexTransformer: (_ sectionName: String?) -> String?
|
||||
internal let sectionIndexTransformer: @Sendable (_ sectionName: String?) -> String?
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ extension SectionBy where O: NSManagedObject {
|
||||
*/
|
||||
public init<T>(
|
||||
_ sectionKeyPath: KeyPath<O, T>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) {
|
||||
|
||||
self.init(
|
||||
@@ -167,7 +167,7 @@ extension SectionBy where O: CoreStoreObject {
|
||||
*/
|
||||
public init<T>(
|
||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) {
|
||||
|
||||
self.init(
|
||||
@@ -185,7 +185,7 @@ extension SectionBy where O: CoreStoreObject {
|
||||
*/
|
||||
public init<T>(
|
||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) {
|
||||
|
||||
self.init(
|
||||
@@ -203,7 +203,7 @@ extension SectionBy where O: CoreStoreObject {
|
||||
*/
|
||||
public init<T>(
|
||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) {
|
||||
|
||||
self.init(
|
||||
@@ -255,7 +255,7 @@ extension SectionBy {
|
||||
|
||||
public init<T>(
|
||||
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) {
|
||||
|
||||
self.init(
|
||||
@@ -266,7 +266,7 @@ extension SectionBy {
|
||||
|
||||
public init<T>(
|
||||
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) {
|
||||
|
||||
self.init(
|
||||
@@ -277,7 +277,7 @@ extension SectionBy {
|
||||
|
||||
public init<T>(
|
||||
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) {
|
||||
|
||||
self.init(
|
||||
@@ -288,7 +288,7 @@ extension SectionBy {
|
||||
|
||||
public init<T>(
|
||||
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||
) {
|
||||
|
||||
self.init(
|
||||
|
||||
@@ -33,7 +33,7 @@ import CoreData
|
||||
/**
|
||||
The `SelectResultType` protocol is implemented by return types supported by the `Select` clause.
|
||||
*/
|
||||
public protocol SelectResultType {}
|
||||
public protocol SelectResultType: Sendable {}
|
||||
|
||||
|
||||
// MARK: - SelectAttributesResultType
|
||||
@@ -43,6 +43,7 @@ public protocol SelectResultType {}
|
||||
*/
|
||||
public protocol SelectAttributesResultType: SelectResultType {
|
||||
|
||||
@_spi(Internals)
|
||||
static func cs_fromQueryResultsNativeType(
|
||||
_ result: [Any]
|
||||
) -> [[String: Any]]
|
||||
|
||||
@@ -54,11 +54,13 @@ public protocol StorageInterface: AnyObject, Sendable {
|
||||
/**
|
||||
Do not call directly. Used by the `DataStack` internally.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
func cs_didAddToDataStack(_ dataStack: DataStack)
|
||||
|
||||
/**
|
||||
Do not call directly. Used by the `DataStack` internally.
|
||||
*/
|
||||
@_spi(Internals)
|
||||
func cs_didRemoveFromDataStack(_ dataStack: DataStack)
|
||||
}
|
||||
|
||||
@@ -146,6 +148,7 @@ public protocol LocalStorage: StorageInterface {
|
||||
/**
|
||||
Called by the `DataStack` to perform checkpoint operations on the storage. (SQLite stores for example, can convert the database's WAL journaling mode to DELETE to force a checkpoint)
|
||||
*/
|
||||
@_spi(Internals)
|
||||
func cs_finalizeStorageAndWait(
|
||||
soureModelHint: NSManagedObjectModel
|
||||
) throws(any Swift.Error)
|
||||
@@ -153,6 +156,7 @@ public protocol LocalStorage: StorageInterface {
|
||||
/**
|
||||
Called by the `DataStack` to perform actual deletion of the store file from disk. **Do not call directly!** The `sourceModel` argument is a hint for the existing store's model version. Implementers can use the `sourceModel` to perform necessary store operations. (SQLite stores for example, can convert WAL journaling mode to DELETE before deleting)
|
||||
*/
|
||||
@_spi(Internals)
|
||||
func cs_eraseStorageAndWait(
|
||||
metadata: [String: Any],
|
||||
soureModelHint: NSManagedObjectModel?
|
||||
|
||||
@@ -32,7 +32,8 @@ import CoreData
|
||||
/**
|
||||
The `SynchronousDataTransaction` provides an interface for `DynamicObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.beginSynchronous(_:)`.
|
||||
*/
|
||||
public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
@_nonSendable
|
||||
public nonisolated final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
|
||||
/**
|
||||
Cancels a transaction by throwing `CoreStoreError.userCancelled`.
|
||||
@@ -67,6 +68,23 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
return super.create(into)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject` or `CoreStoreObject`.
|
||||
|
||||
- parameter persistentID: the `DynamicObjectID` pertaining ot the `NSManagedObject` or `CoreStoreObject` type to be edited
|
||||
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
|
||||
*/
|
||||
public override func edit<O: DynamicObject>(
|
||||
_ persistentID: DynamicObjectID<O>?
|
||||
) -> O? {
|
||||
|
||||
Internals.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to update an entity for \(Internals.typeName(persistentID)) from an already committed \(Internals.typeName(self))."
|
||||
)
|
||||
return super.edit(persistentID)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject` or `CoreStoreObject`.
|
||||
|
||||
|
||||
+7
-7
@@ -42,12 +42,7 @@ import CoreData
|
||||
)
|
||||
```
|
||||
*/
|
||||
public struct Tweak: FetchClause, QueryClause, DeleteClause {
|
||||
|
||||
/**
|
||||
The block to customize the `NSFetchRequest`
|
||||
*/
|
||||
public let closure: (_ fetchRequest: NSFetchRequest<NSFetchRequestResult>) -> Void
|
||||
public struct Tweak: FetchClause, QueryClause, DeleteClause, Sendable {
|
||||
|
||||
/**
|
||||
Initializes a `Tweak` clause with a closure where the `NSFetchRequest` may be configured.
|
||||
@@ -55,7 +50,7 @@ public struct Tweak: FetchClause, QueryClause, DeleteClause {
|
||||
- Important: `Tweak`'s closure is executed only just before the fetch occurs, so make sure that any values captured by the closure is not prone to race conditions. Also, some utilities (such as `ListMonitor`s) may keep `FetchClause`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
|
||||
- parameter closure: the block to customize the `NSFetchRequest`
|
||||
*/
|
||||
public init(_ closure: @escaping (_ fetchRequest: NSFetchRequest<NSFetchRequestResult>) -> Void) {
|
||||
public init(_ closure: @escaping @Sendable (_ fetchRequest: NSFetchRequest<NSFetchRequestResult>) -> Void) {
|
||||
|
||||
self.closure = closure
|
||||
}
|
||||
@@ -67,4 +62,9 @@ public struct Tweak: FetchClause, QueryClause, DeleteClause {
|
||||
|
||||
self.closure(fetchRequest as! NSFetchRequest<NSFetchRequestResult>)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
public let closure: @Sendable (_ fetchRequest: NSFetchRequest<NSFetchRequestResult>) -> Void
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
@preconcurrency import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
|
||||
+15
-22
@@ -38,7 +38,7 @@ import Foundation
|
||||
```
|
||||
- Important: Do not use this class to store thread-sensitive data.
|
||||
*/
|
||||
public final class UserInfo {
|
||||
public final class UserInfo: Sendable {
|
||||
|
||||
/**
|
||||
Allows external libraries to store custom data. App code should rarely have a need for this.
|
||||
@@ -51,25 +51,21 @@ public final class UserInfo {
|
||||
- Important: Do not use this method to store thread-sensitive data.
|
||||
- parameter key: the key for custom data. Make sure this is a static pointer that will never be changed.
|
||||
*/
|
||||
public subscript(key: UnsafeRawPointer) -> Any? {
|
||||
public subscript(key: UnsafeRawPointer) -> (any Sendable)? {
|
||||
|
||||
get {
|
||||
|
||||
self.lock.lock()
|
||||
defer {
|
||||
return self.data.withLock { (info: inout _) in
|
||||
|
||||
self.lock.unlock()
|
||||
return info[key]
|
||||
}
|
||||
return self.data[key]
|
||||
}
|
||||
set {
|
||||
|
||||
self.lock.lock()
|
||||
defer {
|
||||
return self.data.withLock { (info: inout _) in
|
||||
|
||||
self.lock.unlock()
|
||||
info[key] = newValue
|
||||
}
|
||||
self.data[key] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,20 +82,18 @@ public final class UserInfo {
|
||||
- parameter lazyInit: a closure to use to lazily-initialize the data
|
||||
- returns: A custom data identified by `key`
|
||||
*/
|
||||
public subscript(key: UnsafeRawPointer, lazyInit closure: () -> Any) -> Any {
|
||||
public subscript(key: UnsafeRawPointer, lazyInit closure: () -> any Sendable) -> any Sendable {
|
||||
|
||||
self.lock.lock()
|
||||
defer {
|
||||
|
||||
self.lock.unlock()
|
||||
}
|
||||
if let value = self.data[key] {
|
||||
return self.data.withLock { (info: inout _) in
|
||||
|
||||
if let value = info[key] {
|
||||
|
||||
return value
|
||||
}
|
||||
let value = closure()
|
||||
info[key] = value
|
||||
return value
|
||||
}
|
||||
let value = closure()
|
||||
self.data[key] = value
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
@@ -110,6 +104,5 @@ public final class UserInfo {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var data: [UnsafeRawPointer: Any] = [:]
|
||||
private let lock = NSRecursiveLock()
|
||||
private let data: Internals.Mutex<[UnsafeRawPointer: any Sendable]> = .init([:])
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import CoreData
|
||||
/**
|
||||
Used only for `Where.Expression` type constraints. Currently supports `SingleTarget` and `CollectionTarget`.
|
||||
*/
|
||||
public protocol WhereExpressionTrait {}
|
||||
public protocol WhereExpressionTrait: SendableMetatype {}
|
||||
|
||||
|
||||
// MARK: - Where
|
||||
@@ -50,7 +50,7 @@ extension Where {
|
||||
)
|
||||
```
|
||||
*/
|
||||
public struct Expression<T: WhereExpressionTrait, V>: CustomStringConvertible, KeyPathStringConvertible {
|
||||
public struct Expression<T: WhereExpressionTrait, V>: CustomStringConvertible, KeyPathStringConvertible, Sendable {
|
||||
|
||||
/**
|
||||
Currently supports `SingleTarget` and `CollectionTarget`.
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ import CoreData
|
||||
/**
|
||||
The `Where` clause specifies the conditions for a fetch or a query.
|
||||
*/
|
||||
public struct Where<O: DynamicObject>: WhereClauseType, FetchClause, QueryClause, DeleteClause, Hashable {
|
||||
public struct Where<O: DynamicObject>: WhereClauseType, FetchClause, QueryClause, DeleteClause, Hashable, Sendable {
|
||||
|
||||
/**
|
||||
Combines two `Where` predicates together using `AND` operator
|
||||
@@ -432,7 +432,7 @@ public struct Where<O: DynamicObject>: WhereClauseType, FetchClause, QueryClause
|
||||
|
||||
// MARK: AnyWhereClause
|
||||
|
||||
public let predicate: NSPredicate
|
||||
public nonisolated(unsafe) let predicate: NSPredicate
|
||||
|
||||
public init(_ predicate: NSPredicate) {
|
||||
|
||||
|
||||
@@ -164,6 +164,7 @@ public final class XcodeDataModelSchema: DynamicSchema {
|
||||
|
||||
self.modelVersion = modelName
|
||||
self.modelVersionFileURL = modelVersionFileURL
|
||||
self.rootModelFileURL = modelVersionFileURL.deletingLastPathComponent()
|
||||
}
|
||||
|
||||
|
||||
@@ -190,13 +191,9 @@ public final class XcodeDataModelSchema: DynamicSchema {
|
||||
|
||||
internal let modelVersionFileURL: URL
|
||||
|
||||
private lazy var rootModelFileURL: URL = Internals.with { [unowned self] in
|
||||
|
||||
return self.modelVersionFileURL.deletingLastPathComponent()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private weak var cachedRawModel: NSManagedObjectModel?
|
||||
private let rootModelFileURL: URL
|
||||
private nonisolated(unsafe) weak var cachedRawModel: NSManagedObjectModel?
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user