Skip to content

Instantly share code, notes, and snippets.

@vincent-pradeilles
Created March 4, 2018 18:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vincent-pradeilles/904d5801efd20b7c29eb51688d864abc to your computer and use it in GitHub Desktop.
Save vincent-pradeilles/904d5801efd20b7c29eb51688d864abc to your computer and use it in GitHub Desktop.

Writing a multi-attribute sort function using KeyPaths

Back in the days of Objective-C, there was a class called NSSortDescriptor that would allow to declaratively write a sorting criterion, and would later be fed to a generic sorting function.

This system had the great advantage of easily allowing to perform a multi-attribute sort. The drawback lied in the absence of type safety: the API was stringly typed, as the name of the attributes would be provided as NSString.

The risk of crashing at runtime was thus unavoidable. For this reason, the use of NSSortDescriptor is not very popular in the Swift community, as it clashes with the goal of statically checked types. Moreover, NSSortDescriptor only works with Objective-C compatible types.

Introducing KeyPaths

With Swift 4 a new type called KeyPath came along. It basically acts as a "pointer" to an attribute:

https://gist.github.com/5a0d95577a022ab955c937500c2479f8

To obtain a KeyPath, we need to use the syntax \Type.attribute. As with static members, the name of the type can be ommited when the context allows it to be inferred. It also possible to chain attributes (e.g. \UIView.layer.cornerRadius)

The actual value can then be obtained with a syntax similar to that of a dictionary look-up:

https://gist.github.com/2d2b36129780ad54644321e13826f84f

With this mechanism, it's now possible to take a "pointer" to any attribute of any type, wether it's a class or a struct, and regardless of Objective-C compatibility.

Consequently, it's also possible to leverage it in order to implement a type-safe multi-attribute sorting function

Writing the sorting function

We will declare our sorting method in an extension to Array, and inside we'll delegate all the sorting logic to the standard Array.sorted(by:). This way, our function will only have to translate the KeyPaths it will take as inputs into a set of comparisons.

https://gist.github.com/3b45fb34fa9e2fdd2112d3ac81abcfb7

All the function does it go trough the list of attributes until it finds one where the value are not equal, and use it to perform the comparison.

Now we can generate a set of data to test the function:

https://gist.github.com/bfe9f33b64e15d36517c9cf8cf415f9e

And we can indeed observe that sorting is performed as expected:

https://gist.github.com/5479a24d77e7ba449fcc4ce1d393e929

Going further

For the purpose of this article, I voluntarily kept things simple in order to make them easy to read and understand. Of course, in a real world situation some additions would be required.

For instance, our function only performs sorts in ascending order, which is very limiting. Nevertheless, it would be rather easy to create a new type that provides both a KeyPath and a sorting order, and then write a new version of the sorting function that would use this new data in order to decide wether comparison must be ascending or descending.

This is not hard to implement:

https://gist.github.com/2ebd27b4d32df5ee7ef7608a27ecdebd

And we can now perform more complex sorts, albeit at the price of a slightly more convoluted syntax:

https://gist.github.com/f468d4dd9e540335c0734afe66e7c962

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment