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.