diff --git a/Package.swift b/Package.swift index 38fc3dd..25afafc 100644 --- a/Package.swift +++ b/Package.swift @@ -2,6 +2,12 @@ import PackageDescription import CompilerPluginSupport +#if swift(>=5.10) +let settings = [ .enableExperimentalFeature("StrictConcurrency") ] +#else +let settings = [ SwiftSetting ]() +#endif + let package = Package( name: "ManagedModels", @@ -13,8 +19,12 @@ let package = Package( .package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0") ], targets: [ - .target(name: "ManagedModels", dependencies: [ "ManagedModelMacros" ]), - + .target( + name: "ManagedModels", + dependencies: [ "ManagedModelMacros" ], + swiftSettings: settings + ), + .macro( name: "ManagedModelMacros", dependencies: [ diff --git a/Sources/ManagedModels/Container/ModelConfiguration.swift b/Sources/ManagedModels/Container/ModelConfiguration.swift index 3e861c7..ef420ad 100644 --- a/Sources/ManagedModels/Container/ModelConfiguration.swift +++ b/Sources/ManagedModels/Container/ModelConfiguration.swift @@ -3,9 +3,9 @@ // Copyright © 2023 ZeeZide GmbH. // -import CoreData +@preconcurrency import CoreData -public struct ModelConfiguration: Hashable { +public struct ModelConfiguration: Hashable, Sendable { // TBD: Some of those are `let` in SwiftData public var path : String @@ -83,7 +83,7 @@ extension ModelConfiguration: Identifiable { public extension ModelConfiguration { - struct GroupContainer: Hashable { + struct GroupContainer: Hashable, Sendable { enum Value: Hashable { case automatic, none case identifier(String) @@ -100,7 +100,7 @@ public extension ModelConfiguration { public extension ModelConfiguration { - struct CloudKitDatabase: Hashable { + struct CloudKitDatabase: Hashable, Sendable { enum Value: Hashable { case automatic, none case `private`(String) diff --git a/Sources/ManagedModels/PersistentModel/PropertyMetadata.swift b/Sources/ManagedModels/PersistentModel/PropertyMetadata.swift index d7692b0..5551ad1 100644 --- a/Sources/ManagedModels/PersistentModel/PropertyMetadata.swift +++ b/Sources/ManagedModels/PersistentModel/PropertyMetadata.swift @@ -13,7 +13,7 @@ public extension NSManagedObjectModel { * All (code defined) properties of the ``NSManagedObjectModel`` are stored * in the `schemaMetadata` static property. */ - struct PropertyMetadata { + struct PropertyMetadata: @unchecked Sendable { /// The name of the property instance variable, e.g. `street`. public let name : String diff --git a/Sources/ManagedModels/Schema/AttributeOption.swift b/Sources/ManagedModels/Schema/AttributeOption.swift index 4af7fcf..0e9aa86 100644 --- a/Sources/ManagedModels/Schema/AttributeOption.swift +++ b/Sources/ManagedModels/Schema/AttributeOption.swift @@ -7,7 +7,7 @@ import class Foundation.ValueTransformer extension NSAttributeDescription { - public struct Option: Equatable { + public struct Option: Equatable, Sendable { let value : Value @@ -39,7 +39,7 @@ extension NSAttributeDescription { extension NSAttributeDescription.Option { - enum Value { + enum Value: Sendable { case unique, externalStorage, preserveValueOnDeletion, ephemeral, spotlight case transformableByType(ValueTransformer.Type) case transformableByName(String) diff --git a/Sources/ManagedModels/Schema/RelationshipOption.swift b/Sources/ManagedModels/Schema/RelationshipOption.swift index 271c371..2ad1c84 100644 --- a/Sources/ManagedModels/Schema/RelationshipOption.swift +++ b/Sources/ManagedModels/Schema/RelationshipOption.swift @@ -7,9 +7,9 @@ import CoreData public extension NSRelationshipDescription { - struct Option: Equatable { + struct Option: Equatable, Sendable { - enum Value { + enum Value: Sendable { case unique } let value : Value diff --git a/Sources/ManagedModels/SchemaCompatibility/CoreDataPrimitiveValue.swift b/Sources/ManagedModels/SchemaCompatibility/CoreDataPrimitiveValue.swift index 1228903..b84feb5 100644 --- a/Sources/ManagedModels/SchemaCompatibility/CoreDataPrimitiveValue.swift +++ b/Sources/ManagedModels/SchemaCompatibility/CoreDataPrimitiveValue.swift @@ -6,7 +6,7 @@ import CoreData public extension CoreData.NSAttributeDescription { - struct TypeConfiguration { + struct TypeConfiguration: Sendable { let attributeType : NSAttributeType let isOptional : Bool let attributeValueClassName : String? diff --git a/Sources/ManagedModels/SchemaCompatibility/NSManagedObjectModel+Data.swift b/Sources/ManagedModels/SchemaCompatibility/NSManagedObjectModel+Data.swift index d42b2c4..00cdf2c 100644 --- a/Sources/ManagedModels/SchemaCompatibility/NSManagedObjectModel+Data.swift +++ b/Sources/ManagedModels/SchemaCompatibility/NSManagedObjectModel+Data.swift @@ -37,8 +37,14 @@ public extension NSManagedObjectModel { // MARK: - Cached ManagedObjectModels private let lock = NSLock() +#if swift(>=5.10) +nonisolated(unsafe) private var map = [ Set : NSManagedObjectModel ]() -private let sharedBuilder = SchemaBuilder() +nonisolated(unsafe) private let sharedBuilder = SchemaBuilder() +#else // 5.9: nonisolated(unsafe) not available, nonisolated nor working on var +private var map = [ Set : NSManagedObjectModel ]() +nonisolated private let sharedBuilder = SchemaBuilder() +#endif public extension NSManagedObjectModel { diff --git a/Sources/ManagedModels/SchemaCompatibility/NSPropertyDescription+Data.swift b/Sources/ManagedModels/SchemaCompatibility/NSPropertyDescription+Data.swift index 4f3f28a..b4365fa 100644 --- a/Sources/ManagedModels/SchemaCompatibility/NSPropertyDescription+Data.swift +++ b/Sources/ManagedModels/SchemaCompatibility/NSPropertyDescription+Data.swift @@ -3,29 +3,44 @@ // Copyright © 2023 ZeeZide GmbH. // -private var _propertyIsUniqueAssociatedKey: UInt8 = 42 - extension NSPropertyDescription { - + private struct AssociatedKeys { + #if swift(>=5.10) + nonisolated(unsafe) static var propertyIsUniqueAssociatedKey: Void? = nil + #else // 5.9: nonisolated(unsafe) not available + static var propertyIsUniqueAssociatedKey: Void? = nil + #endif + } + public internal(set) var isUnique: Bool { // Note: isUnique is only used during schema construction! set { if newValue { - objc_setAssociatedObject(self, &_propertyIsUniqueAssociatedKey, - type(of: self), .OBJC_ASSOCIATION_ASSIGN) + objc_setAssociatedObject( + self, &AssociatedKeys.propertyIsUniqueAssociatedKey, + type(of: self), // Just used as a flag, type won't go away + .OBJC_ASSOCIATION_ASSIGN + ) } else { - objc_setAssociatedObject(self, &_propertyIsUniqueAssociatedKey, nil, .OBJC_ASSOCIATION_RETAIN) + objc_setAssociatedObject( + self, &AssociatedKeys.propertyIsUniqueAssociatedKey, + nil, // clear // clear flag + .OBJC_ASSOCIATION_ASSIGN + ) } - #if false // do we need this? The entity might not yet be setup? +#if false // do we need this? The entity might not yet be setup? guard !entity.isPropertyUnique(self) else { return } entity.uniquenessConstraints.append( [ self ]) - #endif +#endif } get { - objc_getAssociatedObject(self, &_propertyIsUniqueAssociatedKey) != nil - ? true - : entity.isPropertyUnique(self) + objc_getAssociatedObject( + self, + &AssociatedKeys.propertyIsUniqueAssociatedKey + ) != nil + ? true + : entity.isPropertyUnique(self) } } } diff --git a/Sources/ManagedModels/SchemaCompatibility/NSRelationshipDescription+Data.swift b/Sources/ManagedModels/SchemaCompatibility/NSRelationshipDescription+Data.swift index f4264f5..c47ee9c 100644 --- a/Sources/ManagedModels/SchemaCompatibility/NSRelationshipDescription+Data.swift +++ b/Sources/ManagedModels/SchemaCompatibility/NSRelationshipDescription+Data.swift @@ -13,9 +13,9 @@ extension CoreData.NSRelationshipDescription { extension CoreData.NSRelationshipDescription: SchemaProperty {} public extension CoreData.NSRelationshipDescription { - + @inlinable var isToOneRelationship : Bool { !isToMany } - + @inlinable var isAttribute : Bool { return false } @inlinable var isRelationship : Bool { return true } @@ -109,7 +109,7 @@ extension CoreData.NSRelationshipDescription { // MARK: - Initializer public extension CoreData.NSRelationshipDescription { - + // Note: This matches what the `Relationship` macro takes. convenience init(_ options: Option..., deleteRule: NSDeleteRule = .nullify, minimumModelCount: Int? = 0, maximumModelCount: Int? = 0, @@ -122,16 +122,16 @@ public extension CoreData.NSRelationshipDescription { precondition(minimumModelCount ?? 0 >= 0) precondition(maximumModelCount ?? 0 >= 0) self.init() - + self.name = name ?? "" self.valueType = valueType self.renamingIdentifier = originalName ?? "" self.versionHashModifier = hashModifier self.deleteRule = deleteRule self.inverseKeyPath = inverse - + if options.contains(.unique) { isUnique = true } - + if let minimumModelCount { self.minCount = minimumModelCount } if let maximumModelCount { self.maxCount = maximumModelCount @@ -140,7 +140,7 @@ public extension CoreData.NSRelationshipDescription { if valueType is any RelationshipCollection.Type { self.maxCount = 0 } - else if valueType is NSOrderedSet.Type || + else if valueType is NSOrderedSet.Type || valueType is Optional.Type { self.maxCount = 0 @@ -155,10 +155,8 @@ public extension CoreData.NSRelationshipDescription { // MARK: - Storage -private var _relationshipInfoAssociatedKey: UInt8 = 72 - extension CoreData.NSRelationshipDescription { - + func internalCopy() -> Self { guard let copy = self.copy() as? Self else { fatalError("Could not copy relationship \(self)") @@ -187,7 +185,7 @@ extension CoreData.NSRelationshipDescription { var inverseName : String? var destination : String? var isToOneRelationship : Bool? - + override func copy() -> Any { internalCopy() } func internalCopy() -> MacroInfo { @@ -201,11 +199,18 @@ extension CoreData.NSRelationshipDescription { return copy } } - + + private struct AssociatedKeys { + #if swift(>=5.10) + nonisolated(unsafe) static var relationshipInfoAssociatedKey: Void? = nil + #else // 5.9: nonisolated(unsafe) not available + static var relationshipInfoAssociatedKey: Void? = nil + #endif + } + var writableRelationshipInfo : MacroInfo { - if let info = - objc_getAssociatedObject(self, &_relationshipInfoAssociatedKey) - as? MacroInfo + if let info = objc_getAssociatedObject( + self, &AssociatedKeys.relationshipInfoAssociatedKey) as? MacroInfo { return info } @@ -214,16 +219,19 @@ extension CoreData.NSRelationshipDescription { self.relationshipInfo = info return info } - + var relationshipInfo: MacroInfo? { // Note: isUnique is only used during schema construction! set { - objc_setAssociatedObject(self, &_relationshipInfoAssociatedKey, - newValue, .OBJC_ASSOCIATION_RETAIN) + objc_setAssociatedObject( + self, &AssociatedKeys.relationshipInfoAssociatedKey, + newValue, .OBJC_ASSOCIATION_RETAIN + ) } get { - objc_getAssociatedObject(self, &_relationshipInfoAssociatedKey) - as? MacroInfo + objc_getAssociatedObject( + self, &AssociatedKeys.relationshipInfoAssociatedKey + ) as? MacroInfo } } } diff --git a/Sources/ManagedModels/SwiftUI/ModelContainer+SwiftUI.swift b/Sources/ManagedModels/SwiftUI/ModelContainer+SwiftUI.swift index c8e5a89..5d7437e 100644 --- a/Sources/ManagedModels/SwiftUI/ModelContainer+SwiftUI.swift +++ b/Sources/ManagedModels/SwiftUI/ModelContainer+SwiftUI.swift @@ -25,6 +25,7 @@ public extension View { self.modelContext(container.viewContext) } + @MainActor @ViewBuilder func modelContainer( for modelTypes : [ any PersistentModel.Type ], @@ -48,6 +49,7 @@ public extension View { } } + @MainActor @inlinable func modelContainer( for modelType : any PersistentModel.Type, @@ -73,6 +75,7 @@ public extension Scene { self.modelContext(container.viewContext) } + @MainActor @SceneBuilder func modelContainer( for modelTypes : [ any PersistentModel.Type ], @@ -92,6 +95,7 @@ public extension Scene { self.modelContainer(try! result.get()) } + @MainActor @inlinable func modelContainer( for modelType : any PersistentModel.Type, @@ -113,8 +117,10 @@ public extension Scene { // MARK: - Primitive // Note: The docs say that a container is only ever created once! So cache it. +@MainActor private var modelToContainer = [ ObjectIdentifier: NSPersistentContainer ]() +@MainActor private func makeModelContainer( for modelTypes : [ any PersistentModel.Type ], inMemory : Bool = false,