Hi,
Overview
I am finding #Predicate to 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
}
3
0
470