I'm getting a crash in SwiftData but only on one specific device (iPhone 16 pro running 18.2 22C5131e) and not on an ipad or simulator
I cant troubleshoot this crash and its quite frustrating, all I am getting is
@Query(sort: \Todo.timestamp, order: .reverse) private var todos: [Todo]
ForEach(todos.filter { !$0.completed }) { item in // <---crash
TodoListView()
}
and the error is
Thread 1: signal SIGABRT
An abort signal terminated the process. Such crashes often happen because of an uncaught exception or unrecoverable error or calling the abort() function.
and
_SwiftData_SwiftUI.Query.wrappedValue.getter : τ_0_1
-> 0x105b98b58 <+160>: ldur x8, [x29, #-0x40]
0x105b98b5c <+164>: ldur x0, [x29, #-0x38]
0x105b98b60 <+168>: ldur x1, [x29, #-0x30]
0x105b98b64 <+172>: ldur x9, [x29, #-0x20]
0x105b98b68 <+176>: stur x9, [x29, #-0x28]
0x105b98b6c <+180>: ldr x8, [x8, #0x8]
0x105b98b70 <+184>: blr x8
0x105b98b74 <+188>: ldur x0, [x29, #-0x28]
0x105b98b78 <+192>: sub sp, x29, #0x10
0x105b98b7c <+196>: ldp x29, x30, [sp, #0x10]
0x105b98b80 <+200>: ldp x20, x19, [sp], #0x20
0x105b98b84 <+204>: ret
How do I fix this?
Post
Replies
Boosts
Views
Activity
I've been obsessed with this topic for the past couple of weeks and unfortunately there just isn't a good answer out there even from the community. Therefore I am hoping that I can summon Quinn to get an official Apple position (on what's seemingly a fairly fundamental part of using SwiftUI).
Consider this simple example:
import Foundation
@MainActor
@Observable
class UserViewModel {
var name: String = "John Doe"
var age: Int = 30
// other properties and logic
}
// NetworkManager does not need to update the UI but needs to read/write from UserViewModel.
class NetworkManager {
func updateUserInfo(viewModel: UserViewModel) {
Task {
// Read values from UserViewModel prior to making a network call
let userName: String
let userAge: Int
// Even for a simple read, we have to jump onto the main thread
await MainActor.run {
userName = viewModel.name
userAge = viewModel.age
}
// Now perform network call with the retrieved values
print("Making network call with userName: \(userName) and userAge: \(userAge)")
// Simulate network delay
try await Task.sleep(nanoseconds: 1_000_000_000)
// After the network call, we update the values, again on the main thread
await MainActor.run {
viewModel.name = "Jane Doe"
viewModel.age = 31
}
}
}
}
// Example usage
let viewModel = UserViewModel()
let networkManager = NetworkManager()
// Calling from some background thread or task
Task {
await networkManager.updateUserInfo(viewModel: viewModel)
}
In this example, we can see a few things
The ViewModel is a class that manages states centrally
It needs to be marked as MainActor to ensure that updating of the states is done on the main thread (this is similar to updating @Published in the old days). I know this isn't officially documented in Apple's documentation. But I've seen this mentioned many times to be recommended approach including www.youtub_.com/watch?v=4dQOnNYjO58 and here also I have observed crashes myself when I don't follow this practise
Now so far so good, IF we assume that ViewModel are only in service to Views. The problem comes when the states need to be accessed outside of Views.
in this example, NetworkManager is some random background code that also needs to read/write centralized states. In this case it becomes extremely cumbersome. You'd have to jump to mainthread for each write (which ok - maybe that's not often) but you'd also have to do that for every read.
Now. it gets even more cumbersome if the VM holds a state that is a model object, mentioned in this thread..
Consider this example (which I think is what @Stokestack is referring to)
import Foundation
// UserModel represents the user information
@MainActor // Ensuring the model's properties are accessed from the main thread
class UserModel {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
@MainActor
@Observable
class UserViewModel {
var userModel: UserModel
init(userModel: UserModel) {
self.userModel = userModel
}
}
// NetworkManager does not need to update the UI but needs to read/write UserModel inside UserViewModel.
class NetworkManager {
func updateUserInfo(viewModel: UserViewModel) {
Task {
// Read values from UserModel before making a network call
let userName: String
let userAge: Int
// Jumping to the main thread to safely read UserModel properties
await MainActor.run {
userName = viewModel.userModel.name
userAge = viewModel.userModel.age
}
// Simulate a network call
print("Making network call with userName: \(userName) and userAge: \(userAge)")
try await Task.sleep(nanoseconds: 1_000_000_000)
// After the network call, updating UserModel (again, on the main thread)
await MainActor.run {
viewModel.userModel.name = "Jane Doe"
viewModel.userModel.age = 31
}
}
}
}
// Example usage
let userModel = UserModel(name: "John Doe", age: 30)
let viewModel = UserViewModel(userModel: userModel)
let networkManager = NetworkManager()
// Calling from a background thread
Task {
await networkManager.updateUserInfo(viewModel: viewModel)
}
Now I'm not sure the problem he is referring still exists (because I've tried and indeed you can make codeable/decodables marked as @Mainactor) but it's really messy.
Also, I use SwiftData and I have to imagine that @Model basically marks the class as @MainActor for these reasons.
And finally, what is the official Apple's recommended approach? Clearly Apple created @Observable to hold states of some kind that drives UI. But how do you work with this state in the background?
I have a working ValueTransformer that runs fine in simulator/device, but crashes in SwiftUI Preview. Even though they are the same code.
Here is my code
import Foundation
final class StringBoolDictTransformer: ValueTransformer {
override func transformedValue(_ value: Any?) -> Any? {
guard let stringBoolDict = value as? [String: Bool] else { return nil }
let nsDict = NSMutableDictionary()
for (key, bool) in stringBoolDict {
nsDict[key] = NSNumber(value: bool)
}
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: nsDict, requiringSecureCoding: true)
return data
} catch {
debugPrint("Unable to convert [Date: Bool] to a persistable form: \(error.localizedDescription)")
return nil
}
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? Data else { return nil }
do {
guard let nsDict = try NSKeyedUnarchiver.unarchivedDictionary(ofKeyClass: NSString.self, objectClass: NSNumber.self, from: data) else {
return nil
}
var result = [String: Bool]()
for (key, value) in nsDict {
result[key as String] = value.boolValue
}
return result
} catch {
debugPrint("Unable to convert persisted Data to [Date: Bool]: \(error.localizedDescription)")
return nil
}
}
override class func allowsReverseTransformation() -> Bool {
true
}
override class func transformedValueClass() -> AnyClass {
NSDictionary.self
}
}
and here is the container
public struct SwiftDataManager {
public static let shared = SwiftDataManager()
public var sharedModelContainer: ModelContainer
init() {
ValueTransformer.setValueTransformer(
StringBoolDictTransformer(), forName: NSValueTransformerName("StringBoolDictTransformer")
)
let schema = Schema([,
Plan.self
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
sharedModelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}
}
and the model
@Model
final class Plan {
@Attribute(.transformable(by: StringBoolDictTransformer.self))
var dict: [String: Bool] = [:]
}
I would get that container and pass it in appdelegate and it works fine. I would get that container and pass it inside a #Preview and it would crash with the following:
Runtime: iOS 17.5 (21F79) - DeviceType: iPhone 15 Pro
CoreFoundation:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "dict"; desired type = NSDictionary; given type = _NSInlineData; value = {length = 2, bytes = 0x7b7d}.'
libsystem_c.dylib:
abort() called
Version 16.0 (16A242d)
I have a network call that can sometimes take longer than 30 seconds and user might sometimes background the app after waiting a bit (think like a image generation done on a server which takes a while). I want to make sure that I have exhausted all the options available to provide the best possible solution for the user
(1) Use beginBackgroundTask. The downside to this is that I'm only given about 30 seconds or so, and if it takes longer, the call just get killed.
(2) Use URLSessionConfiguration.background. The upside is that this can take as long as it needs but it seems to be delegated to the system, and you never know when it will run? What if the user stays in the foreground and now the user will not know when the call will even begin (determined by the OS)
(3) Use BGProcessingTask. Again problem is that we cant control when the task is run (which in this case we want it to be immediately).
So really none of the options really is ideal. Are there other options?
What I would like ideally is
The call should start immediately upon user request
The call should go on indefinitely when the app stays in foreground
The call should go on for an extended period (like 2 minutes) if the user puts the app in background
If the call is completed in the background, have a way for the app to retrieve the result when the user brings the app back in the foreground
According to this old thread
the answer is no. But I never understood why.
In the old world. It was always required that you make changes to @Published properties on the main thread. In fact compiler would complain.
In the main world, can you just update that in the background thread? And then SwiftUI take cares of refreshing the views on the main thread? So I guess that begs that question, why did it used to require it for @Published?
Furthermore, I have recently gotten new crashes when update is done from background but I can't be sure it's related:
For example
I have the following, and the crash is as follows:
@Observable
class PlanViewModel {
var stagingPlan: Plan?
func savePlan() async {
//some code here....
stagingPlan = nil //crash
}
}
Is this issue potentially related to main thread? Should I do that assignment forcefully on main thread?
call stack 1
call stack 2
call stack 3
I dont know how to troubleshoot this further as xcode doesnt provide me any info other than that one red line
I created a local notification as follows:
func scheduleNotification(title: String, subtitle: String = "", date: Date, id: String) {
// Extract the components from the date
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let minute = calendar.component(.minute, from: date)
let second = calendar.component(.second, from: date)
// Set the extracted components into DateComponents
var dateComponents = DateComponents()
dateComponents.hour = hour
dateComponents.minute = minute
dateComponents.second = second
let content = UNMutableNotificationContent()
content.title = title
content.subtitle = subtitle
content.sound = UNNotificationSound.default
let action1 = UNNotificationAction(identifier: Constants.NOTIFICATION_ACTION_IDENTIFIER_1.id, title: Constants.NOTIFICATION_ACTION_IDENTIFIER_1.label, options: [])
let action2 = UNNotificationAction(identifier: Constants.NOTIFICATION_ACTION_IDENTIFIER_2.id, title: Constants.NOTIFICATION_ACTION_IDENTIFIER_2.label, options: [])
let category = UNNotificationCategory(identifier: "reminderCategory", actions: [action1, action2], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
content.categoryIdentifier = "reminderCategory"
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
// add our notification request
UNUserNotificationCenter.current().add(request)
}
and I also have
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// Handle foreground presentation options
completionHandler([.sound, .badge])
}
but for some reason the notification only shown on the phone. I've made sure that the phone is locked, apple watch is unlocked and also the notification setting in the watch app for this app is set to mirror.
Consider this code
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
init() {
let schema = Schema([
...
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
sharedModelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
SettingsViewModel.shared = SettingsViewModel(modelContext: sharedModelContainer.mainContext)
}
I'm basically saving a copy of mainContext in a viewModel. And then later on uses that viewModel to operate on the models while using the mainActor.
Is this ok? That same container is also pass into the view using
.modelContainer(sharedModelContainer)
Can it be used in both ways like that?
Consider a sample SwiftData project
var body: some View {
List(recipes) { recipe in
NavigationLink(recipe.name, destination: RecipeView(recipe))
}
}
For SwiftUI views that uses SwiftData it's very straight forward.
However, if I need to read/write to SwiftData while in the background (let's say after a network call), the app crashes.
Imagine a simple workflow where
(1) user selects some model objects (via SwiftData modelContext)
(2) now I need to pass these objects to some API server
(3) in a background thread somewhere I need to use the modelActor but it now cannot read/write to the original model objects without risk of crashing
So instead, the only way I thought of is to create a separate modelContext for background processing. There I passed down the container from the main app, and creates a new ModelActor that takes in the container and owns a new modelContext of its own.
This works, without crash. However it introduces many side effects:
(1) read/write from this modelActor seems to trigger view changes for SwiftData's modelContext. But not vice versa.
(2) model objects fetched from one context cannot be used in another context.
(3) changes made in the actor also doesn’t not automatically sync with icloud but changes in SwiftData’s modelContext do.
So I guess the bigger question here is, what is the proper usage of SwiftData in a background thread?
What is the correct usage of two modelContexts (when you have a main one for swiftUI, and a background one for background operations - like networking)?
Is it expected that when the main one make updates, that the background modelContexts would also update automatically?
And if it doesn't, is there a way to force it to get updates?
I was able to add a spotlight effect to my entities using ImageBasedLightComponent and the sample code. However, I noticed that whenever you set ImageBasedLightComponent the environmental lighting is completely turned off. Is it possible to merge them somehow?
So imagine you have a toy in a the real world, and you shine a flashlight on it. The environment light should still have an effect right?
Let's say I've created a scene with 3 models inside side by side. Now upon user interaction, I'd like to change these models to another model (that is also in the same reality composer pro project). Is that possible? How can one do that?
One way I can think of is to just load all the individual models in RealityView and then just toggle the opacity to show/hide the models. But this doesn't seem like the right way for performance/memory reasons.
How do you swap in and out usdz models?
I have the following code working great in iOS16
TextField(
"Describe...",
text: Binding(
get: { promptText },
set:
{ (newValue, _) in
if let _ = newValue.lastIndex(of: "\n") {
isFocused = false
} else {
promptText = newValue
}
}
),
axis: .vertical
)
but in iOS 17 it breaks.
But it breaks in a very mysterious way.
Firstly, the @ FocusState doesn't work.
Setting it to false does not dismiss keyboard. Fine, but also what's weird is that when I press the done button, the new line gets added to the textField anyway, even if I don't set the newValue. What's going on?
In https://developer.apple.com/wwdc22/10054 it is shown that the best way to organize an iPad layout is by:
struct TwoColumnContentView: View {
@Binding var showExperiencePicker: Bool
@EnvironmentObject private var navigationModel: NavigationModel
var categories = Category.allCases
var dataModel = DataModel.shared
var body: some View {
NavigationSplitView(
columnVisibility: $navigationModel.columnVisibility
) {
List(
categories,
selection: $navigationModel.selectedCategory
) { category in
NavigationLink(category.localizedName, value: category)
}
.navigationTitle("Categories")
.toolbar {
ExperienceButton(isActive: $showExperiencePicker)
}
} detail: {
NavigationStack(path: $navigationModel.recipePath) {
RecipeGrid(category: navigationModel.selectedCategory)
}
}
}
}
NavigationModel defined as
final class NavigationModel: ObservableObject, Codable {
@Published var selectedCategory: Category?
@Published var recipePath: [Recipe]
@Published var columnVisibility: NavigationSplitViewVisibility
}
But what happens when in a child view, it triggers off some action in some other ViewModel that needs to update the navigation stack?
struct ChildViewModel {
func childViewCalledAndDidSomething {
//now I need to update recipePath, how?
}
}
public var zonesChangeToken: [CKRecordZone.ID: CKServerChangeToken]? {
get {
if(backingPreviousZonesChangeToken == nil) {
guard let defaults: UserDefaults = UserDefaults(suiteName: CloudKitHandler.APP_GROUP_ID) else { return nil }
guard let data = defaults.data(forKey: CloudKitHandler.CK_PREVIOUS_ZONES_CHANGE_TOKEN)
else { return [CKRecordZone.ID: CKServerChangeToken]() }
do {
let unarchiver: NSKeyedUnarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
unarchiver.requiresSecureCoding = true
backingPreviousZonesChangeToken = try unarchiver.decodeTopLevelObject() as? [CKRecordZone.ID: CKServerChangeToken]
} catch { }
}
return backingPreviousZonesChangeToken
}
set(value) {
backingPreviousZonesChangeToken = value
guard let value = value else { return }
guard let defaults: UserDefaults = UserDefaults(suiteName: CloudKitHandler.APP_GROUP_ID) else { return }
let archiver: NSKeyedArchiver = NSKeyedArchiver(requiringSecureCoding: true)
archiver.encode(value)
archiver.finishEncoding()
defaults.setValue(archiver.encodedData, forKey: CloudKitHandler.CK_PREVIOUS_ZONES_CHANGE_TOKEN)
}
}I'm trying to encode/decode a dictionary of IDs and Tokens. But for some reason the decode always gives me a nil.How to fix?Error Domain=NSCocoaErrorDomain Code=4864 "value for key '$0' was of unexpected class 'NSDictionary'. Allowed classes are '(null)'." UserInfo={NSDebugDescription=value for key '$0' was of unexpected class 'NSDictionary'. Allowed classes are '(null)'.}
Let's say the sequence of events go like thisI have token A for a Shared DatabaseRecords get created in Shared DatabaseI fetch for changes and downloaded new records, gets token BI accepted a new share in the same zone and databaseI take the new record (NEW_REC) from inside CKShare.Metadata and saves it, but I dont have a new tokenBut now NEW_REC gets deleted by the sharerI then fetch for new changes for the zone and use token B, but CK will not let me know NEW_REC is deletedI tested this using the dashboard, so it has nothing to do with my code.Is this intentional? I'm thinking that the algorithm is designed to work like this because it thinks that from the perspective of token B, NEW_REC never existed? So why bother telling the client is deleted?But that doesn't taken into account that the new record came from metadata. I also wonder the same case if you did CKQuery to get new records, without using CKFetchRecordZoneChangesOperation do you suffer from the same problem?