Chapters

Hide chapters

Design Patterns by Tutorials

Third Edition · iOS 13 · Swift 5 · Xcode 11

14. Prototype Pattern
Written by Joshua Greene

The prototype pattern is a creational pattern that allows an object to copy itself. It involves two types:

  1. A copying protocol that declares copy methods.

  2. A prototype class that conforms to the copying protocol.

There are actually two different types of copies: shallow and deep.

A shallow copy creates a new object instance, but doesn’t copy its properties. Any properties that are reference types still point to the same original objects. For example, whenever you copy a Swift Array, which is a struct and thereby happens automatically on assignment, a new array instance is created but its elements aren’t duplicated.

A deep copy creates a new object instance and duplicates each property as well. For example, if you deep copy an Array, each of its elements are copied too. Swift doesn’t provide a deep copy method on Array by default, so you’ll create one in this chapter!

When should you use it?

Use this pattern to enable an object to copy itself.

For example, Foundation defines the NSCopying protocol. However, this protocol was designed for Objective-C, and unfortunately, it doesn’t work that well in Swift. You can still use it, but you’ll wind up writing more boilerplate code yourself.

Instead, you’ll implement your own Copying protocol in this chapter. You’ll learn about the prototype pattern in depth this way, and your resulting implementation will be more Swifty too!

Playground example

Open IntermediateDesignPatterns.xcworkspace in the Starter directory, and then open the Prototype page.

For the example, you’ll create a Copying protocol and a Monster class that conforms to that protocol. Add the following after Code Example:

public protocol Copying: class {
  // 1
  init(_ prototype: Self)
}

extension Copying {
  // 2
  public func copy() -> Self {
    return type(of: self).init(self)
  }
}
  1. You first declare a required initializer, init(_ prototype: Self). This is called a copy initializer as its purpose is to create a new class instance using an existing instance.

  2. You normally won’t call the copy initializer directly. Instead, you’ll simply call copy() on a conforming Copying class instance that you want to copy.

    Since you declared the copy initializer within the protocol itself, copy() is extremely simple. It determines the current type by calling type(of: self), and it then calls the copy initializer, passing in the self instance. Thereby, even if you create a subclass of a type that conforms to Copying, copy() will function correctly.

Next, ddd the following code:

// 1
public class Monster: Copying {

  public var health: Int
  public var level: Int

  public init(health: Int, level: Int) {
    self.health = health
    self.level = level
  }

  // 2
  public required convenience init(_ monster: Monster) {
    self.init(health: monster.health, level: monster.level)
  }
}

Here’s what that code does:

  1. This declares a simple Monster type, which conforms to Copying and has properties for health and level.

  2. In order to satisfy Copying, you must declare init(_ prototype:) as required. However, you’re allowed to mark this as convenience and call another designated initializer, which is exactly what you do.

Next, add the following code:

// 1
public class EyeballMonster: Monster {

  public var redness = 0

  // 2
  public init(health: Int, level: Int, redness: Int) {
    self.redness = redness
    super.init(health: health, level: level)
  }

  // 3
  public required convenience init(_ prototype: Monster) {
    let eyeballMonster = prototype as! EyeballMonster
    self.init(health: eyeballMonster.health,
              level: eyeballMonster.level,
              redness: eyeballMonster.redness)
  }
}

Taking the above code comment-by-comment:

  1. In a real app, you’d likely have Monster subclasses as well, which would add additional properties and functionality. Here, you declare an EyeballMonster, which adds a terrifying new property, redness. Oooh, it’s so red and icky! Don’t touch that eyeball!

  2. Since you added a new property, you also need to set its value upon initialization. To do so, you create a new designated initializer: init(health:level:redness:).

  3. Since you created a new initializer, you must also provide all other required initializers. Note that you need to implement this with the general type, Monster, and then cast it to an EyeballMonster. That’s because specializing to EyeballMonster would mean that it couldn’t take another subclass of Monster, which would break the condition that this is overriding the required initializer from Monster.

You’re now ready to try out these classes! Add the following:

let monster = Monster(health: 700, level: 37)
let monster2 = monster.copy()
print("Watch out! That monster's level is \(monster2.level)!")

Here, you create a new monster, create a copy named monster2 and then print monster2.level. You should see this output in the console:

Watch out! That monster's level is 37!

Enter the following next:

let eyeball = EyeballMonster(
  health: 3002,
  level: 60,
  redness: 999)
let eyeball2 = eyeball.copy()
print("Eww! Its eyeball redness is \(eyeball2.redness)!")

You here prove that you can indeed create a copy of EyeBallMonster. You should see this output in the console:

Eww! Its eyeball redness is 999!

What happens if you try to create an EyeballMonster from a Monster ? Enter the following last:

let eyeballMonster3 = EyeballMonster(monster)

This compiles fine, but it causes a runtime exception. This is due to the forced cast you performed earlier, where you called prototype as! EyeballMonster.

Comment out this line so the playground can run again.

Ideally, you should not allow calls to init(_ monster:) on any subclasses of Monster. Instead, you should always call copy().

You can indicate this to other developers by marking the subclass method as “unavailable.” Add the following line right before the subclass’s init(_ monster:):

@available(*, unavailable, message: "Call copy() instead")

Then, uncomment the line for eyeballMonster3, and you’ll get this error message in the playground console:

