top of page

Mastering Swift's Development Blog

Follow Us On Twitter
  • Writer's pictureJon Hoffman

Proxy Pattern: Protocol Oriented Design Patterns

Updated: Sep 4, 2022


The proxy pattern is a structural design pattern. It is a very underrated design pattern and is probably used in some form or another in most major mobile and enterprise applications. The proxy pattern shows how we could use a substitute or placeholder to access another entity or service. The proxy pattern can be used to restrict access, hide the complexity or perform other functions prior to or after calling an entity or service.


I used this pattern a lot when calling an external service. Usually, the proxy takes in some information that allows it to make the correct service call, then after the call to the external service it unwraps the data and finally returns it in a format that can be used within the application. In this case, the biggest benefit that you get from the proxy pattern is it hides the underlying complexity of making the service call and centralizes it in one location. Then if anything ever changes with the service call, either how it is called or we need to switch to a new service, we can very easily make the change in one location.


The title of this article is subtitled Protocol Oriented Design Patterns because I am planning on writing several posts showing how to implement classical design patterns using protocol-oriented techniques. I will use a protocol in our simplified example for this post, mainly because it is easier to explain what is going on by starting with the protocol, however when we look at the real-world example you will notice that a protocol is not used because it is not necessary however we are using value types (structs and enums) as Apple has recommended over reference types (classes) which is what you see in most Swift design patterns on the internet. Whenever possible we are giving preference to value types in our design patterns posts.


Problem We Are Trying To Solve

In our application we need to access an external API or service. A proxy object can be used to control access to this API or service and also hide the complexity around it.


Our Solution

The solution that we will be presenting is to create a struct with a single static function to load data from an external service. When we pull the code to access the API or service into its own type, it enables us to easily use the API or service throughout our code without duplicate a lot of the code. If the API or service ever changes it also makes it easier to change the code in one spot rather than throughout our application.


The Example

In this simplified example we will be creating a type that will be used as a service to retrieve data from an external service. The following illustration shows how the proxy will work, where various parts of our code, which needs to retrieve information from the external service, will call our proxy service. The proxy service will then query the external service and return the information requested



Now let's create our example, we will begin by defining the protocol for the service.


protocol Service {
    static func load(_ call: ServiceQuery, parameters: String) -> [ReturnType]
}

The Service protocol defines one static function that will be used to retrieve the data from the external service. The function takes two parameters, one of the ServiceQuery type and the other of the String type. The return type for this function is an Array of the ReturnType types. Lets look at the ServiceQuery type followed by the ReturnType type.


enum ServiceQuery: String {
    case serviceCall1 = "query 1"
    case serviceCall2 = "query 2"
}

The ServiceQuery type is an enum whose associated values are the queries or URLs that the service uses to know what data to retrieve. If you are curious on why we use an enum like this, I wrote a post about why we should replace static constants with enums which you can read here. Now lets look at the ReturnTypetype.


struct ReturnType {
    var element1: String
    var element2: String
}

The ReturnType type is simply a data storage type that represents the data we are retrieving and will contain the appropriate elements to represent the data. Now lets look at the concrete type that will adopt the Serviceprotocol.


struct TestService: Service {
    public static func load(_ call: ServiceQuery, parameters: String) -> [ReturnType] {
        var returnElements = [ReturnType]()
        
        // implementation of the service
        
        return returnElements
    }
}

In this example, we do not care how the TestService type retrieves the data or how it unwraps it. All we are worried about is getting the data back imn a format that we can use. The biggest advantage for this pattern is if we originally used an internal database to store the data, and wanted to switch to an external service, all we would need to do is change the associated values in the ServiceQuery enum and then the implementation within the TestService type.


Now lets look at how we used this pattern within the Mastering Swift Open-Source Cocktails app. You can read about the Cocktail app here with links to the source code and where you can get it from the App Store. To begin with we have the enumeration that contains the various URLs for the calls to the CocktailsDB. This is the CocktailServiceCall enum


enum CocktailServiceCall: String {
    case random = "https://www.thecocktaildb.com/api/json/$key/random.php"
    case byletter = "https://www.thecocktaildb.com/api/json/$key/search.php?f="
    case byingredint = "https://www.thecocktaildb.com/api/json/$key/filter.php?i="
    case byid = "https://www.thecocktaildb.com/api/json/$key/lookup.php?i="
    case tenrandom = "https://www.thecocktaildb.com/api/json/$key/randomselection.php"
    case popular = "https://www.thecocktaildb.com/api/json/$key/popular.php"
}

Now let’s look at the data model types that we use to store information about the various cocktails. Notice that we adopt the decodable protocols for both of these types so we can use the JSONDecoder to populate these types.


struct Cocktails: Decodable {
    let drinks: [Cocktail]
}

struct Cocktail: Decodable, Identifiable, Hashable {
    var id: String { idDrink }
    let idDrink: String
    ...
}

These are the data types that will be used to store the information about the cocktails returned from the CocktailsDB. Now let’s look at the CocktailService type that uses the URLs in the CocktailServiceCall enum to retrieve a list of cocktails and then returns the data back to the calling function.


struct CocktailService {
    static func load(_ call: CocktailServiceCall, parameter: String) async -> Cocktails? {
        let parameter = parameter.replacingOccurrences(of: " ", with: "%20")
        let urlString = call.rawValue.appending(parameter).replacingOccurrences(of: "$key", with: ApiKey.apiKey.rawValue)
        guard let url = URL(string: urlString) else {
            return nil
        }
        let urlSession = URLSession.shared
        
        do {
            let (data, _) = try await urlSession.data(from: url)
            let cocktails: Cocktails = try JSONDecoder().decode(Cocktails.self, from: data)
            return cocktails
        }
        catch {
            debugPrint("Error loading \(url): \(String(describing: error))")
            return nil
        }
    }
}

We are not worried about the details behind how the load() function retrieves and decodes the data, all we really care about is it returns the information that we request. In its current state, it builds the correct URL with the license key, queries the web service, decodes the JSON data that is returned and finally returns the list of cocktails in a format that can be used within our application. This could just as easily be an SQLite call where the CocktailServiceCall contains a list of SQL statements rather than URLs.


If you look through the code of the Mastering Swift Cocktails app, you will notice that the CocktailService and its sister type IngredientsService are used in numerous places to retrieve information from the CocktailsDB service. With these types we are able to control how the CocktailDB service calls are made and if we ever need to change it, like accessing a local database instead, we can very easily do this by changing only these services and avoid having to make changes throughout our code.


masteringSwift.jpeg

Mastering Swift 5.3

The sixth edition of this bestselling book, updated to cover through version 5.3 of the Swift programming language

Amazon

Packt

pop.jpeg

Protocol Oriented Programming

Embrace the Protocol-Oriented design paradigm, for better code maintainability and increased performance, with Swift.

Amazon

Packt

bottom of page