Adapter Pattern in Swift for Beginners
“It is not the strongest of the species that survives, nor the most intelligent. It is the one that is most adaptable to change.”
As human beings, we know we have to adapt to different environmental conditions. And we are argued to be the best at it, thus we are on top of the food chain. The same ability is also valid for our code. If we want our app to last longer, it should be able to adapt.
The Adapter Pattern is a behavioral design pattern that allows classes that are incompatible to collaborate. Mostly an old legacy class and a new modern class.
In other sources, it's also defined as structural design pattern as this pattern combines the capability of two independent interfaces.
It usually consists of four parts:
- An object that uses an adapter is the one that conforms to the adapter protocol.
- The adapter protocol is the one that dictates what the new code should use to adapt to the legacy class.
- An adaptor is developed to pass arguments and calls to the legacy object.
- A legacy class that existed prior to the creation of the protocol and cannot be directly changed to adhere to it.
An adapter can be created by extending an existing class or by constructing a new adapter class.
When do we use it?
It’s not always possible to change classes, modules, or functions, especially if they’re from a third-party library.
The most common metaphor to use is an electric adapter. US and EU uses different outlets for their electrical devices. So as someone who lives in US, what do we do when we need to charge our phone when we are visiting EU? We take an adapter with us. So that our foreign charger can communicate with local electrical outlet safely.
A classic use case is a third party login authentication service such as Apple, Google, or Facebook. We definitely do not want to handle three different cases for three different provider. That's why we create an adapter and handle every provider with appropriate adapter class. So that our view controllers can interact with only one interface rather than three.
Another example is an event service in a mobile app. Our app may have a third party event provider. We don't want to rely on solely the functions that they provided. We want to be able to control fail cases. There may be a case where the third party library has changed, and we don't want to be left alone with modifying every single event on our app. That's why we put an adapter between their functions and our view controllers.
Enough talk, let's make an example using Xcode Playground. Create a new Playground project and follow through.
For this example, we are going to adapt a 3rd party authentication service.
ThirdPartyAuthenticator is a third-party class that cannot be modified. Therefore, it is the legacy object. For the simplicities sake, we have provided the token directly and pretended like a network call is done without an error. Expect this process to be much more complex and discrete in the real life libraries.
Ultimately the login function returns a
User and an
Next, add the following code to the end of the playground:
This is the authentication service protocol for our app which acts as the adapter protocol. It requires an email and password. If login succeeds, it calls
success with a
Token. Otherwise, it calls
failure with an
Our app will utilise the protocol instead of
ThirdPartyAuthenticator directly, and it will gain a leverage by doing that. For example, we will be able to support multiple authentication mechanisms – Apple, Facebook and others – simply by having them all conform to the same protocol. This is the foundation of this design pattern. Isn't that clever?
Next, add the following code :
ThirdPartyAuthenticationAdapter as the adapter and conform it to
AuthenticationServiceProtocol. Then we created an instance of
ThirdPartyAuthenticator . It’s private so that it's hidden from other classes.
We added the
AuthenticationServiceProtocol login method as required by the protocol. Inside this method, we call ThirdParty libraries login method to get a
AuthenticatorUser. If there’s an error, we call
failure with it. Otherwise, we create
token from the
authenticatorUser and call
ThirdPartyAuthenticator is now being enclosed inside the adapter, so that the end consumers don’t need to interact with ThirdPartyAuthenticator's API directly. This protects against future changes and possible failures. For example, if the Third Party Authenticator ever change their API and it will broke the app. But with the adapter class we only need to fix it here.
Continue with adding the following code:
For LoginViewController, we first created a new class. It has an authService property as well as e-mail and password text fields. We would generally build the views in viewDidLoad or define each as a @IBOutlet in an actual view controller.
After that, we made a class method that creates a LoginViewController and sets the authService. It takes any type that conforms to the AuthenticatorAdapterProtocol.
Finally, we called created a log in function and used the log in method of the authService, so that we can log in using the e-mail address and password entered in the text fields.
Moment of truth, let's test it out
Write down the code below.
We should see the output below on the console:
We could develop adapters for new APIs, such as Facebook login, and have the LoginViewController use them in the same way without having to change any code.
Be careful when…
You may use the adapter pattern to conform to a new protocol without having to change the underlying type. This protects you against future changes to the underlying type, but it also makes your implementation more difficult to comprehend and maintain. So document your adapters. Add descriptions and comments.
If you’re going to use the adapter pattern, make sure you’re aware of the likelihood of change. If there isn’t, evaluate whether using the objects directly makes more sense.
Congratulations. You've made it to the end.
This is a great pattern to learn and implement, since it's used on almost every app out on the market. This will be a great asset to your coding arsenal.
I hope that the above explanation makes sense and will be helpful to you on your iOS development journey.
Thanks for reading. Have a great one.