Structural Design Pattern — Adapter in iOS-Swift

Saeid Rezaeisadrabadi
4 min readAug 16, 2022

--

In this story, I’m going to explain Adapter pattern in Swift.
It can be extremely useful and it has basically no drawbacks

There are cases where we have to use a type that doesn’t fit well into the rest of the software system. Situations like this might occur when we use code that we don’t own like for example, third-party libraries or legacy code.
We can’t modify anything in the incompatible interface but we don’t want to refactor our existing code either.

The adapter pattern comes to the rescue.
In this typical implementation, it wraps the object to be adapted and exposes the interface that’s familiar to the caller. The caller uses the adapter, which, in turn, converts the cause that makes sense to the adapted type.
The adapter returns the results to the caller through the compatible protocol.

Swift type extensions make it even easier to implement the adapter pattern.
We don’t need to introduce a wrapper type. Instead, we extend the type and add the required methods and calculated properties to that extension.

One common mistake is trying to adapt an interface that doesn’t provide the expected functionality. So, before you start implementing the adapter, make sure that you’re using the right component.

Let’s start with a common example to show you the benefits of the adapter pattern.

protocol PaymentGateway {
func receivePayment(amount: Double)
var totalPayments: Double {get}
}

I’ve implemented a PaymentGateway protocol. the protocol declares the receivePayment method and a read-only calculated property called totalPayments.
I’ve created two classes to conform to this protocol. The PayPal and Stripe classes adopt the PaymentGateway protocol.

final class PayPal: PaymentGateway {
private var total = 0.0
func receivePayment(amount: Double) {
total += amount
}
var totalPayments: Double {
print("Total payments via PayPal: \(total)")
return total
}
}
final class Stripe: PaymentGateway {
private var total = 0.0
func receivePayment(amount: Double) {
total += amount
}
var totalPayments: Double {
print("Total payments via Stripe: \(total)")
return total
}
}

You could use these types like this.

let paypal = PayPal()
paypal.receivePayment(amount: 100)
let stripe = Stripe()
stripe.receivePayment(amount: 5.99)

Since they conform to the same protocol, they have the same methods.
Now, to calculate the total payments received through all the payment gateways, we can iterate through the list of stripe and PayPal objects.

var total = 0.0var paymentGateways: [PaymentGateway] = [paypal, stripe]for gateway in paymentGateways {
total += gateway.totalPayments
}
print(total)

Now let’s assume that we have to use a third-party framework to process Ko-fi payments. Although the KoFiPayments type provides the functionality we need, its interface is completely different. The KoFiPayments class doesn’t conform to our PaymentGateway protocol. That makes it hard to integrate it with our code.

final class KoFiPayments {
var payments = 0.0
func paid(value: Double, currency: String) {
payments += value
print("Paid \(currency)\(value) via Ko-fi Payments")
}
func fulfilledTransactions() -> Double {
return payments
}
}

We can’t use the KoFiPayments instance in our for-in loop as we did with the PayPal and Stripe objects. We can’t rely on the elegance of polymorphism, since the third-party type doesn’t share a common protocol with our other payment gateway classes.

Integrating the incompatible type in our system would require additional lines of code whenever we try to use it. We can avoid the proliferation of such code by employing the adapter pattern.

So we’re going to implement an extension for the KoFiPayments instance. and conform to the PaymentGateway protocol.

extension KoFiPayments: PaymentGateway { func receivePayment(amount: Double) {
paid(value: amount, currency: "USD")
}
var totalPayments: Double {
let total = payments
print("Total payments received via Ko-fi Payments: \(total)")
return total
}
}

With the type extension, we can even enhance built-in and third-party types whose code is inaccessible to us.

We can now use the new type without issues.

let kofiPayments = KoFiPayments()kofiPayments.receivePayment(amount: 120)paymentGateways.append(kofiPayments)for gateway in paymentGateways {
total += gateway.totalPayments
}
print(total)

This lets us use it together with the PayPal and the Stripe types.

This is an easy and convenient way to implement the adapter. You can find the source code here.

--

--

Saeid Rezaeisadrabadi
Saeid Rezaeisadrabadi

Written by Saeid Rezaeisadrabadi

Over 8 years of experience in iOS software development

No responses yet