Snippets

Alex Lynch Create NSPredicate from KeyPath

Updated by Alex Lynch

File XXXX-create-nspredicate-from-keypath.md Modified

  • Ignore whitespace
  • Hide word diff
 # Create NSPredicate from KeyPath
 
-* Proposal: [SE-00XX](https://bitbucket.org/snippets/lynchrb/r487zn)
+* Proposal: [SE-00XX](https://bitbucket.org/snippets/lynchrb/oe8qzk)
 * Authors: [Alex Lynch](https://bitbucket.org/iam_apps/), 
 * Review Manager: TBD
 * Status: **Awaiting implementation**
Created by Alex Lynch

File XXXX-create-nspredicate-from-keypath.md Added

  • Ignore whitespace
  • Hide word diff
+# Create NSPredicate from KeyPath
+
+* Proposal: [SE-00XX](https://bitbucket.org/snippets/lynchrb/r487zn)
+* Authors: [Alex Lynch](https://bitbucket.org/iam_apps/), 
+* Review Manager: TBD
+* Status: **Awaiting implementation**
+
+## Introduction
+
+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)
+
+## Motivation
+
+NSPredicate string literals are a notorious source of faulty refactors. Consider the following model:
+
+    class Person {
+        var name: String
+        var age: Int
+        var friends: Set<Person>
+    }
+
+Common usage of NSPredicate might look like this:
+
+    let findByName = NSPredicate(format: "name == %@", argumentArray: ["John Smith"])
+
+However if the model were later refactored for greater clarity such that `name` becomes `fullName`, then the predicate will fail at runtime.
+
+Other examples of potential runtime-only errors include type mismatches and simple typos:
+
+    let findByAge = NSPredicate(format: "age == %@", argumentArray: ["John Smith"]) // type mismatch
+    let findByFriends = NSPredicate(format: "frnds < 2", argumentArray: nil) // property name misspelled
+
+## Proposed solution
+
+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. 
+
+The following is the proposed enhancement:
+
+    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 findByName: NSPredicate = \Person.name == "John Smith"
+    let findByAge: NSPredicate = \Person.age >= 30
+    let findByFriends: NSPredicate = \Person.friends < 2
+
+The new syntax can be combined with the usual logical operators: 
+
+    let findPotentialDinnerGuests: NSPredicate = \Person.age => 25 && \.friends < 2 && \.name != "John Smith" 
+    // don't invite John to the party
+
+And of course the syntax could be very happily married to CoreData:
+
+    let peopleToInvite = Person.find(in: myMOC, where: \.age => 25 && \.friends < 2 && \.name != "John Smith" )
+    // This search is resilant against name refactors, type mismatches and typos.
+
+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.)
+
+## Detailed design
+
+See section "Alternatives considered" for a discussion comparing `NSPredicate`'s `%K` format to `NSPredicate(block:)` constructor.
+
+## Source compatibility
+
+The proposed change has no effect on source compatibility.
+
+## Effect on ABI stability
+
+The proposed change should have no effect on ABI stability.
+
+## Effect on API resilience
+
+The proposed feature has no effect on public API. 
+
+## Alternatives considered
+
+Two alternative designs were considered.
+
+### Alternative 1: KeyPath._kvcKeyPathString
+
+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.
+
+### Alternative 2: NSPredicate(block:)
+
+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. 
HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.