Using KeyPaths as functions in Swift 5.2

Published on: February 26, 2020

One of Swift 5.2's new features is the ability to use KeyPaths as functions. This can be extremely useful in cases where you'd only return the value of a certain KeyPath in a closure. Let's look at a pre-Swift 5.2 example where this is the case:

// Swift 5.1 and earlier
struct User {
  let id: UUID
  let name: String
  let age: Int
}

func extractUserIds(from users: [User]) -> [UUID] {
  return users.map { $0.id }
}

This code should look familiar to you. It's likely that you've written or seen something like this before. This code transforms an array of User objects into an array of UUID objects using map(_:) by returning $0.id for every user in the array.

In Swift 5.2, it's possible to achieve the same with the following code;

func extractUserIds(from users: [User]) -> [UUID] {
  return users.map(\.id)
}

This is much cleaner, and just as clear. We want to map over the user array with the \.id KeyPath which means that we'll end up with an array of UUID. Just like we did before.

You can use this Swift feature in all places where a single-argument closure is used, and where the KeyPath's return type matches that of the closure. We could write the following code to filter an array of users and extract only users that are 21 and up:

extension User {
  var isTwentyOneOrOlder: Bool {
    return age >= 21
  }
}

func extractUsers(_ users: [User]) -> [User] {
  return users.filter(\.isTwentyOneOrOlder)
}

Swift's filter(_:) takes a closure that accepts a single argument (in this case User), and the closure must return a Bool. So by adding a computed property to User that is a Bool, we can use its KeyPath to filter the array of users. Pretty neat, right?

Tip:
Other Swift 5.2 features that I wrote about are callAsFunction and the ability to pass default values to subscript arguments.

Categories

Quick Tip

Subscribe to my newsletter