3

I’ve run into a reproducible issue when using SwiftData with @Model classes that contain non-optional value type properties (struct) with optional properties inside.

After creating a new model, the app crashes with:

Fatal error: Passed nil for a non-optional keypath \MyModel.myStruct

This happens immediately after try? modelContext.save() is called.

This is my example:

import SwiftData
import SwiftUI

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Query(sort: \MyModel.name) private var models: [MyModel]
    @State private var selectedModel: MyModel?

    var body: some View {
        NavigationStack {
            List(models) { model in
                Button(model.name) {
                    selectedModel = model
                }
            }
            .navigationTitle("Models")
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    Button("Add") {
                        let newModel = MyModel(name: "Test")
                        modelContext.insert(newModel)
                        try? modelContext.save()
                        selectedModel = newModel
                    }
                }
            }
            .sheet(item: $selectedModel) { model in
                Text("Editing \(model.name)")
            }
        }
    }
}

@Model
class MyModel {
    var name: String
    var myStruct: MyStruct

    init(name: String) {
        self.name = name
        self.myStruct = MyStruct(value: nil)
    }
}

struct MyStruct: Codable {
    var value: String?
}

@main
struct MinimalReproApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: MyModel.self)
    }
}

If I change the value property in MyStruct so it's no longer an optional, the crash no longer occurs.

I would prefer a solution that ideally does not involve modifying MyStruct.

3
  • AFAIK (could not find Apple docs on this), in SwiftData if you have var myStruct: MyStruct it should be optional ?. You could just make @Model class MyStructModel and use that instead of a struct. Commented May 10 at 2:26
  • If all properties can be nil and that is your default value then make the struct property, myStruct, optional instead and make that property have nil as its default value instead. This makes much more sense from a design point in my opinion Commented May 10 at 6:07
  • If you ever want to do migrations it’s best the model properties are all optionals from the outset Commented May 10 at 22:18

1 Answer 1

2

You’re encountering this crash due to how SwiftData internally manages persistence and serialization of model properties—especially with non-optional value types (struct) stored as non-optional properties in a @Model class, which themselves contain optional values.

When you declare:
var myStruct: MyStruct

SwiftData treats myStruct as a non-optional embedded value, meaning it expects every key path within myStruct (i.e., value) to also be non-optional, or at the very least, to not be nil at encoding time.

However, you’re passing:
self.myStruct = MyStruct(value: nil)

Even though myStruct is non-optional, its property value is nil, which causes SwiftData’s encoder to crash when it encounters a nil in a non-optional path (as far as the serialization logic is concerned). This is a known current limitation or bug in SwiftData as of iOS 17.

The error you're seeing:
Fatal error: Passed nil for a non-optional keypath \MyModel.myStruct
…indicates that SwiftData’s encoder is incorrectly assuming that because myStruct is non-optional, all of its fields are also non-optional, and it cannot handle nil in a nested optional during encoding.

You have a couple options:

First, you can make myStruct optional — this doesn't change the implementation of MyStruct as you requested:

@Model
class MyModel {
    var name: String
    var myStruct: MyStruct?

    init(name: String) {
        self.name = name
        self.myStruct = MyStruct(value: nil)
    }
}

This is currently the only reliable way to use structs with optional members in SwiftData.

Alternatively, you could provide a non-optional value at init-time (if you really want myStruct non-optional)

init(name: String) {
    self.name = name
    self.myStruct = MyStruct(value: "")
}

But this changes the semantics of your data—"" is not the same as nil.

Hope that helps, or gets you closer to the answer you're looking for!

Sign up to request clarification or add additional context in comments.

1 Comment

This seems like a completely ridiculous bug from Apple. how is this not a reasonable use case?!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.