I want to write some extensions for some existing classes(like NSPredicate), which allows using the property names(String) to avoid typos and improve the efficiency of code writing. I have tried many ways to no avail. Thx.
Are you perhaps referring to keypaths? Can you post an example code of what youre trying to do?
Yes, but I can't convert a struct KeyPath to String, here is what I want:
struct AStruct {
let age: Int = 10
}
let s = AStruct()
let predicate = NSPredicate(format: "%K == %@", s.properties.age, 10)
I used to consider using Mirror
, but it seems too slow, and I also got some errors when I debug it in the playground.
I've struggled with this as well and haven't found an elegant solution that doesn't introduce potential errors from typos.
Aside from Mirror
, the only other built-in way I know of is to add Codable
conformance, which at compile time will automatically synthesize a private enum inside the struct named CodingKeys
... which is its own conundrum because it's private and you can't easily access its values outside of the struct, such as in your NSPredicate example.
You can always add your own, non-private CodingKeys
enum and statically reference it. You can even name it something like properties
to make it read better.
struct Thing: Codable {
let name: String
let id: Int
enum properties: String, CodingKey {
case name, id
}
}
print(Thing.properties.name.stringValue)
ok, here you converting s.properties.age
to "age"
. please show us what do you do with the predicate later. (i guess at some point there'll be the opposite "age" -> s.age
translation, but you didn't show it.)
Yeah, this is a way to achieve it, but I also need to write a lot of code.
I will use the predicate to query data from Core Data and translate it to the struct.
@azone Iām not sure if this is still maintained but here is a library / code generation tool for creating type safe predicates and queries for Core Data. It reads your model files and outputs structs that mirror the attributes and can be used in where statements which create an NSPredicte behind the scene.
If the intention is to create a predicate for fetching objects of some Core Data subclass then you can use #keyPath
:
class MyMOClass: NSManagedObject {
@NSManaged var age: Int
}
let predicate = NSPredicate(format: "%K == %ld", #keyPath(MyMOClass.age), 10)
However, this does not work with structs, only with properties of classes which are exposed to Objective-C.
If your query string is hardcoded then you should consider using Swift's type system to articulate the query instead. Note that NSPredicate (block:)
is not supported in CoreData as the query is converted to SQL (it should work with SQLite callbacks but hey ho).
Hopefully some time soon, this kind of thing will be built in.
import Foundation
extension NSPredicate {
convenience init<Root, Value>(keyPath: KeyPath<Root, Value>, value: Value) where Value: Equatable {
self.init(block: { object, _ in
guard let object = object as? Root else { return false}
return object[keyPath: keyPath] == value
})
}
}
func ==<Root, Value>(lhs: KeyPath<Root, Value>, rhs: Value) -> NSPredicate where Value: Equatable {
return NSPredicate(keyPath: lhs, value: rhs)
}
struct AStruct {
let age: Int = 10
}
let s = AStruct()
do {
let predicate = NSPredicate(keyPath: \AStruct.age, value: 10)
print(predicate.evaluate(with: s))
}
do {
let predicate = \AStruct.age == 10
print(predicate.evaluate(with: s))
}