Prototype Design Pattern in Swift


Greetings, traveler!

We continue our exploration of Creational Design Patterns. Now, it is the turn of the Prototype Design Pattern.

When might we need to use it? Consider a scenario where we need to create multiple copies of an object, each with minor differences. How could we approach this? Sure thing, we can create something like this.

struct Object {
    var name: String
    var color: UIColor
    var count: Int
}

let firstObject = Object(name: "First Name", color: .red, count: 1)
let secondObject = Object(name: "Second Name", color: .red, count: 1)
let thirdObject = Object(name: "Third Name", color: .red, count: 1)

But it looks inconvenient. We can also make a mistake while copying the code. However, we can follow the Prototype pattern, which will simplify the process and reduce the chances of errors. 

Example of usage

Let’s take a real-world example to illustrate the Prototype Design Pattern. Imagine we’re designing a furniture catalog. We need to create a set of furniture: chair, table, and sofa. We created a model for these objects in our previous discussion on the Builder pattern. Let’s repeat the code.

enum FurnitureKind {
    case chair, table, sofa
}

enum WoodKind {
    case oak, pine, cherry
}

struct Furniture {
    let kind: FurnitureKind
    let color: UIColor
    let material: WoodKind
}

Now, let’s create multiple copies of it. Every single copy will have only one difference — the furniture type. 

let chair = Furniture(kind: .chair, color: .brown, material: .cherry)
let table = Furniture(kind: .table, color: .brown, material: .cherry)
let sofa = Furniture(kind: .sofa, color: .brown, material: .cherry)

Let’s create a function inside our Furniture model to avoid mistakes and achieve a cleaner solution.

struct Furniture {
    
    let kind: FurnitureKind
    let color: UIColor
    let material: WoodKind
    
    func copy(
        kind: FurnitureKind? = nil,
        color: UIColor? = nil,
        material: WoodKind? = nil
    ) -> Furniture {
        
        Furniture(
            kind: kind ?? self.kind,
            color: color ?? self.color,
            material: material ?? self.material
        )
    }
    
}

And now, let’s use it:

let chair = Furniture(kind: .chair, color: .brown, material: .cherry)
let table = chair.copy(kind: .table)
let sofa = chair.copy(kind: .sofa)

Much better!

When It Makes Sense — and When It Doesn’t

The Prototype pattern is designed to create new objects by cloning existing ones. Instead of rebuilding configuration from scratch, you define a base instance (a “prototype”) and produce variations from it. This avoids duplication and reduces the risk of inconsistent configurations across multiple objects.

In Swift, however, the practicality of this pattern heavily depends on whether you are dealing with value types or reference types.

Value Types: Why Structs Rarely Need Prototype

In Swift, struct types have value semantics. This means every assignment, return, or mutation implicitly produces a copy of the struct. Swift handles this efficiently through Copy-on-Write, giving you copying “for free”:

let original = Product(name: "Table", color: .brown)
var copy = original
copy.color = .black

Here, copy and original become independent without any special pattern, no custom cloning logic, and no risk of shared mutable state.

Because of this, structs rarely need the Prototype pattern.

Reference Types: Where Prototype Actually Matters

For class types, copying no longer happens automatically. Classes have reference semantics, so two variables may point to the same underlying instance:

let a = Chair()
let b = a  // b references the same object!

Mutating b affects a, creating bugs that are often subtle.

This is where Prototype becomes meaningful: you explicitly define how an object is cloned, whether the copying is shallow or deep, and what parts of the object graph need to be duplicated.

NSCopying: The Standard Way to Implement Copying for Classes

Swift’s Foundation framework provides a dedicated protocol — NSCopying — for reference-type cloning:

final class Furniture: NSCopying {
    var name: String
    var color: UIColor
    var material: WoodKind

    init(name: String, color: UIColor, material: WoodKind) {
        self.name = name
        self.color = color
        self.material = material
    }

    func copy(with zone: NSZone? = nil) -> Any {
        Furniture(
            name: self.name,
            color: self.color,
            material: self.material
        )
    }
}

Conclusion

This is all I wanted to say about the Prototype Design Pattern. In the following article, we will talk about Singleton (finally!) — see you there!