Software Design

Swift Proxy Pattern: the proxy object hidden behind lazy

When developing for iOS, you end up using lazy var very often.

4 min read
Cover image for Swift Proxy Pattern: the proxy object hidden behind lazy

When developing for iOS, you end up using lazy var very often.

I did too. I habitually used it to defer heavy object initialization.

Then I wondered: isn’t lazy essentially the same Proxy Pattern from design-pattern textbooks?

Let me give you the conclusion first.

lazy is the language-integrated version of a Virtual Proxy that defers creation until the real object is needed.

Today, I’ll explain what Swift’s Proxy Pattern is and how the concept hides behind the lazy keyword, following what I learned by working through the code myself.

Here’s the key summary first.

  1. The Proxy Pattern is a structural pattern that controls access by placing a proxy in front of the real object.
  2. lazy serves the same purpose as a Virtual Proxy that defers creation.
  3. However, lazy cannot handle features such as access control or logging; it only performs lazy creation.
  4. If you need logging or permission checks, you must create a proxy object yourself.

What exactly is Swift’s Proxy Pattern?

Proxy means a representative or intermediary.

You place an intermediary with the same interface in front of the object that does the actual work.

From the outside, it looks as though you are calling the real object, but the request actually goes through the proxy.

The proxy may simply forward the request or do additional work in between: checking permissions, recording calls, or deferring creation of the real object.

There are three typical uses.

  • Virtual Proxy: defer heavy-object creation until it is actually needed
  • Protection Proxy: block unauthorized access
  • Logging Proxy: record call history

Today’s focus is the first one, the Virtual Proxy. It is exactly where lazy connects.


The identity of the proxy object hidden behind lazy

Let’s consider a situation involving high-resolution images.

Image loading is expensive. Loading everything in advance when it is not even visible wastes memory.

That leads to the idea of loading it only when it is truly needed. That is the core of a Virtual Proxy.

Here is a direct implementation using the Proxy Pattern.

protocol Image { func display() }

// Proxy: defer creating the real object until needed
final class ImageProxy: Image {
    private let filename: String
    private var real: RealImage?          // Not created yet
    init(_ filename: String) { self.filename = filename }
    func display() {
        if real == nil { real = RealImage(filename) }  // Created at this moment
        real?.display()
    }
}

Only when display() is first accessed is RealImage created.

Until then, ImageProxy quietly waits while holding only the file name. That is exactly the proxy’s job.

Swift, however, has this pattern built into its syntax. It is lazy.

The moment the proxy holds the real object on its behalf
The moment the proxy holds the real object on its behalf
final class Gallery {
    // created exactly once when this property is first accessed
    lazy var cover: RealImage = RealImage("cover.png")
}

let g = Gallery()   // not yet RealImage created
g.cover.display()   // finally created here

When Gallery() is created, RealImage does not exist.

It is created the first time g.cover is accessed. The compiler effectively creates the deferred initialization that ImageProxy performed above.


If lazy exists, do we still need the Proxy Pattern?

That was my first question too.

The answer is no.

lazy handles only lazy creation.

It cannot block access, record calls, or return different objects depending on conditions.

I summarized the difference in a table.

Item lazy property Custom proxy
Lazy creation Yes Yes
Access permission checks No Yes
Call logging and caching No Yes
Code size One line One class
Reusability Limited to that property Reusable in multiple places

So the guideline is simple.

If you only need to defer creation, lazy one line is the answer. There is no need to create a class.

But if you need to intervene with permission checks, logging, or wrapping a remote call, create a proxy object yourself.

When deferring heavy initialization, this is always the first line I think of
When deferring heavy initialization, this is always the first line I think of

There is one point to watch out for.

lazy does not guarantee thread safety.

If multiple threads access it for the first time simultaneously, initialization may happen twice. In a multithreaded environment, it is safer to wrap this in a custom proxy and handle synchronization yourself.


Frequently asked questions

Q. How does lazy differ from a computed property?

lazy is evaluated once and its value is stored. A computed property is recalculated every time it is accessed. If you want heavy initialization to happen only once, use lazy.

Q. Why can lazy only be declared with var?

Because initialization happens later, an instance briefly exists before its value is determined. let does not allow that, so lazy let is syntactically impossible.

Building a proxy class yourself makes it immediately clear what lazy does for you
Building a proxy class yourself makes it immediately clear what lazy does for you

In summary, lazy is like a gift in which the language has packaged a Virtual Proxy from the Proxy Pattern in advance.

You do not need to create a proxy class for something that fits on one line, but understanding the concept hidden behind that line changes how you read code.

From today on, whenever you use lazy var, think, “Ah, I’m placing a proxy here.” The pattern will feel much more familiar.

More to read