Hi,
Overview
- I am finding
#Predicateto be a bit tricky when used with Swift Data - It compiles fine but crashes at runtime
- I know the fix for the problem just wondering if such pitfalls can be avoided at compile time
Exception
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'can't use NULL on left hand side'
terminating due to uncaught exception of type NSException
CoreData: error: SQLCore dispatchRequest: exception handling request: <NSSQLFetchRequestContext: 0x11209b000> , can't use NULL on left hand side with userInfo of (null)
Questions
- Could anything be done to improve the safety to avoid such issues at runtime?
- Could I write the code better (better than the fix below) to avoid this?
My thoughts
- Fix is possible however it wasn't obvious to me that there was a problem with my original code
- Would be nice to prevent them at compile time if possible.
- Currently got to be really careful to avoid such crashes.
Code
import Foundation
import SwiftData
@Model
class Car {
var name: String
var modelRawValue: String?
init(name: String, modelRawValue: String?) {
self.name = name
self.modelRawValue = modelRawValue
}
}
enum CarModel: String, CaseIterable {
case modelA
case modelB
}
func makePredicate(filterModels: [CarModel]?) -> Predicate<Car> {
let filterRawValues = filterModels?.map { $0.rawValue }
let predicate = #Predicate<Car> { car in
if let filterRawValues {
if let carModelRawValue = car.modelRawValue {
filterRawValues.contains(carModelRawValue)
} else {
false
}
} else {
true
}
}
return predicate
}
func fetch(context: ModelContext) throws {
let predicate = makePredicate(filterModels: nil)
let fetchDescriptor = FetchDescriptor(predicate: predicate)
do {
let cars = try context.fetch(fetchDescriptor)
print(cars.count)
} catch {
print("Error: \(error)")
throw error
}
}
Fix
func makePredicate(filterModels: [CarModel]?) -> Predicate<Car> {
// Checking nil condition even before creating the predicate fixes the issue
guard let filterModels else { return .true }
let filterRawValues = filterModels.map { $0.rawValue }
let predicate = #Predicate<Car> { car in
if let carModelRawValue = car.modelRawValue {
filterRawValues.contains(carModelRawValue)
} else {
false
}
}
return predicate
}