Understanding Proxy Pattern in Swift within 5 minutes
Implementing Proxy Pattern in Swift
The proxy pattern is one of the few underrated patterns that nobody really talks about in iOS. A proxy pattern is one of the structural design patterns that lets you provide a substitute or placeholder for another object. A proxy has total control and access to the original object, and let the proxy object do some magical stuff with the original object and restrict the access to it, however, the one who uses a proxy object, won’t have any knowledge behind the scene.
Proxy pattern normally being used outside of mobile development, when some request wants to map the request to a specific object without exposing underlying knowledge about that object, so it can be interchangeable with any object that conforms to the original object. However, proxy patterns can be also used in mobile development.
The benefit of the proxy, it can be disguised as a real object, because the interface is similar to the real client object, however, we don’t interrupt the original object that will do the process under the hood, which means we can interpolate or dislodge something in the proxy without we harm the original object. Proxy is having similar behavior as a gateway to the original process of the operation.
USE CASE
Let’s take real use case here. A repository object wants to call a network request to an API, however since the company has moved forward to the new system, they want to migrate gradually from the old REST API to the GraphQL endpoint. So in order for the repository to still able to work with the existing interface without breaking change, we will make a Proxy object that has a similar interface as the original object does
1protocol Requestable {
2 typealias Params
3 func fetchData(with params: Params)
4 func updateData(with params: Params)
5}
6
7final class Proxy: Requestable {
8 typealias Params = [String: Any]
9
10 // 1. Your service must be private
11 // TIPS: You can use enum and factory to create service base on the type
12 private lazy var service: Requestable = LegacyRestService() // or GraphQLService
13
14 func fetchData(with params: Params) {
15 // do something with your parameters or other object
16 // if you wish before making real request
17
18 service.fetchData(with: params)
19 }
20
21 func updateData(with params: Params) {
22 service.updateData(with: params)
23 }
24
25}
So the code is quite simple, define your interface of the service. Then make your real implementation class of the service for both classes that serving the old system with REST API and the new system with GraphQL. This how the caller looks like. Pretty simple, instantiate your proxy class and just call the proxy method.
1final class ProxyImplementation {
2 private lazy var proxy: Proxy = Proxy()
3 func refreshPage() {
4 proxy.fetchData(with: ["page": 1])
5 }
6
7 func updatePage(status: Bool) {
8 proxy.updateData(with: ["update": status])
9 }
10}
There is some confusion related to the difference between an adapter and a proxy pattern. This stack overflow answer has a very good answer about that. Basically Proxy is only a surrogate and it has a similar interface with the original object, however the opposite with the adapter. The adapter pattern’s intention is to help the accessor able to work with the old system that can’t work together with the new implementation, hence the adapter object and the adaptee object has a different interface.