I'm getting a lot of warnings of this within my app. I'm trying to migrate to Swift 6.
SwiftData @Model does not conform to the 'Sendable' protocol; thi
A SwiftData Model cannot cross actor boundaries to avoid data races. You shouldn't make your model conform to Sendable because it still doesn't.
I am currently in the same situation and as far as I can tell the best option to migrate to Swift 6 when using SwiftData is using ModelActor There are not as many tutorials and articles about it as other SwiftData topics but I believe this is the way to go.
Do not return SwiftData Models from your ModelActor. This may result in data races when these models are accessed from different threads. Only return sole properties and the persistentModelID
to identify models. This is what my actor looks like:
@ModelActor
actor DataHandler {
public func fetch<T>(_ descriptor: FetchDescriptor<T>) throws -> [PersistentIdentifier] {
return try self.modelContext.fetchIdentifiers(descriptor)
}
public func update<T>(_ persistentIdentifier: PersistentIdentifier, keypath: ReferenceWritableKeyPath<YourModel, T>, to value: T) throws {
guard let model = modelContext.model(for: persistentIdentifier) as? YourModel else {
// Error handling
}
model[keyPath: keypath] = value
}
public func read<T>(_ persistentIdentifier: PersistentIdentifier, keypath: ReferenceWritableKeyPath<YourModel, T>) throws -> T {
guard let result = modelContext.model(for: persistentIdentifier) as? Hoerspiel else {
// Error Handling
}
return result[keyPath: keypath]
}
// And others like delete fetchCount etc. I think you get the point
}
You can also return a struct that has the same properties as your model if you need to read properties in a synchronous context. This struct will automatically conform to Sendable if you only use standard data types like Ints and Bools etc. Otherwise make it conform to Sendable.
You will probably also have the error Passing argument of non-sendable type FetchDescriptor outside of outside main actor-isolated context may introduce data races
You can either just never modify the fetch descriptor inside the modelActor (which there is no reason to do anyway) and mark the FetchDescriptor @unchecked Senable
. I think the better option is to define your fetchDescriptor, modify it to your needs and then create a let constant that copies the previous fetchDescriptor. This way it cannot be mutated and therefore no data races can occur.
I hope you were able to follow. Happy to provide clarifications or corrections if someone has found a better option.
I use the @Model class in my UICollectionViewDiffableDataSource as the ItemIdentifierType, which I now see is required to be Sendable. I have a ton of errors relating to this. This is a big deal. Do I need to create Sendable structs which reads the @Model parameters? I will look into ModelActor to see if that can get me what I need.
I've fixed this by creating a struct for the data returned by SwiftData. I stuff the struct with the SwiftData and use the struct in my UICollectionViewDiffableDataSource. I am using SwiftData with UIKit.
let fetchDescriptor = FetchDescriptor<MyModel>() works fine, but
let fetchDescriptor = FetchDescriptor<MyModel>(sortBy: [SortDescriptor( \.title)])
fails with
The operation couldn’t be completed. (SwiftData.SwiftDataError error 1.)
@FPST I had to write a method that returned the models from a sorted fetch and then get their PersistentIdentifiers.
public func fetchSort<T: PersistentModel>(_ descriptor: FetchDescriptor<T>) throws -> [PersistentIdentifier] {
var result = [PersistentIdentifier]()
var models = [T]()
models = try self.modelContext.fetch(descriptor)
for model in models {
let pID = model.persistentModelID
result.append(pID)
}
return result
}
@SpaceMan You should use this instead:
public func fetch<T>(_ descriptor: @Sendable () -> FetchDescriptor<T>) throws -> [PersistentIdentifier] {
return try self.modelContext.fetchIdentifiers(descriptor())
}
and you use that like:
guard let loaded = try? await dataHandler.handler.fetch( {
FetchDescriptor<MyModel>(predicate: #Predicate { model in
model.somebool
}, sortBy: [SortDescriptor(\.someKeypath)])
}) else { return }