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.