Proxy pattern in iOS production

abdul ahad
7 min readMar 30, 2024

--

According to GOF

Photo by Connor Coyne on Unsplash

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

The Proxy pattern provides a placeholder for another object to control its access. It's useful when you want to control or manage access to the original object, typically to add some kind of behavior, like lazy initialization, access control, or weak reference management.

Proxies vary in the degree to which they are implemented like a decorator. A protection proxy might be implemented exactly like a decorator. On the other hand, a remote proxy will not contain a direct reference to its real subject but only an indirect reference, such as “host ID and local address on host.” A virtual proxy will start off with an indirect reference such as a file name but will eventually obtain and use a direct reference._

let’s see theoretically how it works?

the proxy implements the same interface as the the object it wants to control access so the client thinks it’s talking to the real object. so behind the scenes it can do things like lazy initialization, access control, or weak reference management and forward the call to the real object.

lazy initialization

// Protocol defining the methods for the real subject
protocol ObjectLifecycleProtocol: AnyObject {
func performAction()
}

// Real subject class that we want to manage the lifecycle of
class RealObject: ObjectLifecycleProtocol {
init() {
print("RealObject initialized")
}

func performAction() {
print("RealObject performing action")
}

deinit {
print("RealObject deinitialized")
}
}

// Proxy class that will manage the lazy initializing of the real object
class ObjectProxy: ObjectLifecycleProtocol {
private var realObject: RealObject?

init() {
print("ObjectProxy initialized")
}

func performAction() {
// Lazy initialization of the real object
if realObject == nil {
realObject = RealObject()
}
realObject?.performAction()
}

deinit {
print("ObjectProxy deinitialized")
}
}

// Usage example
var proxy: ObjectProxy? = ObjectProxy() // Proxy initialized
proxy?.performAction() // Real object initialized and action performed
proxy = nil // Proxy deinitialized
  • We have a real subject class RealObject that conforms to ObjectLifecycleProtocol. It represents the object whose lifecycle we want to manage.
  • We have a proxy class ObjectProxy that also conforms to ObjectLifecycleProtocol.
  • When performAction() is called on the proxy, it lazily initializes the real object if it hasn't been initialized yet and then forwards the method call to the real object.
  • We create an instance of the proxy, call performAction(), and then set the proxy to nil to observe the initialization and deallocation of the proxy and real object.

access control with proxy

Access control refers to the ability to restrict the visibility and usage of certain parts of your code from other parts. In Swift, access control is managed using access control keywords like private, fileprivate, internal, public, and open.

When using the Proxy pattern, access control plays an important role in determining which parts of your proxy and real subject can be accessed from outside the module.

Let’s consider a scenario where we have a real subject class RealObject and a proxy class ObjectProxy. We want to restrict direct access to the RealObject from outside the module and only allow access through the proxy.

Here’s how you can apply access control with a proxy in Swift:

so the real question is how does the above translate to iOS development

we will use proxy patterns in iOS applications to prevent retain cycles effectively and cleanly.

// ModuleA.swift

// Real subject class with restricted access only within module
class RealObject {
private var value: Int

init(value: Int) {
self.value = value
}

func performAction() {
print("RealObject performing action with value \(value)")
}
}

// public Proxy class that is accessible from outside the module
public class ObjectProxy {
private var realObject: RealObject

init(value: Int) {
self.realObject = RealObject(value: value)
}

func performAction() {
realObject.performAction()
}
}

// Usage in another module

let proxy = ObjectProxy(value: 42)
proxy.performAction() // Can access RealObject through the proxy
  • In ModuleA.swift, we define the RealObject class with access control set to private. This means that RealObject can only be accessed from within the same source file and not from outside the module.
  • We define the ObjectProxy class with access control set to public. This allows ObjectProxy to be accessed from outside the module.
  • The ObjectProxy class creates and manages an instance of RealObject internally, but the RealObject itself is not directly accessible from outside the module.
  • When using the proxy from another module, we can only interact with the RealObject through the proxy, ensuring that access to the real subject is controlled.