error: 'init' is unavailable: Call copy() instead

Great, this prevents calling this method directly! Go ahead and comment out the line again so the playground can run.

What should you be careful about?

As shown in the playground example, by default it’s possible to pass a superclass instance to a subclass’s copy initializer. This may not be a problem if a subclass can be fully initialized from a superclass instance. However, if the subclass adds any new properties, it may not be possible to initialize it from a superclass instance.

To mitigate this issue, you can mark the subclass copy initializer as “unavailable.” In response, the compiler will refuse to compile any direct calls to this method.

It’s still possible to call the method indirectly, like copy() does. However, this safeguard should be “good enough” for most use cases.

If this doesn’t prevent issues for your use case, you’ll need to consider how exactly you want to handle it. For example, you may print an error message to the console and crash, or you may handle it by providing default values instead.

Tutorial project

Over the next few chapters, you’ll complete an app called MirrorPad. This is a drawing app that allows users to create animated mirror-image drawings.

In the Starter directory, open MirrorPad\MirrorPad.xcodeproj in Xcode.

Build and run to try out the app. Draw into the top-left view by using your finger on a real device or mouse on the simulator.

Then press Animate, and your drawing will be re-drawn animated on screen. Super cool!

However, the app is supposed to copy and reflect the image into each of the other views. This currently isn’t implemented because the app doesn’t know how to copy anything! It’s your job to fix this.

Open DrawView.swift and check out this class. This is the heart of the application: it creates a new LineShape object when touchesBegan is called and adds points to LineShape when touchesMoved is called.

Next, open LineShape.swift and check out this class. This is a subclass of CAShapeLayer (see https://developer.apple.com/documentation/quartzcore/cashapelayer), which is used to create simple, light-weight shape layers from paths. If LineShape were copyable, you’d be able to duplicate each of them into the other DrawView instances on screen.

First, however, you actually need to define what “copyable” actually means!

Under the Protocols group in the File hierarchy, create a new Swift file named Copying.swift and replace its contents with the following:

// 1
public protocol Copying {
  init(_ prototype: Self)
}

extension Copying {
  public func copy() -> Self {
    return type(of: self).init(self)
  }
}

// 2
extension Array where Element: Copying {
  public func deepCopy() -> [Element] {
    return map { $0.copy() }
  }
}
  1. You first declare a new Copying protocol, which is exactly the same as the one in the playground example.

  2. You then create an extension on Array when its Element conforms to Copying. Therein, you create a new method called deepCopy(), which uses map to create a new array where each element is generated by calling copy().

Return back to LineShape.swift, and replace the class declaration with the following:

public class LineShape: CAShapeLayer, Copying {

Then, replace init(layer: Any) with the following:

public override convenience init(layer: Any) {
  let lineShape = layer as! LineShape
  self.init(lineShape)
}

public required init(_ prototype: LineShape) {
  bezierPath = prototype.bezierPath.copy() as! UIBezierPath
  super.init(layer: prototype)

  fillColor = nil
  lineWidth = prototype.lineWidth
  path = bezierPath.cgPath
  strokeColor = prototype.strokeColor
}

init(layer:) looks very familiar to init(_ prototype:). This method is used internally by Core Animation during layer animations. In order to actually conform to Copying, however, the method signature must exactly match init(_:). Thereby, you simply hand off init(layer:) to init(_:), and both Core Animation and Copying requirements are satisfied.

You also need a method to actually copy each LineShape onto the DrawView. Open DrawView.swift and add the following right before the ending class curly brace:

public func copyLines(from source: DrawView) {
  layer.sublayers?.removeAll()
  lines = source.lines.deepCopy()
  lines.forEach { layer.addSublayer($0) }
}

This method first removes all of the sublayers, which represent the existing LineShape layers. It then creates a deepCopy from the DrawView that’s passed as the source. Lastly, it adds each line to the layer.

Finally, you actually need to call this method whenever the Animate button on screen is pressed. Open ViewController.swift and add the following right after the opening curly brace for animatePressed(_:):

mirrorDrawViews.forEach { $0.copyLines(from: inputDrawView) }
mirrorDrawViews.forEach { $0.animate() }

This first iterates through each mirrorDrawView and copies the inputDrawView. It then calls animate() on each mirrorDrawView to start the animation.

Build and run, draw into the top-left input view, and press Animate.

Key points

You learned about the prototype pattern in this chapter. Here are its key points:

  • The prototype pattern enables an object to copy itself. It involves two types: a copying protocol and a prototype.

  • The copying protocol declares copy methods, and the prototype conforms to the protocol.

  • Foundation provides an NSCopying protocol, but it doesn’t work well in Swift. It’s easy to roll your own Copying protocol, which eliminates reliance on Foundation or any other framework entirely.

  • The key to creating a Copying protocol is creating a copy initializer with the form init(_ prototype:).

In this chapter, you also implemented key functionality in MirrorPad. This is a pretty neat app, but it does have some issues. For example, the app allows you to continue drawing while it’s animating. You could try to hack a solution for this directly within DrawView, but this class is already starting to get messy and hard to maintain. You’ll use another pattern to fix both of these problems: the state pattern.

Continue onto the next chapter to learn about the state design pattern and continue building out MirrorPad!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.