Keypath in Swift

abdul ahad
4 min readSep 2, 2024

--

Photo by Everyday basics on Unsplash

What is a Key Path?

In Swift, a key path is like a reference to a property, but it’s not directly invoked. Instead, it’s a way to describe how to access a property or a series of properties on an object. Key paths are useful for writing code that can work with properties indirectly, such as passing around property accessors.

Key paths are especially powerful because they can be passed around as values, tested for equality, and used in various ways that regular property access can’t be. They are type-safe, meaning the compiler ensures that they are correct and won’t lead to runtime errors.

Basic Syntax

  • A key path starts with a backslash (\), followed by the type and property name, like this: \Person.name.
  • You can omit the type if the context makes it clear, such as in \.name.

Example Scenario: Using Key Paths in a Shopping Cart

Let’s say we have a Product struct that represents items in a shopping cart. Each product has a name, a price, and an optional discount value. We'll create a CartItem struct to represent an item in the cart, which includes a Product and a quantity.

Here’s how key paths could be used:

struct Product {
var name: String
var price: Double
var discount: Double?
}

struct CartItem {
var product: Product
var quantity: Int
}

let apple = Product(name: "Apple", price: 1.0, discount: nil)
let bread = Product(name: "Bread", price: 2.5, discount: 0.5)

var cartItems = [
CartItem(product: apple, quantity: 3),
CartItem(product: bread, quantity: 2)
]

Accessing Properties with Key Paths

Let’s say you want to get the name of the product in the first cart item. You can do this with key paths:

let nameKeyPath = \CartItem.product.name

let firstItemName = cartItems[0][keyPath: nameKeyPath]
print(firstItemName) // Output: Apple

In this example, nameKeyPath is a key path that starts at a CartItem, goes to its product, and then to the product’s name. The [keyPath:] subscript allows us to "invoke" the key path on an instance to get the value.

Modifying Properties with Writable Key Paths

Key paths can also be used to modify properties if the key path is writable. CartItems needs to be a var and not a let . For instance, if you want to apply a discount to all products in the cart, you can use a writable key path:

let discountKeyPath = \CartItem.product.discount

cartItems[0][keyPath: discountKeyPath] = 0.1
print(cartItems[0].product.discount ?? "No discount") // Output: 0.1

In this case, the key path discountKeyPath allows us to update the discount property of the first item’s product.

Using Key Paths in Functions

Using key paths as parameters in functions is a powerful feature in Swift that allows you to pass references to properties without directly accessing them. This lets you write generic and reusable functions that can operate on various properties of an object or struct

  • KeyPath<Type, Property>: Refers to a property that can be read.
  • WritableKeyPath<Type, Property>: Refers to a property that can be both read and written.

Example Scenario: Working with a Person Struct

Let’s say we have a Person struct with several properties:

struct Person {
var name: String
var age: Int
var city: String
}

let person1 = Person(name: "Alice", age: 30, city: "New York")
let person2 = Person(name: "Bob", age: 25, city: "San Francisco")
let person3 = Person(name: "Charlie", age: 35, city: "Los Angeles")

let people = [person1, person2, person3]

1. Accessing Properties with Key Paths

Suppose we want to create a function that prints a specific property of each Person in an array. Instead of writing separate functions for each property, we can create a generic function using key paths.

func printProperty<T>(of people: [Person], keyPath: KeyPath<Person, T>) {
for person in people {
print(person[keyPath: keyPath])
}
}

// Example: Print names of all people
printProperty(of: people, keyPath: \.name)
// Output:
// Alice
// Bob
// Charlie

// Example: Print ages of all people
printProperty(of: people, keyPath: \.age)
// Output:
// 30
// 25
// 35

2. Sorting with Key Paths

You can also sort an array of Person objects based on a specific property by passing a key path as a parameter.

func sortPeople<T: Comparable>(by keyPath: KeyPath<Person, T>, in people: [Person]) -> [Person] {
return people.sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
}

// Example: Sort people by age
let sortedByAge = sortPeople(by: \.age, in: people)
print(sortedByAge.map { $0.name })
// Output:
// ["Bob", "Alice", "Charlie"]

// Example: Sort people by name
let sortedByName = sortPeople(by: \.name, in: people)
print(sortedByName.map { $0.name })
// Output:
// ["Alice", "Bob", "Charlie"]

3. Filtering with Key Paths

You can filter an array based on a property by passing a key path as a parameter.

func filterPeople<T>(by keyPath: KeyPath<Person, T>, equalTo value: T, in people: [Person]) -> [Person] where T: Equatable {
return people.filter { $0[keyPath: keyPath] == value }
}

// Example: Filter people by city
let peopleInNY = filterPeople(by: \.city, equalTo: "New York", in: people)
print(peopleInNY.map { $0.name })
// Output:
// ["Alice"]

4. Updating Properties with Writable Key Paths

If you want to update a property of each object in an array, you can use a writable key path (WritableKeyPath). This allows the function to not only read but also modify the property.

func updateProperty<T>(of people: inout [Person], keyPath: WritableKeyPath<Person, T>, to newValue: T) {
for i in 0..<people.count {
people[i][keyPath: keyPath] = newValue
}
}

// Example: Update city of all people
var mutablePeople = people
updateProperty(of: &mutablePeople, keyPath: \.city, to: "Unknown")
print(mutablePeople.map { $0.city })
// Output:
// ["Unknown", "Unknown", "Unknown"]

Summary

  • Key Paths as Parameters: You can pass key paths as parameters to access, modify, filter, and sort properties generically.
  • Accessing Properties: Use KeyPath to read properties dynamically.
  • Modifying Properties: Use WritableKeyPath when you need to modify a property.
  • Filtering and Sorting: Key paths can be used to create flexible functions for filtering and sorting collections.

Using key paths in functions provides a clean, reusable, and generic way to operate on properties, reducing boilerplate code and increasing flexibility.

--

--

abdul ahad
abdul ahad

Written by abdul ahad

A software developer dreaming to reach the top and also passionate about sports and language learning

No responses yet