Skip to content

The Proxy Design Pattern

Published on:February 23, 2021 at 12:00 PM

What is the Proxy design pattern?

As defined in the book Design Patterns by Gamma, Johnson, Vlissides, Helm and collaborators, the proxy design pattern is defined:

Provide a surrogate or placeholder for another object to control access to it. It makes consumers believe they’re talking to the real implementation.

And its diagram looks like this:

Diagram

Diagram from Wikipedia

If this is the first time that you hear about this design pattern, it can be a little confused, and their propourse can be not clear at all. But let me give you an example to try to explain where can use this design pattern.

Using a proxy in the MVP UI design pattern

When we are working with a UI design pattern like MVP in iOS applications, we notice that a retain cycle can be created if we don’t weakify the view controller due a two-way communication channel between View and Presenter. Let’s see the diagram:

MVP Diagram

For example:

// MARK: - UI

class ViewController: UIViewController, View {
    var presenter: Presenter?

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        presenter?.didRequestMessage()
    }

    func display(_ viewModel: ViewModel) {
        print(viewModel.message)
    }
}

// MARK: - Presenter

struct ViewModel {
    let message: String
}

protocol View {
    func display(_ viewModel: ViewModel)
}

class Presenter {
    private let fetcher: FetchMessage
    private let view: View

    init(fetcher: FetchMessage, view: View) {
        self.fetcher = fetcher
        self.view = view
    }

    func didRequestMessage() {
        fetcher.get { [weak self] message in
            self?.view.display(ViewModel(message: message))
        }
    }
}

// MARK: - Business logic

class FetchMessage {
    func get(completion: @escaping (String) -> Void) {
        completion("Hello, World!")
    }
}

As we can see, we need to be careful with the connection between the Presenter and the Controller due the two-way communication. One way that solves the problem is making weak the reference between the presenter and the controller.

private weak var view: View?

But doing this, generates the following compiler error:

‘weak’ must not be applied to non-class-bound ‘View’; consider adding a protocol conformance that has a class bound

We can easily solve this compilation error is making the protocol only for classes (using the class keyword to make the protocol Class-Only). This constraint is defined as The protocol to which all classes implicitly conform. like this:

protocol View: class {
    func display(_ viewModel: ViewModel)
}

This solves our problem, but doing this we expose memory management in the presenter. Ideally we need to deal with it in the composition root. Until now, our composition root looks like this:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) {
        guard let scene = (scene as? UIWindowScene) else { return }
        window = UIWindow(windowScene: scene)
        window?.rootViewController = Composer.composeWith(fetcher: FetchMessage())
        window?.makeKeyAndVisible()
    }
}

class Composer {
    static func composeWith(fetcher: FetchMessage) -> ViewController {
        let controller = ViewController()
        let presenter = Presenter(fetcher: fetcher, view: controller)
        controller.presenter = presenter
        return controller
    }
}

To move the memory management from the Presenter into CompositionRoot we can create a proxy, where we can make consumers believe they’re talking to the real implementation. So, How does look like the proxy implementation?

class WeakRefProxy: View {
    weak var view: (View & AnyObject)?

    init(_ view: View & AnyObject) {
        self.view = view
    }

    func display(_ viewModel: ViewModel) {
        view?.display(viewModel)
    }
}

Also remove the class only constraint in the View protocol and make our view: View variable let again:

protocol View { // Remove class constraint
    func display(_ viewModel: ViewModel)
}

class Presenter {
    private let fetcher: FetchMessage
    private let view: View // Restore to a let

    init(fetcher: FetchMessage, view: View) {
        self.fetcher = fetcher
        self.view = view
    }

    func didRequestMessage() {
        fetcher.get { [weak self] message in
            self?.view.display(ViewModel(message: message))
        }
    }
}

In the Proxy design pattern we have a <> that in this case is our View protocol. Also we require a RealSubject and the Proxy, that in this case our RealSubject is the ViewController class and our Proxy is of course the WeakRefProxy class.

Diagram

In the WeakRefProxy we surrogate the real implementation with a weak reference to it, that’s all we want to avoid retain cycles. And now our Composition Root looks like this:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) {
        guard let scene = (scene as? UIWindowScene) else { return }
        window = UIWindow(windowScene: scene)
        window?.rootViewController = Composer.composeWith(fetcher: FetchMessage())
        window?.makeKeyAndVisible()
    }
}

class Composer {
    static func composeWith(fetcher: FetchMessage) -> ViewController {
        let controller = ViewController()
        let presenter = Presenter(fetcher: fetcher, view: WeakRefProxy(controller))
        controller.presenter = presenter
        return controller
    }
}

We moved the memory management from the Presenter into the CompositionRoot, by doing so, we don’t need to cover the requirement to constrain all view protocols to be classes in MVP and don’t leak composition details in the presenter implementation.

Making generic our WeakRef Proxy

Of course, creating a WeakRefProxy can be very tedious, so we ca create a generic one and implement the View methods into a extension, like so:

class WeakRefProxy<T: AnyObject> {
    weak var object: T?

    init(_ object: T) {
        self.object = object
    }
}
extension WeakRefProxy: View where T: View {
    func display(_ viewModel: ViewModel) {
        object?.display(viewModel)
    }
}

You can find the example source code on github: https://github.com/alfredohdzdev/WeakRef