Cool Swift Tricks 4: That's an Order

This is the fourth of four posts about random little Swiftisms for you to amaze your friends and confound your enemies. The Swift language has a lot of cool features hidden away in its nooks and crannies, but most real developers are too busy getting real work done to keep up with all of them; so every once in a while I’ve submitted a pull request that made someone say, “Hey, I didn’t know about that!” But this one worked the other way: someone else on the team surprised me.

We had some table views in our app that were jumping around. Well, the table views weren’t jumping, but the cells were. You’d see the table view, you’d tap on a cell to navigate to a detail view controller, you’d do some editing, you’d come back to the table view, and it would be in a different order.

It soon became evident that the sorting of the data populating the table view was not dispositive. To give an obvious case in point, if you’re listing contacts, it isn’t enough to sort them by lastName, because what if two contacts have the same last name? In that case, you’re just rolling the dice; multiple contacts with the same last name can appear in any order, because you haven’t said what to do in that situation and you don’t know anything about the underlying sort algorithm.

The obvious answer is to sort by lastName, then by firstName. That might or might not be right. In our case, it wasn’t; it was better, but it still wasn’t dispositive, because what if two contacts had the same last name and the same first name? If we didn’t want the table view to jump around, ever, we had to keep drilling down until we got to something unique: sort by last name, then by first name, then by unique id.

Having received my marching orders from the rest of the team, I headed off to write the code, and immediately regretted, for what must be the thousandth time, that Swift has no built-in analog to Cocoa’s NSSortDescriptor. This kind of situation comes up all the time: sort by property X, but if they are the same, sort by property Y, but if they are the same, sort by property Z.

And writing out the code to express that rule, by hand, is tedious and error-prone. The safest way is to take a plodding approach, going in reverse order and spelling out every step explicitly. For example, if we have a Person struct with properties lastName, firstName, and guid, we can sort an array of Person instances on those properties in succession:

let orderedPeople = people.sorted {
    if $0.lastName == $1.lastName && $0.firstName == $1.firstName {
        return $0.guid < $1.guid
    if $0.lastName == $1.lastName  {
        return $0.firstName < $1.firstName
    return $0.lastName < $1.lastName

How often have you had to write code like that? It gets real old real fast. What to do?

There have been various discussions about how to encapsulate sorting instructions in an elegant Swifty way, but none of them have yet bubbled up into an accepted Swift language feature. In the meantime, however, it turns out that just in the very special case where all your sort values are Comparable adopters and the sort direction is the same for all of them, there’s a built-in solution: tuple sorting. The preceding code is an instance of that special case, so it can be rewritten like this:

let orderedPeople = people.sorted {
    ($0.lastName, $0.firstName, $0.guid) < ($1.lastName, $1.firstName, $1.guid)

That’s not particularly pretty, but it’s better than what we had before! It works because the < operator is already defined for a tuple of Comparable types in exactly the way we want it defined. The first elements of the tuples are compared, and if they are not equal, the matter is settled. If they are equal, we turn to the second element — and so on, up to whatever the limit is on tuple size (I think it’s six elements).

So in some places in the app — but not all — I was able to adopt this notation. Incredibly, I didn’t know about this trick until someone pointed it out to me during a PR review.

Check out Cool Swift Tricks 1: Mirror, Mirror

Check out Cool Swift Tricks 2: No Escape

Check out Cool Swift Tricks 3: The Garden of Forking Key Paths

You Might Also Like…

Swift 5.5: Replacing GCD With Async/Await

Multithreading! The mere word sends shivers up one’s spine. And if it doesn’t, it should. Main thread and background threads. Code that runs asynchronously. Code that can run simultaneously with other code. Code that can run simultaneously with itself. Code that can share data across threads — possibly with disastrous consequences. Concurrency. Multithreaded code is …

Swift 5.5: Replacing GCD With Async/Await Read More »

    Sign Up

    Get more articles and exclusive content that takes your iOS skills to the next level.