How to write safer code using the Lock ๐ and Key ๐ pattern
Youโre more of a video kind of person? Iโve got you covered! Hereโs a video with the same content than this article ๐ฟ
Do you see whatโs the issue with this code?
Here, we have an actor that implements a network client.
We have a first method called authenticate(), which allows us to get an authentication token.
And then we have another method called fetchNewsFeed(), which uses the authentication token to retrieve some actual data.
This code will run perfectly fine, however thereโs one annoying issue: the code assumes that the method authenticate() will always be called before the method fetchNewsFeed().
However, the compiler has no way to enforce this rule!
This is a problem because not only does it open up the way to potential bugs, but it also forces every call site of the method fetchNewsFeed() to deal with a potential error case!
So how could we improve?
We would like to have a mechanism that makes it impossible to call the method fetchNewsFeed() as long as the property authToken is still set to nil.
This mechanism can be implemented by using something called the Lock ๐ and Key ๐ pattern.
Hereโs how it works.
First, we split our NetworkClient into two parts:
On one side an UnauthenticatedNetworkClient, and on the other an AuthenticatedNetworkClient.
Then, in the AuthenticatedNetworkClient, we make the authToken non-optional.
This means that in order to initialize an AuthenticatedNetworkClient, it will now be mandatory to provide an authToken:
Finally, we also update the UnauthenticatedNetworkClient, so that the method authenticate() now returns an AuthenticatedNetworkClient:
Thanks to these changes, we now have the compile time guarantee that when a method is called on an AuthenticatedNetworkClient, an authToken will always be available ๐
The initializer of the AuthenticatedNetworkClient has become a Lock ๐ around its methods, and the token has become the Key ๐ which opens that Lock ๐.
Hence the name of the Lock ๐ and Key ๐ pattern!
Thatโs all for this article!
I hope youโve enjoyed discovering this new pattern to make your code safer.
Hereโs the code if you want to experiment with it:
// Before
actor NetworkClient {
var authToken: String?
func authenticate(
login: String,
password: String
) async {
// ...
self.authToken = authToken
}
func fetchNewsFeed() async throws -> NewsFeed {
guard let authToken else {
throw NetworkError.noToken
}
// ...
}
}
// After
actor UnauthenticatedNetworkClient {
func authenticate(
login: String,
password: String
) async -> AuthenticatedNetworkClient {
// ...
return AuthenticatedNetworkClient(authToken: authToken)
}
}
actor AuthenticatedNetworkClient {
var authToken: String
init(authToken: String) {
self.authToken = authToken
}
func fetchNewsFeed() async throws -> NewsFeed {
// ...
}
}