Swift Data Predicate Evaluation Crashes in Release Build When Generics Used

I'm using Swift Data for an app that requires iOS 18.

All of my models conform to a protocol that guarantees they have a 'serverID' String variable.

I wrote a function that would allow me to pass in a serverID String and have it fetch the model object that matched. Because I am lazy and don't like writing the same functions over and over, I used a Self reference so that all of my conforming models get this static function.

Imagine my model is called "WhatsNew". Here's some code defining the protocol and the fetching function.

protocol RemotelyFetchable: PersistentModel {
    var serverID: String { get }
}

extension WhatsNew: RemotelyFetchable {}

extension RemotelyFetchable {
    static func fetchOne(withServerID identifier: String, inContext modelContext: ModelContext) -> Self? {
        var fetchDescriptor = FetchDescriptor<Self>()
        fetchDescriptor.predicate = #Predicate<Self> { $0.serverID == identifier }

        do {
            let allModels = try modelContext.fetch(fetchDescriptor)
            
            return allModels.first
        } catch {
            return nil
        }
    }
}

Worked great! Or so I thought...

I built this and happily ran a debug build in the Simulator and on devices for months while developing the initial version but when I went to go do a release build for TestFlight, that build reliably crashed on every device with a message like this:

SwiftData/DataUtilities.swift:65: Fatal error: Couldn't find \WhatsNew. on WhatsNew with fields [SwiftData.Schema.PropertyMetadata(name: "serverID", keypath: \WhatsNew., defaultValue: nil, metadata: Optional(Attribute - name: , options: [unique], valueType: Any, defaultValue: nil, hashModifier: nil)), SwiftData.Schema.PropertyMetadata(name: "title", keypath: \WhatsNew., defaultValue: nil, metadata: nil), SwiftData.Schema.PropertyMetadata(name: "bulletPoints", keypath: \WhatsNew.)>, defaultValue: nil, metadata: nil), SwiftData.Schema.PropertyMetadata(name: "dateDescription", keypath: \WhatsNew., defaultValue: nil, metadata: nil), SwiftData.Schema.PropertyMetadata(name: "readAt", keypath: \WhatsNew.)>, defaultValue: nil, metadata: nil)]

It seems (cannot confirm) that something in the release build optimization process is stripping out some metadata / something about these models that makes this predicate crash.

Tested on iOS 18.0 and 18.1 beta.

How can I resolve this? I have two dozen types that conform to this protocol. I could manually specialize this function for every type myself but... ugh.

Answered by DTS Engineer in 807995022

I believe the issue is related to a bug that the Swift compiler doesn’t correctly handle the dynamic Self when it is used with a key path. I’d suggest that you file a feedback report (http://developer.apple.com/bug-reporting/) to voice that you are impacted, which may help the Swift folks to prioritize the work – If you do so, please share your report ID here.

In your case, you might work around the issue by avoiding using Self with a key path, as shown below:

  1. Change fetchOne to accept a predicate:
static func fetchOne(witPredicate: Predicate<Self>, inContext modelContext: ModelContext) -> Self? {
    var fetchDescriptor = FetchDescriptor<Self>()
    fetchDescriptor.predicate = witPredicate

    do {
        let allModels = try modelContext.fetch(fetchDescriptor)
        
        return allModels.first
    } catch {
        return nil
    }
}
  1. Use fetchOne in the following way:
 let identifier = whatsNew.serverID
 let predicate = #Predicate<WhatsNew> {
     $0.serverID == identifier
 }

 if let existingWhatsNew = WhatsNew.fetchOne(witPredicate: predicate, inContext: modelContext) {
     whatsNew.readAt = existingWhatsNew.readAt
 }

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Accepted Answer

I believe the issue is related to a bug that the Swift compiler doesn’t correctly handle the dynamic Self when it is used with a key path. I’d suggest that you file a feedback report (http://developer.apple.com/bug-reporting/) to voice that you are impacted, which may help the Swift folks to prioritize the work – If you do so, please share your report ID here.

In your case, you might work around the issue by avoiding using Self with a key path, as shown below:

  1. Change fetchOne to accept a predicate:
static func fetchOne(witPredicate: Predicate<Self>, inContext modelContext: ModelContext) -> Self? {
    var fetchDescriptor = FetchDescriptor<Self>()
    fetchDescriptor.predicate = witPredicate

    do {
        let allModels = try modelContext.fetch(fetchDescriptor)
        
        return allModels.first
    } catch {
        return nil
    }
}
  1. Use fetchOne in the following way:
 let identifier = whatsNew.serverID
 let predicate = #Predicate<WhatsNew> {
     $0.serverID == identifier
 }

 if let existingWhatsNew = WhatsNew.fetchOne(witPredicate: predicate, inContext: modelContext) {
     whatsNew.readAt = existingWhatsNew.readAt
 }

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Thanks.

This has been filed as FB15340069.

Swift Data Predicate Evaluation Crashes in Release Build When Generics Used
 
 
Q