When I logged into my cloudkit console to inspect the database for some debugging work I couldn't access the private database. It keeps saying "failed to access iCloud data, please signi n again". No matter how many times I sign in again, whether with password or passwordless key it keeps saying the same thing. It says that message when I click on Public database, and private and shared databases are below it. I only noticed this a couple of days ago. It's done this in the past, but I eventually got back into the database but I don't know what changed to make it work.
iCloud & Data
RSS for tagLearn how to integrate your app with iCloud and data frameworks for effective data storage
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Posting here to see if folks have workarounds or if I have a misunderstanding of SwiftData supported types.
In adopting SwiftData, I have swiftData properties of collection type (Array or Set - both have this issue). E.g:
@Model
final class Item {
var timestamp: Date
var strings = ["aa", "bb"]
var display: String {
strings.joined(separator: " ")
}
init(timestamp: Date) {
self.timestamp = timestamp
}
}
So far in development I haven't had issues on iOS 17, but on the iOS 18 betas 4-5 the app logs show the following error:
"fault: Could not materialize Objective-C class named "Array" from declared attribute value type "Array<String>" of attribute named strings"
It happens immediately in my app when creating an object with a collection attribute.
In a minimal test example, the error log appears only after a few minutes and doesn't seem to affect the template app's basic functionality.
Anyone else running into this?
Was filed as FB14397250
anyone getting the following error with CloudKit+CoreData on iOS16 RC?
delete/resintall app, delete user CloudKit data and reset of environment don't fix.
[error] error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _requestAbortedNotInitialized:](2044): <NSCloudKitMirroringDelegate: 0x2816f89a0> - Never successfully initialized and cannot execute request '<NSCloudKitMirroringImportRequest: 0x283abfa00> 41E6B8D6-08C7-4C73-A718-71291DFA67E4' due to error: Error Domain=NSCocoaErrorDomain Code=4864 "*** -[NSKeyedUnarchiver _initForReadingFromData:error:throwLegacyExceptions:]: incomprehensible archive (0x53, 0x6f, 0x6d, 0x65, 0x20, 0x65, 0x78, 0x61)" UserInfo={NSDebugDescription=*** -[NSKeyedUnarchiver _initForReadingFromData:error:throwLegacyExceptions:]: incomprehensible archive (0x53, 0x6f, 0x6d, 0x65, 0x20, 0x65, 0x78, 0x61)}
Swift recently added support for Int128. However, they do need NOT seem to be supported in SwiftData. Now totally possible I'm doing something wrong too.
I have the project set to macOS 15 to use a UInt128 in @Model class as attribute. I tried using a clean Xcode project with Swift Data choosen in the macOS app wizard.
Everything compiles, but it fails at runtime in both my app and "Xcode default" SwiftData:
SwiftData/SchemaProperty.swift:380: Fatal error: Unexpected property within Persisted Struct/Enum: Builtin.Int128
with the only modification to from stock is:
@Model
final class Item {
var timestamp: Date
var ipv6: UInt128
init(timestamp: Date) {
self.timestamp = timestamp
self.ipv6 = 0
}
}
I have tried both Int128 and UInt128. Both fails exactly the same. In fact, so exactly, when using UInt128 it still show a "Int128" in error message, despite class member being UInt128 .
My underlying need is to store an IPv6 addresses with an app, so the newer UInt128 would work to persist it. Since Network Framework IPv6Address is also not compatible, it seems, with SwiftData. So not a lot of good options, other an a String. But for an IPv6 address that suffers from that same address can take a few String forms (i.e. "0000:0000:0000:0000:0000:0000:0000:0000" =="0:0:0:0:0:0:0:0" == "::") which is more annoying than having a few expand Int128 as String separator ":".
Ideas welcomed. But potentially a bug in SwiftData since Int128 is both a Builtin and conforms to Codable, so from my reading it should work.
public static func fetch(in context: NSManagedObjectContext, configurationBlock: (NSFetchRequest) -&gt; () = { _ in }) -&gt; [Self] {
let request = NSFetchRequest(entityName: Self.entityName)
configurationBlock(request)
return try! context.fetch(request)
}
context.fetch(request), 'fetch' function has error. Thread 24: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
Topic:
App & System Services
SubTopic:
iCloud & Data
Tags:
Xcode Sanitizers and Runtime Issues
Core Data
While experimenting with CloudKit dashboard, I accidentally turned off a iCloud container.
Now in the Certificates, Identifiers & Profiles section of developer portal, this iCloud container identifier is listed under "hidden" not "active"
I can edit its name but there is not way to unhide or active it again.
What am I missing?
Topic:
App & System Services
SubTopic:
iCloud & Data
I've spent a few months writing an app that uses SwiftData with inheritance. Everything worked well until I tried adding CloudKit support. To do so, I had to make all relationships optional, which exposed what appears to be a bug. Note that this isn't a CloudKit issue -- it happens even when CloudKit is disabled -- but it's due to the requirement for optional relationships.
In the code below, I get the following error on the second call to modelContext.save() when the button is clicked:
Could not cast value of type 'SwiftData.PersistentIdentifier' (0x1ef510b68) to 'SimplePersistenceIdentifierTest.Computer' (0x1025884e0).
I was surprised to find zero hit when Googling "Could not cast value of type 'SwiftData.PersistentIdentifier'".
Some things to note:
Calling teacher.computers?.append(computer) instead of computer.teacher = teacher results in the same error.
It only happens when Teacher inherits Person.
It only happens if modelContext.save() is called both times.
It works if the first modelContext.save() is commented out.
If the second modelContext.save()is commented out, the error occurs the second time the model context is saved (whether explicitly or implicitly).
Keep in mind this is a super simple repro written to generate on demand the error I'm seeing in a normal app. In my app, modelContext.save() must be called in some places to update the UI immediately, sometimes resulting in the error seconds later when the model context is saved automatically. Not calling modelContext.save() doesn't appear to be an option.
To be sure, I'm new to this ecosystem so I'd be thrilled if I've missed something obvious! Any thoughts are appreciated.
import Foundation
import SwiftData
import SwiftUI
struct ContentView: View {
@Environment(\.modelContext) var modelContext
var body: some View {
VStack {
Button("Do it") {
let teacher = Teacher()
let computer = Computer()
modelContext.insert(teacher)
modelContext.insert(computer)
try! modelContext.save()
computer.teacher = teacher
try! modelContext.save()
}
}
}
}
@Model
class Computer {
@Relationship(deleteRule: .nullify)
var teacher: Teacher?
init() {}
}
@Model
class Person {
init() {}
}
@available(iOS 26.0, macOS 26.0, *)
@Model
class Teacher: Person {
@Relationship(deleteRule: .nullify, inverse: \Computer.teacher)
public var computers: [Computer]? = []
override init() {
super.init()
}
}
I'm trying to convert some data, then save it back to Core Data. Sometimes this works fine without an issue, but occasionally I'll get an error
Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)
It seems to occur when saving the core data context. I'm having trouble trying to debug it as it doesn't happen on the same object each time and can't reliably recreate the error
Full view code can be found https://pastebin.com/d974V5Si but main functions below
var body: some View {
VStack {
// Visual code here
}
.onAppear() {
DispatchQueue.global(qos: .background).async {
while (getHowManyProjectsToUpdate() > 0) {
leftToUpdate = getHowManyProjectsToUpdate()
updateLocal()
}
if getHowManyProjectsToUpdate() == 0 {
while (getNumberOfFilesInDocumentsDirectory() > 0) {
deleteImagesFromDocumentsDirectory()
}
if getNumberOfFilesInDocumentsDirectory() == 0 {
DispatchQueue.main.asyncAfter(deadline: .now()) {
withAnimation {
self.isActive = true
}
}
}
}
}
}
}
update local function
func updateLocal() {
autoreleasepool {
let fetchRequest: NSFetchRequest<Project> = Project.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "converted = %d", false)
fetchRequest.fetchLimit = 1
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Project.name, ascending: true), NSSortDescriptor(keyPath: \Project.name, ascending: true)]
do {
let projects = try viewContext.fetch(fetchRequest)
for project in projects {
currentPicNumber = 0
currentProjectName = project.name ?? "Error loading project"
if let projectMain = project.mainPicture {
currentProjectImage = getUIImage(picture: projectMain)
}
if let pictures = project.pictures {
projectPicNumber = pictures.count
// Get main image
if let projectMain = project.mainPicture {
if let imgThumbData = convertImageThumb(picture: projectMain) {
project.mainPictureData = imgThumbData
}
}
while (getTotalImagesToConvertForProject(project: project ) > 0) {
convertImageBatch(project: project)
}
project.converted = true
saveContext()
viewContext.refreshAllObjects()
}
}
} catch {
print("Fetch Failed")
}
}
}
convertImageBatch function
func convertImageBatch(project: Project) {
autoreleasepool {
let fetchRequestPic: NSFetchRequest<Picture> = Picture.fetchRequest()
let projectPredicate = NSPredicate(format: "project = %@", project)
let dataPredicate = NSPredicate(format: "pictureData == NULL")
fetchRequestPic.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [projectPredicate, dataPredicate])
fetchRequestPic.fetchLimit = 5
fetchRequestPic.sortDescriptors = [NSSortDescriptor(keyPath: \Picture.dateTaken, ascending: true)]
do {
let pictures = try viewContext.fetch(fetchRequestPic)
for picture in pictures {
currentPicNumber = currentPicNumber + 1
if let imgData = convertImage(picture: picture), let imgThumbData = convertImageThumb(picture: picture) {
// Save Converted
picture.pictureData = imgData
picture.pictureThumbnailData = imgThumbData
// Save Image
saveContext()
viewContext.refreshAllObjects()
} else {
viewContext.delete(picture)
saveContext()
viewContext.refreshAllObjects()
}
}
} catch {
print("Fetch Failed")
}
}
}
And finally saving
func saveContext() {
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
For the past several days every time I log in to to the Cloudkit dashboard I get Error looking up Developer Teams, Please sign out and try again. No amount of singing out and back in changes anything.
Hi,
Not sure how to describe my issue best: I am using SwiftData and CloudKit to store my data.
In the past, when I tested my app on different devices, the data would sync between the devices automatically. For whatever reason this has stopped now and the data no longer syncs. No matter what I do, it feels as if all the data is actually stored just locally on each device.
How can I check if the data is actually stored in the cloud and what could be reasons, why its no longer synching between my devices (and yes, I am logged in with the same Apple ID on all devices).
Thanks for any hint!
Max
I have a very simple CoreData model that has 1 entity and 2 attributes.
This code works fine:
.onChange(of: searchText) { _, text in
evnts.nsPredicate = text.isEmpty ? nil :NSPredicate(format: "eventName CONTAINS %@ " , text )
but I'd like to also search with the same text string for my second attribute (which is a Date). I believe an OR is appropriate for two conditions (find either one). See attempted code below:
evnts.nsPredicate = text.isEmpty ? nil : NSPredicate(format: "(eventName CONTAINS %@) OR (dueDate CONTAINS %i) " , text )
This crashes immediately %@ does the same. Is there a way to accomplish this?
How is SwiftUI not an option below?
Problem
The following code doesn't work:
let predicate = #Predicate<Car> { car in
car.size == size //This doesn't work
}
Console Error
Query encountered an error: SwiftData.SwiftDataError(_error: SwiftData.SwiftDataError._Error.unsupportedPredicate)
Root cause
Size is an enum, #Predicate works with other type such as String however doesn't work with enum
Enum value is saved however is not filtered by #Predicate
Environment
Xcode: 15.0 (15A240d) - App Store
macOS: 14.0 (23A339) - Release Candidate
Steps to reproduce
Run the app on iOS 17 or macOS Sonoma
Press the Add button
Notice that the list remains empty
Expected behaviour
List should show the newly created small car
Actual behaviour
List remains empty inspite of successfully creating the small car.
Feedback
FB13194334
Code
Size
enum Size: String, Codable {
case small
case medium
case large
}
Car
import SwiftData
@Model
class Car {
let id: UUID
let name: String
let size: Size
init(
id: UUID,
name: String,
size: Size
) {
self.id = id
self.name = name
self.size = size
}
}
ContentView
struct ContentView: View {
var body: some View {
NavigationStack {
CarList(size: .small)
}
}
CarList
import SwiftUI
import SwiftData
struct CarList: View {
let size: Size
@Environment(\.modelContext)
private var modelContext
@Query
private var cars: [Car]
init(size: Size) {
self.size = size
let predicate = #Predicate<Car> { car in
car.size == size //This doesn't work
}
_cars = Query(filter: predicate, sort: \.name)
}
var body: some View {
List(cars) { car in
VStack(alignment: .leading) {
Text(car.name)
Text("\(car.size.rawValue)")
Text(car.id.uuidString)
.font(.footnote)
}
}
.toolbar {
Button("Add") {
createCar()
}
}
}
private func createCar() {
let name = "aaa"
let car = Car(
id: UUID(),
name: name,
size: size
)
modelContext.insert(car)
}
}
I am implementing a custom migration, and facing an issue while implementing a WAL checkpointing.
Here is the code for WAL checkpointing
func forceWALCheckpointingForStore(at storeURL: URL, model: NSManagedObjectModel) throws {
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
let options = [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
let store = try persistentStoreCoordinator.addPersistentStore(type: .sqlite, at: storeURL, options: options)
try persistentStoreCoordinator.remove(store)
}
When the coordinator tries to add the store I am getting the following error
fault: Store opened without NSPersistentHistoryTrackingKey but previously had been opened with NSPersistentHistoryTrackingKey - Forcing into Read Only mode store
My questions are
Is it really necessary to force WAL checkpointing before migration? I am expecting NSMigrationManager to handle it internally. I am assuming this because the migrateStore function asks for the sourceType where I am passing StoreType.sqlite
If checkpointing is required, then how do I address the original issue
Note:
Since my app supports macOS 13, I am not able to use the newly introduced Staged migrations.
There is similar question on Stackoverflow that remains unanswered. https://stackoverflow.com/q/69131577/1311902
In my app, I've been using ModelActors in SwiftData, and using actors with a custom executor in Core Data to create concurrency safe services.
I have multiple actor services that relate to different data model components or features, each that have their own internally managed state (DocumentService, ImportService, etc).
The problem I've ran into, is that I need to be able to use multiple of these services within another service, and those services need to share the same context. Swift 6 doesn't allow passing contexts across actors.
The specific problem I have is that I need a master service that makes multiple unrelated changes without saving them to the main context until approved by the user.
I've tried to find a solution in SwiftData and Core Data, but both have the same problem which is contexts are not sendable. Read the comments in the code to see the issue:
/// This actor does multiple things without saving, until committed in SwiftData.
@ModelActor
actor DatabaseHelper {
func commitChange() throws {
try modelContext.save()
}
func makeChanges() async throws {
// Do unrelated expensive tasks on the child context...
// Next, use our item service
let service = ItemService(modelContainer: SwiftDataStack.shared.container)
let id = try await service.expensiveBackgroundTask(saveChanges: false)
// Now that we've used the service, we need to access something the service created.
// However, because the service created its own context and it was never saved, we can't access it.
let itemFromService = context.fetch(id) // fails
// We need to be able to access changes made from the service within this service,
/// so instead I tried to create the service by passing the current service context, however that results in:
// ERROR: Sending 'self.modelContext' risks causing data races
let serviceFromContext = ItemService(context: modelContext)
// Swift Data doesn't let you create child contexts, so the same context must be used in order to change data without saving.
}
}
@ModelActor
actor ItemService {
init(context: ModelContext) {
modelContainer = SwiftDataStack.shared.container
modelExecutor = DefaultSerialModelExecutor(modelContext: context)
}
func expensiveBackgroundTask(saveChanges: Bool = true) async throws -> PersistentIdentifier? {
// Do something expensive...
return nil
}
}
Core Data has the same problem:
/// This actor does multiple things without saving, until committed in Core Data.
actor CoreDataHelper {
let parentContext: NSManagedObjectContext
let context: NSManagedObjectContext
/// In Core Data, I can create a child context from a background context.
/// This lets you modify the context and save it without updating the main context.
init(progress: Progress = Progress()) {
parentContext = CoreDataStack.shared.newBackgroundContext()
let childContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
childContext.parent = parentContext
self.context = childContext
}
/// To commit changes, save the parent context pushing them to the main context.
func commitChange() async throws {
// ERROR: Sending 'self.parentContext' risks causing data races
try await parentContext.perform {
try self.parentContext.save()
}
}
func makeChanges() async throws {
// Do unrelated expensive tasks on the child context...
// As with the Swift Data example, I am unable to create a service that uses the current actors context from here.
// ERROR: Sending 'self.context' risks causing data races
let service = ItemService(context: self.context)
}
}
Am I going about this wrong, or is there a solution to fix these errors?
Some services are very large and have their own internal state. So it would be very difficult to merge all of them into a single service. I also am using Core Data, and SwiftData extensively so I need a solution for both.
I seem to have trapped myself into a corner trying to make everything concurrency save, so any help would be appreciated!
I am trying to migrate my Core Data model to a new version with a new attribute added to it. Since my app supports macOS 13 I am not able to use the newly introduced Staged migrations.
After much digging I found that the app is not able to find the Mapping Model when one of the attribute has "Preserve after deletion" enabled.
I have enabled migration debbuging using
com.apple.CoreData.MigrationDebug 1
I am getting following error
error: CoreData: error: (migration) migration failed with error Error Domain=NSCocoaErrorDomain Code=134140 "Persistent store migration failed, missing mapping model."
What is the way out here?
I have some models in my app:
[SDPlanBrief.self, SDAirport.self, SDChart.self, SDIndividualRunwayAirport.self, SDLocationBrief.self]
SDLocationBrief has a @Relationship with SDChart
When I went live with my app I didn't have a versioned schema, but quickly had to change that as I needed to add items to my SDPlanBrief Model.
The first versioned schema I made included only the model that I had made a change to.
static var models: [any PersistentModel.Type] {
[SDPlanBrief.self]
}
I had made zero changes to my model container and the whole time, and it was working fine. The migration worked well and this is what I was using:
.modelContainer(for: [SDAirport.self, SDIndividualRunwayAirport.self, SDLocationBrief.self, SDChart.self, SDPlanBrief.self])
I then saw that to do this all properly, I should actually include ALL of my @Models in the versioned schema:
enum AllSwiftDataSchemaV3: VersionedSchema {
static var models: [any PersistentModel.Type] {
[SDPlanBrief.self, SDAirport.self, SDChart.self, SDIndividualRunwayAirport.self, SDLocationBrief.self]
}
static var versionIdentifier: Schema.Version = .init(2, 0, 0)
}
extension AllSwiftDataSchemaV3 {
@Model
class SDPlanBrief {
var destination: String
etc...
init(destination: String, etc...) {
self.destination = destination
etc...
}
}
@Model
class SDAirport {
var catABMinima: String
etc...
init(catABMinima: String etc...) {
self.catABMinima = catABMinima
etc...
}
}
@Model
class SDChart: Identifiable {
var key: String
etc...
var brief: SDLocationBrief? // @Relationship with SDChart
init(key: String etc...) {
self.key = key
etc...
}
}
@Model
class SDIndividualRunwayAirport {
var icaoCode: String
etc...
init(icaoCode: String etc...) {
self.icaoCode = icaoCode
etc...
}
}
@Model
class SDLocationBrief: Identifiable {
var briefString: String
etc...
@Relationship(deleteRule: .cascade, inverse: \SDChart.brief) var chartsArray = [SDChart]()
init(
briefString: String,
etc...
chartsArray: [SDChart] = []
) {
self.briefString = briefString
etc...
self.chartsArray = chartsArray
}
}
}
This is ALL my models in here btw.
I saw also that modelContainer needed updating to work better for versioned schemas. I changed my modelContainer to look like this:
actor ModelContainerActor {
@MainActor
static func container() -> ModelContainer {
let schema = Schema(
versionedSchema: AllSwiftDataSchemaV3.self
)
let configuration = ModelConfiguration()
let container = try! ModelContainer(
for: schema,
migrationPlan: PlanBriefMigrationPlan.self,
configurations: configuration
)
return container
}
}
and I am passing in like so:
.modelContainer(ModelContainerActor.container())
Each time I run the app now, I suddenly get this message a few times in a row:
CoreData: error: Attempting to retrieve an NSManagedObjectModel version checksum while the model is still editable. This may result in an unstable verison checksum. Add model to NSPersistentStoreCoordinator and try again.
I typealias all of these models too for the most recent V3 version eg:
typealias SDPlanBrief = AllSwiftDataSchemaV3.SDPlanBrief
Can someone see if I am doing something wrong here? It seems my TestFlight users are experiencing a crash every now and then when certain views load (I assume when accessing @Query objects). Seems its more so when a view loads quickly, like when removing a subscription view where the data may not have had time to load??? Can someone please have a look and help me out.
Hi Folks,
starting with iOS18 and using Xcode16, accessing fetchedProperties results in an error. I identified the issue to occur as soon as the initialization of a fetched property with external binary data storage starts.
Console output during debugging:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'This expression has evaluation disabled'
*** First throw call stack:
[...]
libc++abi: terminating due to uncaught exception of type NSException
Console output when trying to "print" the item via the contact menu of the debugger:
Printing description of variable:
error: error: Execution was interrupted, reason: internal ObjC exception breakpoint(-6)..
The process has been returned to the state before expression evaluation.
Message from debugger: killed
The identical code works with iOS before iOS 18 (same for iPadOS 18).
Does anyone observed a similar issue and figured out a solution already?
Cheers,
folox
I'm getting the following error message when executing the rollback method in a modelContext, what could be causing this ?
SwiftData/ModelSnapshot.swift:46: Fatal error: A ModelSnapshot must be initialized with a known-keys dictionary
I have a UIKit app where I've adopted SwiftData and I'm struggling with a crash coming in from some of my users. I'm not able to reproduce it myself and as it only happens to a small fraction of my user base, it seems like a race condition of some sort.
This is the assertion message:
SwiftData/DefaultStore.swift:453: Fatal error: API Contract Violation: Editors must register their identifiers before invoking operations on this store SwiftData.DefaultStore: 00CF060A-291A-4E79-BEC3-E6A6B20F345E did not. (ID is unique per crash)
This is the ModelActor that crashes:
@available(iOS 17, *)
@ModelActor
actor ConsumptionDatabaseStorage: ConsumptionSessionStorage {
struct Error: LocalizedError {
var errorDescription: String?
}
private let sortDescriptor = [SortDescriptor(\SDConsumptionSession.startTimeUtc, order: .reverse)]
static func createStorage(userId: String) throws -> ConsumptionDatabaseStorage {
guard let appGroupContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: UserDefaults.defaultAppGroupIdentifier) else {
throw Error(errorDescription: "Invalid app group container ID")
}
func createModelContainer(databaseUrl: URL) throws -> ModelContainer {
return try ModelContainer(for: SDConsumptionSession.self, SDPriceSegment.self, configurations: ModelConfiguration(url: databaseUrl))
}
let databaseUrl = appGroupContainer.appendingPathComponent("\(userId).sqlite")
do {
return self.init(modelContainer: try createModelContainer(databaseUrl: databaseUrl))
} catch {
// Creating the model storage failed. Remove the database file and try again.
try? FileManager.default.removeItem(at: databaseUrl)
return self.init(modelContainer: try createModelContainer(databaseUrl: databaseUrl))
}
}
func isStorageEmpty() async -> Bool {
(try? self.modelContext.fetchCount(FetchDescriptor<SDConsumptionSession>())) ?? 0 == 0 // <-- Crash here!
}
func sessionsIn(interval: DateInterval) async throws -> [ConsumptionSession] {
let fetchDescriptor = FetchDescriptor(predicate: #Predicate<SDConsumptionSession> { sdSession in
if let startDate = sdSession.startTimeUtc {
return interval.start <= startDate && interval.end > startDate
} else {
return false
}
}, sortBy: self.sortDescriptor)
let consumptionSessions = try self.modelContext.fetch(fetchDescriptor) // <-- Crash here!
return consumptionSessions.map { ConsumptionSession(swiftDataSession: $0) }
}
func updateSessions(sessions: [ConsumptionSession]) async throws {
if #unavailable(iOS 18) {
// Price segments are duplicated if re-inserted so unfortunately we have to delete and reinsert sessions.
// On iOS 18, this is enforced by the #Unique macro on SDPriceSegment.
let sessionIds = Set(sessions.map(\.id))
try self.modelContext.delete(model: SDConsumptionSession.self, where: #Predicate<SDConsumptionSession> {
sessionIds.contains($0.id)
})
}
for session in sessions {
self.modelContext.insert(SDConsumptionSession(consumptionSession: session))
}
if self.modelContext.hasChanges {
try self.modelContext.save()
}
}
func deleteAllSessions() async {
if #available(iOS 18, *) {
try? self.modelContainer.erase()
} else {
self.modelContainer.deleteAllData()
}
}
}
The actor conforms to this protocol:
protocol ConsumptionSessionStorage {
func isStorageEmpty() async -> Bool
func hasCreditCardSessions() async -> Bool
func sessionsIn(interval: DateInterval) async throws -> [ConsumptionSession]
func updateSessions(sessions: [ConsumptionSession]) async throws
func deleteAllSessions() async
}
The crash is coming in from line 30 and 41, in other words, when trying to fetch data from the database. There doesn't seem to be any common trait for the crashes. They occur across iOS versions and device types.
Any idea what might cause this?
Hi everyone,
I'm looking for the correct architectural guidance for my SwiftData implementation.
In my Swift project, I have dedicated async functions for adding, editing, and deleting each of my four models. I created these functions specifically to run certain logic whenever these operations occur. Since these functions are asynchronous, I call them from the UI (e.g., from a button press) by wrapping them in a Task.
I've gone through three different approaches and am now stuck.
Approach 1: @MainActor Functions
Initially, my functions were marked with @MainActor and worked on the main ModelContext. This worked perfectly until I added support for App Intents and Widgets, which caused the app to crash with data race errors.
Approach 2: Passing ModelContext as a Parameter
To solve the crashes, I decided to have each function receive a ModelContext as a parameter. My SwiftUI views passed the main context (which they get from @Environment(\.modelContext)), while the App Intents and Widgets created and passed in their own private context. However, this approach still caused the app to crash sometimes due to data race errors, especially during actions triggered from the main UI.
Approach 3: Creating a New Context in Each Function
I moved to a third approach where each function creates its own ModelContext to work on. This has successfully stopped all crashes. However, now the UI actions don't always react or update. For example, when an object is added, deleted, or edited, the change isn't reflected in the UI. I suspect this is because the main context (driving the UI) hasn't been updated yet, or because the async function hasn't finished its work.
My Question
I'm not sure what to do or what the correct logic should be. How should I structure my data operations to support the main UI, Widgets, and App Intents without causing crashes or UI update failures?
Here is the relevant code using my third (and current) approach. I've shortened the helper functions for brevity.
// MARK: - SwiftData Operations
extension DatabaseManager {
/// Creates a new assignment and saves it to the database.
public func createAssignment(
name: String, deadline: Date, notes: AttributedString,
forCourseID courseID: UUID, /*...other params...*/
) async throws -> AssignmentModel {
do {
let context = ModelContext(container)
guard let course = findCourse(byID: courseID, in: context) else {
throw DatabaseManagerError.itemNotFound
}
let newAssignment = AssignmentModel(
name: name, deadline: deadline, notes: notes, course: course, /*...other properties...*/
)
context.insert(newAssignment)
try context.save()
// Schedule notifications and add to calendar
_ = try? await scheduleReminder(for: newAssignment)
newAssignment.calendarEventIDs = await CalendarManager.shared.addEventToCalendar(for: newAssignment)
try context.save()
await MainActor.run {
WidgetCenter.shared.reloadTimelines(ofKind: "AppWidget")
}
return newAssignment
} catch {
throw DatabaseManagerError.saveFailed
}
}
/// Finds a specific course by its ID in a given context.
public func findCourse(byID id: UUID, in context: ModelContext) -> CourseModel? {
let predicate = #Predicate<CourseModel> { $0.id == id }
let fetchDescriptor = FetchDescriptor<CourseModel>(predicate: predicate)
return try? context.fetch(fetchDescriptor).first
}
}
// MARK: - Helper Functions (Implementations omitted for brevity)
/// Schedules a local user notification for an event.
func scheduleReminder(for assignment: AssignmentModel) async throws -> String {
// ... Full implementation to create and schedule a UNNotificationRequest
return UUID().uuidString
}
/// Creates a new event in the user's selected calendars.
extension CalendarManager {
func addEventToCalendar(for assignment: AssignmentModel) async -> [String] {
// ... Full implementation to create and save an EKEvent
return [UUID().uuidString]
}
}
Thank you for your help.