Proxy Design Pattern in Swift


Greetings, traveler!

We continue to analyze Design Patterns, and today, we will focus on the latest Structural Pattern, a Proxy.

A Proxy is used in different situations. For instance, you might have a service that you only want to start under specific conditions. In this case, you could use a Proxy to check if the conditions are met before allowing the service to start.

But a Proxy’s versatility doesn’t end there. It can also log or cache data or check conditions for running specific functions.

Example of usage

Let’s look at some usage examples. For instance, you have a network service and want to run a query multiple times. However, you also want to be able to cancel a previous request with the same label. To do this, we can use a proxy.

protocol DownloadManagerProtocol {
    func download(url: URL) -> URLSessionDownloadTask
}

final class DownloadManager: DownloadManagerProtocol {
    
    @discardableResult
    func download(url: URL) -> URLSessionDownloadTask {
        let task = URLSession
            .shared
            .downloadTask(with: URLRequest(url: url))
        
        task.resume()
        
        return task
    }
    
}

final class DownloadManagerProxy: DownloadManagerProtocol {

    private let manager: DownloadManagerProtocol
    private var cache: [URL: URLSessionDownloadTask] = [:]

    init(manager: DownloadManagerProtocol) {
        self.manager = manager
    }

    @discardableResult
    func download(url: URL) -> URLSessionDownloadTask {
        if let task = cache[url] {
            task.cancel()
        }
        
        let newTask = manager.download(url: url)
        cache[url] = newTask
        
        return newTask
    }
    
}

Another example could be restricting access to certain features or actions if specific conditions are not fulfilled. Here is an example of how this might work in practice.

We can only provide certain features to those users who have purchased a premium subscription.

struct User {
    let isPremium: Bool
}

protocol DisplayProtocol {
    func display()
}

final class DisplayManager: DisplayProtocol {
    
    func display() {
        print("display premium features")
    }
    
}

final class DisplayManagerProxy: DisplayProtocol {
    
    private let user: User
    private let manager: DisplayManager
    
    init(user: User, manager: DisplayManager) {
        self.user = user
        self.manager = manager
    }
    
    func display() {
        guard user.isPremium else {
            print("Wanna buy premium?")
            return
        }
        
        manager.display()
    }
    
}

Conclusion 

Of course, there are many more great examples, but I think the overall idea is clear, so let’s wrap up this discussion of the Proxy design pattern and move on to the next topic, Behavioral Design Patterns. The first one we will discuss is the Chain of Responsibility pattern. See you in the following article.