ios – SwiftData runtime crash utilizing Predicate macro with protocol-based generic mannequin

0
1
ios – SwiftData runtime crash utilizing Predicate macro with protocol-based generic mannequin


I am working with SwiftData and attempting to share logic throughout a number of fashions utilizing protocols and protocol extensions.

I’ve created some frequent protocols like Queryable, StatusRepresentable, and Trackable, which my SwiftData fashions (e.g., Pet) conform to.

My mannequin seems to be like this:

@Mannequin
last class Pet {
    var id: UUID
    var title: String
    var statusRaw: String
    // ... different properties
}

And I outline these protocols:

protocol StatusRepresentable: AnyObject, PersistentModel {
    var statusRaw: String { get set }
}

extension StatusRepresentable {
    var standing: Standing {
        get { Standing(rawValue: statusRaw) ?? .energetic }
        set { statusRaw = newValue.rawValue }
    }

    func changeStatus(to newStatus: Standing) {
        if newStatus != standing {
            self.updateTimestamp(onChange: newStatus)
            self.statusRaw = newStatus.rawValue
        }
    }
}

And:

protocol Queryable: AnyObject, Identifiable, StatusRepresentable, PersistentModel {}

extension Queryable {
    static var activePredicate: Predicate {
        .withStatus(.energetic)
    }

    static func predicate(for id: UUID) -> Predicate the place Self.ID == UUID {
        .withId(id)
    }
}

Here is the problematic half:

I’m utilizing a generic predicate extension like this:

extension Predicate {
    static func withStatus(_ standing: Standing...) -> Predicate {
        let rawValues = standing.map { $0.rawValue }
        return #Predicate {
            rawValues.comprises($0.statusRaw)
        }
    }
}

Then in my SwiftUI View, I exploit it like so:

struct ComponentActiveList: View {
    @Question non-public var activePets: [Pet]

    init() {
        self._activePets = Question(
            filter: .activePredicate, // or .withStatus(.energetic)
            type: .title,
            order: .ahead
        )
    }

    var physique: some View {
        // ...
    }
}

The issue:

It compiles high quality, however crashes at runtime with this error (simplified):

keyPath: .statusRaw
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x...)

Within the expanded macro, I can see this:

Basis.Predicate({
    PredicateExpressions.build_contains(
        PredicateExpressions.build_Arg(rawValues),
        PredicateExpressions.build_KeyPath(
            root: PredicateExpressions.build_Arg($0),
            keyPath: .statusRaw
        )
    )
})

It looks like the macro is having hassle resolving .statusRaw through protocol extension / dynamic lookup. I am guessing this has one thing to do with SwiftData + `#Predicate being unable to resolve protocol-constrained properties at runtime?


Earlier than introducing protocols like Queryable and StatusRepresentable, I had this working by duplicating the predicate logic for every mannequin individually – for instance:

extension Predicate {
    static func pets(with standing: Standing...) -> Predicate {
        let rawValues = standing.map { $0.rawValue }
        return #Predicate {
            rawValues.comprises($0.statusRaw)
        }
    }

    static func pet(with id: UUID) -> Predicate {
        #Predicate { $0.id == id }
    }
}

As a workaround, I’ve at the moment reverted all of the protocol code and am duplicating the predicate logic for every mannequin straight. However ideally, I’d wish to outline these in a single place through protocols or generics.

LEAVE A REPLY

Please enter your comment!
Please enter your name here