At iOS App Templates, we are building fully coded mobile templates written in Swift, to help developers and entrepreneurs create their minimum viable products more quickly. Part of our mission is to provide highly modularized source code so that developers can easily customize and adapt our code to fit their needs. In this article, we are going to talk about the adapter pattern, which we widely use in our Xcode projects.
The Adapter Pattern in Swift
In software engineering, the adapter pattern is a software design pattern (also known as a wrapper, an alternative naming shared with the decorator pattern) that allows the interface of an existing class to be used as a different interface. It is often used to make existing classes work with each other, without modifying their source code. Check out Wikipedia for more information on the technical definition of the adapter pattern.
The adapter pattern allows two objects to understand each other's APIs and exchange messages between themselves. Implementing Cocoa DataSource protocol into ViewController is the simplest example of using the adapter pattern. The specialized object that implements the data source protocol is an adapter for UITableView/UICollectionView. Similarly, for implementing the UITableViewDelegate, our view controller (in most cases) is adapting pieces of information about interactions between the user and UI part of the table view.
In our Swift projects, we create a specialized adapter for each (model, cell) pair. In this way, we can fetch any model from any data source (disk, network, mock data, etc) and we can display it in any UI cell. In this way, we get to use only ONE single generic view controller for ALL the screens. Awesome, right?
Implementation Example
We are going to present a concise example of the adapter pattern. Let's assume that we would like to build an app to manage tasks from our tasks manager tool. We have an API structure that looks like this:
struct APITask: Decodable {
let id: String
let createdAt: TimeInterval
let createdBy: String
let developedBy: String
let finishedAt: TimeInterval?
}
Within the app, we want to not rely on the API models, so we need to adopt this object using a protocol:
protocol Task {
var id: String { get }
var createdAtDate: Date { get }
var author: String { get }
var finishedAtDate: Date? { get }
}
And last, but not least we need to adapt APITask to conform to Task protocol:
extension APITask: Task {
var author: String { return createdBy }
var createdAtDate: Date { return Date(timeIntervalSince1970: createdAt) }
var finishedAtDate: Date? { return finishedAt.map { Date(timeIntervalSince1970: $0) } }
}
In this example, the APITask extension adapts the APITask concrete object to the generic Task protocol. Now, any class that deals with a generic object conforming to Task (for instance, a view), can deal with an APITask as well, without even realizing it does so. The main benefit here is that we completely decoupled that class (e.g. a view controller) from the model layer (APITask).
The adapter pattern can be implemented in different flavors, so it can definitely be applied via concrete objects as well, rather than protocols. We'll leave that as an exercise to the reader.
We hope you find the adapter pattern in Swift useful. Apple is using it extensively in Cocoa and once you get familiar with it, it will help you come with a more robust architecture approach in iOS app development. The best part is that this adapter pattern can be used in any programming language since it is not Swift specific. Yes, it even works in modern languages, such as React Native.
Don't forget to spread the word by sharing this tutorial. Thank you!
This article was originally published in Mobile App Templates.
Top comments (0)