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 {
// ...
}
}