Updated by
Modified
XXXX-create-nspredicate-from-keypath.md- Ignore whitespace
+This proposal aims to improve the type safety and integrity of NSPredicates by founding them in KeyPaths instead of Strings.
+Swift-evolution thread: [[Pitch] KeyPath -> NSPredicate, securely](https://forums.swift.org/t/pitch-keypath-nspredicate-securely/10640)
+NSPredicate string literals are a notorious source of faulty refactors. Consider the following model:
+However if the model were later refactored for greater clarity such that `name` becomes `fullName`, then the predicate will fail at runtime.
+ let findByFriends = NSPredicate(format: "frnds < 2", argumentArray: nil) // property name misspelled
+This proposal suggests enhancing NSPredicate to accept swift's KeyPath type in its `%K` format. NSPredicate currently accepts only strings. The following is currently possible:
+ let findByName = NSPredicate(format: "%K == %@", argumentArray: [#keyPath(Person.name), "John Smith"]) // works
+The above example is safe against typos and refactors of property _names_ but is not resilient against type mismatch errors.
+ let findByName = NSPredicate(format: "%K == %@", argumentArray: [\Person.name, "John Smith"]) // currently does not work
+The difference between these two forms is subtle but important. In the second, proposed form the key path is supplied as a `KeyPath` value not as a `#keyPath` literal. This difference allows for the creation of an entirely new syntax for predicate description:
+ let findPotentialDinnerGuests: NSPredicate = \Person.age => 25 && \.friends < 2 && \.name != "John Smith"
+ let peopleToInvite = Person.find(in: myMOC, where: \.age => 25 && \.friends < 2 && \.name != "John Smith" )
+An example of such this kind of KeyPath manipulation can be [found here](https://github.com/kishikawakatsumi/Kuery). Credit Kishikawa Katsumi.
+(N.B. This proposal does not include the design of such a keypath library, only the necessary extension to `NSPredicate`'s `%K` format.)
+See section "Alternatives considered" for a discussion comparing `NSPredicate`'s `%K` format to `NSPredicate(block:)` constructor.
+KeyPath privately declares `_kvcKeyPathString`, which ostensibly is the exact value needed to pass to a `%K` format. Publicizing this value (with a sensible name) was considered as an alternative, however this approach might have security/stability implications and was therefor rejected. Passing a `KeyPath` directly to `NSPredicate` keeps the internals of the matter private.
+NSPredicate provides a constructor that uses a supplied block to evaluate the subject. This capability is _semantically_ sufficient to achieve the kind of typesafe KeyPath-to-NSPredicate library that is described in the "Proposed solution" section. **However** the block constructor for NSPredicate is not supported by CoreData because it would force the reification of every candidate object in the store, which is untenably slow. The proposed design allows for CoreData to perform its usual conversion of NSPredicates to SQL queries.
You can clone a snippet to your computer for local editing. Learn more.