App sometimes crashes when saving a SwiftData item containing a specific type

I'm working on an app to help with cataloguing books, and using SwiftData for the first time. This is the model I have for each book:

@Model
final class Book {
    var name: String?
    var author: String?
    var year: Int?
    var isbn: ISBN?
    var shelf: Shelf?
    var collections: [Collection]
    var marcXML: Data?
    let dateCaptured: Date
    init(name: String? = nil, author: String? = nil, year: Int? = nil, isbn: ISBN? = nil, shelf: Shelf? = nil, marcXML: Data? = nil, collections: [Collection]? = nil) {
        self.name = name
        self.author = author
        self.year = year
        self.isbn = isbn
        self.shelf = shelf
        self.dateCaptured = Date()
        self.marcXML = marcXML
        self.collections = collections ?? []
    }
    
}

However, the ISBN property (line 6) has been giving me trouble. Saving a Book object with an ISBN (or maybe just initialising it, it's hard to tell) sometimes crashes the app with the following error:

exception handling request: <NSSQLSaveChangesRequestContext: 0x109d0ae00> , -[__NSDictionaryM UTF8String]: unrecognized selector sent to instance 0x109b3b1c0 with userInfo of (null)

This is the ISBN type (along with an ISBNType enum that it uses):

enum ISBNType: Codable {
    case ten
    case thirteen
}

struct ISBN: Codable {
    fileprivate init(data: String, type: ISBNType) {
        self.data = data
        self.type = type
    }
    let data: String // Raw ISBN number
    let type: ISBNType // Type of ISBN, 10 or 13
}

The init is fileprivate because I want all instances of ISBN to have been validated, so a fileprivate init forces all other parts of the code to call the validation function instead of initialising the struct directly. If I remove the custom init, the app still crashes.

At the moment, I am creating test data on launch. It's stored in memory only so that it can be used in SwiftUI Previews:

@MainActor
let previewData: ModelContainer = {
    do {
        let bookSchema = Schema([Book.self])
        let configuration = ModelConfiguration(isStoredInMemoryOnly: true)
        let container = try ModelContainer(
            for: bookSchema,
            configurations: configuration)
        let modelContext = container.mainContext
        if try modelContext.fetch(FetchDescriptor<Book>()).isEmpty {
            let isbn = try validateISBN(isbn: "9780571211258")
            print("ISBN: \(isbn)")
            var books = [
                Book(name: "A book", year: 2024),
                Book(name: "On Film Making", author: "Alexandar Mackendrick", year: 2005, isbn: isbn)
            ]
            for book in books {
                modelContext.insert(book)
                print("Inserted book")
            }
        }
        return container
    } catch {
        fatalError("Failed to create container")
    }
}()

This is how the container is loaded on launch:

struct Book_AppApp: App {
    var sharedModelContainer: ModelContainer = { // The permanent SwiftData container. Currently not used since previewContainer is being used for testing.
    ...
    }()

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(previewData)
    }
}

Based on the fact that the crash only occurs about 50% of the time, it seems to be something race condition-related, but other than that I have no idea what might be happening. I have set concurrency checking to Strict, so if it is a race-condition I don't think it's in my code.

Try to save the rawValue from the enum as a String. I figured out that the SwiftData model works with enums, but when I tried to filter data by enum using #Predicate before fetching, it didn't work. It crashed the app or filtering didn't work at all. When I converted it to plain String, it all worked well then.

1 Like

I've been working on my app against the iOS 18 SDK and I haven't run into this issue yet, so I think it's been fixed.

1 Like