I'm building a generic Realm Swift query function. I started by getting it to work with everything declared explicitly. Now I'm moving parameters into generic types.
This is my function and it works like this:
func queryFromCollection<T: Object>(of type: T.Type, query set: Set<UUID>, for key: String) async -> Results<T>? {
let objects = await RealmReadService.instance.readObjects(of: type)
var predicates: [NSPredicate] = []
for item in set {
let query = NSPredicate(format: "%K == %@", key, item as CVarArg)
predicates.append(query)
}
let compoundPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: predicates)
return objects!.filter(compoundPredicate)
}
The Set
of UUID
s converts to CVarArg
just fine.
Now, when I use this code to make my Set
generic:
func queryFromCollection<T: Object, S>(of type: T.Type, query set: Set<S>, for key: String) async -> Results<T>?
and in my loop I have to change item as! CVarArg
. This compiles and runs until I do the query.
Once I run the query it fails and I get this: "Could not cast value of type 'Foundation.UUID' (0x1df22b1e0) to 'Swift.CVarArg' (0x13b14e7b8)"
I'm struggling to understand why it can convert UUID
s to CVarArg
when the Set
is declared as a Set<UUID>
but not with a generic Set
.
CodePudding user response:
I think this is because UUID
is bridged to NSUUID
in the case of Set<UUID>
. NSUUID
inherits NSObject
, which does implement CVarArg
.
However, in the generic case, this bridging does not happen, because it is not necessarily the case that the generic type can be bridged.
Other than making your function only take CVarArg
s,
func queryFromCollection<T: Object, S: NSObject>(of type: T.Type, query set: Set<S>, for key: String) async -> Results<T>?
which would require the caller to do a lot of explicit conversion to NSUUID
, I can only think of one other rather ugly way - make a CVarArgConvertible
protocol
protocol CVarArgConvertible {
func toCVarArg() -> CVarArg
}
extension UUID: CVarArgConvertible {
func toCVarArg() -> CVarArg {
self as NSUUID
}
}
// other bridgable types you want to use...
Then queryFromCollection
would look like:
func queryFromCollection<T: Object, S: CVarArgConvertible>(of type: T.Type, query set: Set<S>, for key: String) async -> Results<T>? {
// in here you would call "toCVarArg" on an element of the set to get a CVarArg.
}
Or, as Larme reminded me in the comments, you can use the other overload of NSPredicate
, that doesn't take CVarArg
s, but an [Any]?
instead:
let query = NSPredicate(format: "%K == %@", argumentArray: [key, item])