By applying access control in this way, we ensure that the real subject’s implementation details are encapsulated and only accessible through the proxy, promoting better encapsulation and modularization of code.

weak reference counting

Weak reference counting is a mechanism used in programming languages, including Swift, to manage memory and avoid strong reference cycles, also known as retain cycles.

In Swift, objects are held in memory as long as there is at least one strong reference to them. A strong reference means that an object cannot be deallocated as long as there is at least one strong reference pointing to it. This is the default behavior in Swift when you create an instance of a class.

However, strong references can lead to retain cycles, where two or more objects hold strong references to each other, preventing them from being deallocated even when they are no longer needed. This can cause memory leaks and degrade app performance.

// Protocol defining the methods for the real subject
protocol ObjectProtocol: AnyObject {
func performAction()
}

// Real subject class that we want to manage the lifecycle of
class RealObject: ObjectProtocol {
func performAction() {
print("RealObject performing action")
}
}

// Proxy class that will manage the reference counting of the real object
class ObjectProxy: ObjectProtocol {
// Weak reference to the real object
weak var realObject: RealObject?

init(realObject: RealObject) {
self.realObject = realObject
}

func performAction() {
realObject?.performAction()
}
}

// Usage example
var realObject: RealObject? = RealObject()
var proxy: ObjectProxy? = ObjectProxy(realObject: realObject!)

// Perform action through the proxy
proxy?.performAction() // Output: RealObject performing action

// Set the real object and proxy to nil to observe deallocation
realObject = nil
proxy = nil

we have a proxy class ObjectProxy that acts as a surrogate for the real object RealObject. We want to avoid creating a retain cycle where the real object holds a strong reference to the proxy, and the proxy holds a weak reference to the real object.

iOS Example: Using a proxy in the MVP UI design pattern

Problem

let us take a simple example from the MVP UI pattern where we simulate a simple request and show it on the View Controller.

class loader{
func load() -> [String] {
return["a","b","c","d"]
}
}
// for presentation
struct ViewModel{
let message:[String]
}
protocol AbstractView{
func display(viewModel:ViewModel)
}

class Presenter{
let view:AbstractView
let loader:loader

init(view: AbstractView, loader: loader) {
self.view = view
self.loader = loader
}

func fetch() {
print("fetch")
let a = loader.load()
self.view.display(viewModel: ViewModel(message: a))

}
}

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

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

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




//composer for instantiating
public class Composer{
public static func instantiate() -> UIViewController {
let vc = ViewController()
let loader = loader()
let presenter = Presenter(view:vc, loader: loader)
vc.presenter = presenter
return vc
}
}

#Preview{
Composer.instantiate()
}

as you can see in the diagram below both the VC and the presenter reference each other which creates a memory leak.

let’s see it using a test. As you can see we get a memory leak as the ViewController doesn’t get deallocated.

Solution: Proxy

Proxy conforms to the same interface as the viewController and acts as its surrogate by using a weak reference whereby managing the reference counting of the real object.

import Foundation

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

init(_ object: T) {
self.object = object
}
}

extension WeakRefProxy: AbstractView where T: AbstractView {
func display(viewModel: ViewModel) {
object?.display(viewModel: viewModel)
}
}

now let us see how we use the proxy

public class Composer{
public static func instantiate() -> UIViewController {
let vc = ViewController()
let loader = loader()
//the proxy acting as surrogate here for the viewcontroller
let presenter = Presenter(view: WeakRefProxy(vc), loader: loader)
vc.presenter = presenter
return vc
}
}

let’s verify using our tests if we get a memory leak and viola we don’t

Conclusion

The proxy Pattern promotes cleaner, more modular, and maintainable software architectures, ultimately enhancing the quality and robustness of the system.

Reference:

Github: https://github.com/abdahad1996/DesignPatterns_Bootcamp

--

--

abdul ahad

A software developer dreaming to reach the top and also passionate about sports and language learning