The story of my Experiments with Swift KeyPath
Each key 🗝 is destined for single path
KeyPath refers to a property itself instead of property’s value. They‘re useful when you want to perform operations on property, rather on the value behind it.
Let’s take an example:
KeyPath let us refer to the properties without accessing them using .
like this: \type name.path
.
nameKeyPath
syntax in the screenshot is <User, String> where User
is type name and String
is path name.
This struct has an issue now, it won’t let me update nameKeyPath
value as it’s declared let
. It gives below error on assigning value so to update nameKeyPath
value from outside of type need to declare it public.
Let’s make the name
of User
public, on making it public key path becomes WritableKeyPath<User, String>
. Values can be assigned only on WritableKeyPath
Although it helps in meeting criteria but the type is mutated from outside, had to compromise by making name
and user
property var
.
Let’s use private(set)
Now I have 2 problems:
- I don’t have access to
WritableKeyPath
outside Type, so compiler gives error on assigning data using KeyPath - Another is I are mutating property, ‘mutating’ function can only be called on vars
To mitigate this problem, I created a protocol that can be called on any struct to make a new copy with only one property changed.
Modify Struct Property value using KeyPath
Using update
function I can update value of any property.
First KeyPath
is casted toWritableKeyPath
and then update(keyPath:value:)
is called on it.
This solution works perfectly for updating any property value but let’s say I have to update the value of 3 properties that requires us to repeat the code of casting multiple times. So let’s move casting to protocol extension. While moving casting code I realized that instead of KeyPath
I should pass PartialKeyPath
which just accepts root type.
- PartialKeyPath — It’s useful when you need to send keyPath to another function or you want to store keypaths of similar type.
- AnyKeyPath — It’s useful when you want to store key paths of any type into an array.
Finally 🎉 KeyPathEditable protocol can be conformed by any struct to make a new copy with only one property changed
Complete source code can be found here
Modify class property using KeyPath
We can’t use existing KeyPathEditable
protocol here as that creates a copy on modifying property value. Let’s create a new protocol to modify a property value only
If you notice apart from copy
there is one more difference b/w this protocol and previous protocol. With class
type we can’t specify Self
type in protocol, it gives error:
Error: protocol can only be used as a generic constraint because it has Self or associated type requirements
To fix this error I used associatedtype Root
and passed type in function argument
Protocol can bee consumed like this:
Finally 🎉 ReferenceKeyPathEditable protocol can be conformed by any class to modify property value while keeping property setter private for other types.
Complete source code can be found here
KeyPath with Functions:
- map()
KeyPath with functions can be used when you have a requirement to work with a single property. I mostly like this for high level functions.
It helps while reading as well. In a map with closure, our intention is to map user by name property but we specify by value. Although it works but keyPath fits better here as by keyPath we say to map by given path.
- filter() — keypath can be used with a filter as well
- sort()
Swift standard library doesn’t provide a function to sort by keypath but we can add utility function in extension:
Complete source code can be find here
P.S: It was fun playing with it
For more insights, this article is worth to read