Software Design

Swift Visitor Pattern: When Double Dispatch Is Needed

In iOS development, you eventually need to keep adding operations to objects of many kinds, such as shapes or nodes.

4 min read
Cover image for Swift Visitor Pattern: When Double Dispatch Is Needed

In iOS development, you eventually need to keep adding operations to objects of many kinds, such as shapes or nodes.

You often end up writing code that inspects types one by one with if let and as?. Each new kind means reopening the branching logic.

This article explains Swift Visitor Pattern and, through examples, exactly when its core technique, double dispatch, is needed.

When choosing a method, Swift looks only at the dynamic type of the receiver, the object receiving the message.

When both “which shape?” and “which operation?” can change, single dispatch is not enough. Double dispatch determines both types at runtime through two calls.


Why Is Swift’s Method Dispatch Not Enough on Its Own?

When you call shape.draw() in Swift, shape matching the actual type of draw() is executed.

That is single dispatch: the method is selected using only the receiver type, ****.

The problem appears when there are multiple operations.

Shapes include circles, rectangles, and triangles; operations include drawing, area calculation, and JSON export. Combining the two axes rapidly multiplies the combinations.

So you often end up writing code like this.

// It keeps growing as operations are added for each shape if-else
func export(_ shape: Shape) -> String {
    if let c = shape as? Circle { return drawCircle(c) }
    if let r = shape as? Rect   { return drawRect(r) }
    return "" // You have to modify this again whenever a new shape appears
}

What happens when you add one shape? You must reopen this function and insert another branch.

With five operations, you effectively have five such functions.

When if-else branches pile up this much, I take it as a signal.
When if-else branches pile up this much, I take it as a signal.

When Do You Need Double Dispatch?

My rule is simple.

When the behavior of a method depends on two types at once, that is when double dispatch is needed.

The example above is exactly that: the code to execute depends on both the shape type and the operation type.

Swift’s default dispatch considers only the receiver type, so you handle the other axis manually with type checks such as as?.

That manual branching is the code smell.

Double dispatch delegates even the second type decision to the language’s overloading, so developers no longer inspect types themselves.

Conversely, if there is only one operation or the types will not grow, this pattern is unnecessary.


How Do You Implement the Swift Visitor Pattern?

The key is to split the call into two steps. That is why it is called double dispatch.

protocol Shape { func accept(_ v: Visitor) -> String }
struct Circle: Shape { func accept(_ v: Visitor) -> String { v.visit(self) } }
struct Rect: Shape   { func accept(_ v: Visitor) -> String { v.visit(self) } }

protocol Visitor {
    func visit(_ c: Circle) -> String   // The shape type is determined here
    func visit(_ r: Rect) -> String      // Overloading determines the operation here
}

Let’s follow the flow.

In the first call, shape.accept(v), the actual type of the shape is determined. Runtime chooses whether Circle’s accept or Rect’s is called.

In the second call, v.visit(self), the static type of self is already fixed as Circle or Rect. The overloaded visit is therefore selected correctly.

These two calls overlap, determining both types precisely.

The point where two calls overlap: double dispatch
The point where two calls overlap: double dispatch

To add an operation, create one new struct adopting Visitor. Existing shape code remains untouched.


Should You Use the Visitor Pattern? Comparing the Trade-offs

No pattern is perfect. Let’s examine the trade-offs.

Category Type branching (as? / switch) Visitor Pattern
Adding an operation Modify branches in multiple places Add one new Visitor
Adding a new type Add one branch Modify every Visitor
Code readability Gets complex as branches grow Cleanly separated by operation
Initial implementation cost Low Relatively high

As the table shows, the Visitor Pattern shines when operations are added frequently while types remain stable.

If types (shapes) keep being added, however, it creates more work because every Visitor must be updated.

Call accept, then visit—just two steps
Call accept, then visit—just two steps

Frequently Asked Questions (Q&A)

Q. Swift has generics. Is the Visitor Pattern still necessary?

Generics are powerful when types are known at compile time. But when runtime data contains mixed concrete types, such as [Shape] an array, double dispatch still has a role.

Q. Couldn’t enum and switch work too?

They can. If types are fixed, enum + switch is often simpler. Visitor is better suited to situations where operations keep growing.


When behavior depends on two types at once, think of double dispatch—and the Swift Visitor Pattern.

If you have code where as? branches keep multiplying, use today’s example to try a refactor. The result will be much easier to maintain 🙂