Top 12 iOS Development Design Patterns You Should Know

Throughout my career I’ve learned some design patterns that make my code so much cleaner.

Here are my top 12 design patterns that you should know:

  1. Singleton
  2. Facade
  3. Factory
  4. Decorator
  5. Memento
  6. Adapter
  7. Observer
  8. Iterator
  9. Mediator
  10. Command
  11. Template
  12. Strategy

In this article I’ll explain each of these patterns, when you should use them and when you shouldn’t.

Singleton

A singleton class only allows one instance of itself to be created.

This is some-what disputed design pattern, as some critics say it’s an anti-pattern as it introduces global state into your application.

There are definitely right and wrong times to use a singleton pattern, and you should only use it when you are sure you only want one instance of a class created.

Apple uses the Singleton pattern often in their code.

Audio channels to play sound effects and network managers are good examples where singletons can be useful.

To create singleton use a static type property. Static properties are guaranteed to be lazily initialized only once, even across multiple threads.

class SomeSingleton {
  static let sharedInstace = SomeSingleton()
}

If you need to do some additional configuration you can use the following code:

class SomeSingleton {
  static let sharedInstance: SomeSingleton = {
    let singleton = SomeSingleton()
    // other configuration code here
    return singleton
  }
}

Try to avoid using singletons for global state, as they can quickly balloon in size and become unmanageable.

Facade

top 10 ios development design patterns you should know 2020 03 20 12 28 22

A facade is a a structural design pattern. The word facade means the “face of a building”.

As the name suggests, the clients walking outside don’t know what’s inside the building. The complexities of the electrical, plumbing, elevators and stairways…

The facade design pattern does the same, it hides the complexities of a system and displays a friendly face.

This allows clients to query more complex systems with a simplified interface.

A good example of this facade is the start up of a computer.

When you turn on your computer of bunch of different things happen.

  1. Screen turns on
  2. Keyboard lights up
  3. Hard drives start spinning
  4. Wifi antenna is turned on
  5. Power on noise plays
  6. Mouse and keyboard are connected
  7. And many more…

For the user or client, this would be annoying to do all these tasks every time.

So the power button acts as a facade, the user just pushes a button and all the necessary steps to power on the computer are activated.

The facade design pattern is great to use when you want to simply the interface of a complex system.

You should not use a facade if the system is already simple, as this adds an unnecessary layer of code.

Factory

The factory design pattern is a very popular pattern not only in Swift, but in many programming languages.

The factory pattern allows you to create an object without exposing the creation details to the client. The object adheres to a common interface or in Swift, a common protocol.

top 10 ios development design patterns you should know 2020 03 20 13 08 50

Here is the shape example in Swift:

import UIKit

protocol Shape {
    func draw()
}

struct Triangle: Shape {
    func draw() {
        print("Drawing a Triangle!")
    }
}

struct Circle: Shape {
    func draw() {
        print("Drawing a Circle!")
    }
}

struct Rectangle: Shape {
    func draw() {
        print("Drawing a Rectangle!")
    }
}

struct ShapeFactory {
    func getShape(shapeType: String) -> Shape? {
      if shapeType == "circle" {
         return Circle()
      } else if shapeType == "rectangle" {
         return Rectangle()
      } else if shapeType == "triangle" {
         return Triangle()
      }
      return nil
   }
}

let userInput = "triangle"
let shapeFactory = ShapeFactory()
let shape = shapeFactory.getShape(shapeType: userInput)
shape?.draw() // prints: Drawing a Triangle!

So when is the best time to used a factory pattern?

When you don’t know what kind of object until runtime.

In the above example, we’re not sure whether we need a rectangle, shape or circle until some user input.

If we didn’t use the factory pattern, we would have to create a bunch of if statements on the user input like this:

let userInput = "triangle"
var shape: Shape?
if userInput == "circle" {
    shape = Circle()
} else if userInput == "rectangle" {
    shape = Rectangle()
} else if userInput == "triangle" {
    shape = Triangle()
}
shape?.draw()

The factory method just keeps all this organized in one struct.

When should you not use factories? Well, most of the time…

Only use factories when you need to abstract away object creation.

You should ask yourself, does this make your code better? Easier to read?

Decorator

Decorators are a conceptual design pattern that allows you to add new functionality to an object without altering its original structure.

They are quite often used in streams. Decorators are essentially wrapper objects.

You can wrap objects as many times as you want, since both the object and the decorator follow the same interface.

Here’s an example of how to use it.

protocol Shape {
    func draw() -> String
}

class Triangle: Shape {

    func draw() -> String {
        return "drawing triangle"
    }
}

class Decorator: Shape {

    private var shape: Shape

    init(_ shape: Shape) {
        self.shape = shape
    }

    func draw() -> String {
        return shape.draw()
    }
}

class BorderDecorator: Decorator {
    override func draw() -> String {
        return "\(super.draw()) + drawing border"
    }
}

class FillDecorator: Decorator {
    override func draw() -> String {
        return "\(super.draw()) + filling in shape with blue color"
    }
}


let triangle = Triangle()
print(triangle.draw())

let triangleWithBorder = BorderDecorator(triangle)
print(triangleWithBorder.draw())

let triangleWithBorderAndFilled = FillDecorator(triangleWithBorder)
print(triangleWithBorderAndFilled.draw())

As you can see, you can wrap an object in as many decorators as you want.

Be sure when you’re creating decorators that you consider the pros and cons of just modifying the original struct.

Memento

Memento is a behavioral design pattern that captures the current state of an object and allows it to be restored in the future.

Kind of the like the movie… Memento. A great flick by the way if you haven’t seen it yet.

This is great when you need to restore a state, for example if your app allows the user to “Undo” certain actions.

The memento design pattern consists of thee objects: the originator, a caretaker and a memento.

The originator is an object that has an internal state.

The caretaker is the one that changes the internal state of the originator, but wants to be able to undo the change.

The memento is what tracks all the changes.

Here’s an example of the memento design pattern in Swift:

class Originator {
    private var state: String

    init(state: String) {
        self.state = state
        print("Originator state: \(state)")
    }

    func changeState() {
        print("Originator is changing state")
        state = getRandomString()
        print("Originator state is now: \(state)")
    }

    private func getRandomString() -> String {
        return String(UUID().uuidString.suffix(10))
    }

    func save() -> SomeMemento {
        return SomeMemento(state: state)
    }

    func restore(memento: SomeMemento) {
        self.state = memento.state
        print("Originator: My state has changed to: \(state)")
    }
}

protocol Memento {
    var name: String { get }
    var date: Date { get }
}

class SomeMemento: Memento {

    var state: String
    var date: Date

    init(state: String) {
        self.state = state
        self.date = Date()
    }

    var name: String { return "\(state) - \(date.description)" }
}

class Caretaker {

    private lazy var mementos = [SomeMemento]()
    private var originator: Originator

    init(originator: Originator) {
        self.originator = originator
    }

    func backup() {
        print("Caretaker: Saving state")
        mementos.append(originator.save())
    }

    func undo() {
        print("Caretaker: Undoing state")
        originator.restore(memento: mementos.removeLast())
    }
}

let originator = Originator(state: "Random String Here")
let caretaker = Caretaker(originator: originator)

caretaker.backup()
originator.changeState()

caretaker.backup()
originator.changeState()

caretaker.backup()
originator.changeState()

caretaker.undo()
caretaker.undo()
caretaker.undo()

This will print out:

Originator state: Random String Here
Caretaker: Saving state
Originator is changing state
Originator state is now: 757A7CED44
Caretaker: Saving state
Originator is changing state
Originator state is now: 55BE176B81
Caretaker: Saving state
Originator is changing state
Originator state is now: D95496BC60
Caretaker: Undoing state
Originator: My state has changed to: 55BE176B81
Caretaker: Undoing state
Originator: My state has changed to: 757A7CED44
Caretaker: Undoing state
Originator: My state has changed to: Random String Here

Adapter

The adapter pattern is similar to an electrical outlet adapter.

When you’re traveling you use an outlet adapter to plug in your phone and other electronics.

An adapter pattern converts a class interface into another interface that is expected by the client.

This allows objects that normally couldn’t work, be able to interface with one another.

This often used to make legacy code work with the newer code.

There are three main actors in the adapter design pattern: the adaptee, the adapter and the target.

The adapter makes the adaptee’s interface compatible with the target interface.

Here’s an example of this pattern in use:

class Target {
    func orderBurger() -> String {
        return "I want a hamburger with onions and cheese."
    }
}


class Adaptee {
    public func specialOrderBurger() -> String {
        return ".puhctek dna seotamot htiw regrubmah a tnaw I"
    }
}

class Adapter: Target {
    private var adaptee: Adaptee

    init(_ adaptee: Adaptee) {
        self.adaptee = adaptee
    }

    override func orderBurger() -> String {
        return "Adapter: " + adaptee.specialOrderBurger().reversed()
    }
}

print("I can understand the target's order fine:")
let target = Target()
print(target.orderBurger())
print("I cannot understand the adaptee's order:")
let adaptee = Adaptee()
print(adaptee.specialOrderBurger())
print("With a help of an adapter I can understand the adaptee's order:")
let adapter = Adapter(adaptee)
print(adapter.orderBurger())

This prints out:

I can understand the target's order fine:
I want a hamburger with onions and cheese.
I cannot understand the adaptee's order:
.puhctek dna seotamot htiw regrubmah a tnaw I
With a help of an adapter I can understand the adaptee's order:
Adapter: I want a hamburger with tomatoes and ketchup.

Observer

The observer pattern is a one-to-many relationship.

It’s best used when there’s a relationship between objects such that if one object is modified, the observing objects are notified automatically.

The object being watched is called the subject and the dependent objects are the observers.

A good example of this is an email newsletter.

The subject in this case is the email newsletter.

The observers are the subscribers.

Every time there is a new email newsletter, all the subscribers are immediately notified.

Then each subscriber can perform an action when notified. In this email example, subscribers can choose to read it, delete it, forward it or reply to it.

Here’s an example in Swift code:

class Subject {

    var number: Int = 2

    private lazy var observers = [Observer]()

    func subscribe(_ observer: Observer) {
        print("Subject: Subscribed an observer")
        observers.append(observer)
    }

    func publish() {
        print("Subject: Publishing to observers")
        observers.forEach({ $0.update(subject: self)})
    }

    func addOne() {
        number += 1
        publish()
    }
}

protocol Observer: class {
    func update(subject: Subject)
}

class OddObserver: Observer {
    func update(subject: Subject) {
        if subject.number % 2 == 1 {
            print("Wohoo odd number!")
        }
    }
}

class EvenObserver: Observer {
    func update(subject: Subject) {
        if subject.number % 2 == 0 {
            print("Yay an even number!")
        }
    }
}


let subject = Subject()

let observer1 = OddObserver()
let observer2 = EvenObserver()

subject.subscribe(observer1)
subject.subscribe(observer2)

subject.addOne()
subject.addOne()

The observer pattern is best used in a one-to-many object relationship. Consider another pattern if the object relationship does not look like this .

Iterator

The iterator pattern allows sequential traversal through data structures without exposing the internal workings.

For example, usually when you iterate through an array of numbers, they will come out ordered by array index.

var someNumbers = [1, 2, 3]

for number in someNumbers {
    print(number)
}

Prints:

1
2
3

But what if you had a special data structure that reversed the order?

class ReverseNumbersCollection {
    fileprivate lazy var items = [Int]()
    func append(_ item: Int) {
        self.items.append(item)
    }
}

extension ReverseNumbersCollection: Sequence {
    func makeIterator() -> AnyIterator<Int> {
        var index = self.items.count - 1
        return AnyIterator {
            defer { index -= 1 }
            return index >= 0 ? self.items[index] : nil
        }
    }
}

let someNumbers = ReverseNumbersCollection()
someNumbers.append(1)
someNumbers.append(2)
someNumbers.append(3)

for number in someNumbers {
    print(number)
}

This will print:

3
2
1

Notice how the for loop knew nothing about the internal workings of the ReverseNumbersCollection but was able to output the correct result.

The pattern is useful when you want to create a collection of objects that need a specific traversal method.

Mediator

The mediator design pattern helps reduce the coupling between two objects.

The objects communicate indirectly, through a mediator instead of directly.

This can simplify and decouple parts of the codebase.

The mediator “adds functionality” by orchestrating different actions by different objects. This is the main difference between mediators and facades.

A good example of this is air traffic controller at an airport.

Air traffic control is responsible for the planes interaction with each other. When they can land and when they can take off.

The planes do not talk to each other directly.

top 10 ios development design patterns you should know 2020 03 22 15 00 06

Here is a code example in Swift:

protocol Mediator: AnyObject {
    func notify(sender: BaseWidget, event: String)
}

class SomeMediator: Mediator {

    private var widget1: Widget1
    private var widget2: Widget2

    init(_ widget1: Widget1, _ widget2: Widget2) {
        self.widget1 = widget1
        self.widget2 = widget2

        widget1.update(mediator: self)
        widget2.update(mediator: self)
    }

    func notify(sender: BaseWidget, event: String) {
        if event == "A" {
            widget2.executeC()
        }
        else if (event == "D") {
            widget1.executeB()
            widget2.executeC()
        }
    }
}

class BaseWidget {
    fileprivate weak var mediator: Mediator?
    init(mediator: Mediator? = nil) {
        self.mediator = mediator
    }

    func update(mediator: Mediator) {
        self.mediator = mediator
    }
}

class Widget1: BaseWidget {
    func executeA() {
        print("Widget 1 doing A!")
        mediator?.notify(sender: self, event: "A")
    }
    func executeB() {
        print("Widget 1 doing B!")
        mediator?.notify(sender: self, event: "B")
    }
}

class Widget2: BaseWidget {
    func executeC() {
        print("Widget 2 doing C!")
        mediator?.notify(sender: self, event: "C")
    }
    func executeD() {
        print("Widget 2 doing D!")
        mediator?.notify(sender: self, event: "D")
    }
}


let widget1 = Widget1()
let widget2 = Widget2()

let mediator = SomeMediator(widget1, widget2)
widget1.executeA()
widget2.executeD()

This pattern is useful if you don’t want your objects directly communicating with each other. You should consider the alternative of direct communication before using a mediator design pattern.

Command

The command design pattern is that turns an action or request into an encapsulated object, which contains all the information required for the request.

For example, think about saving a document in a word processor.

There are multiple ways you could save the document, you could use the keyboard shortcut CMD + S or go to File > Save.

Now both the menu and the keyboard shortcut could save the document themselves, but this would duplicate logic.

It’s better to create an object that’s the SaveCommand which can be used by different objects.

Now when you want to implement an auto-save feature, maybe the document saves automatically every 5 minutes, you can just use the SaveCommand with a timer.

Template

The template design pattern defines a skeleton of a task or algorithm in the superclass but allows subclasses to override certain parts of the algorithm.

You should use the templating design pattern when you want to control at which point subclassing/overriding is allowed.

For example, maybe you have a document parser that can read files and save it to the database. Certain parts of the reading action are the same.

Checking the created date, the modified date and the name of the file. These can all be extracted and save to the data base with the template function.

However, for different file types, say for word documents vs CSVs or PDFs, you’ll need a different method to read the content of the files.

This is where the subclasses come in to override the parsing function.

Strategy

The strategy design pattern is when you define a group of algorithms, each in a separate class and then choose which one you want to use at run-time.

A good example of this is pricing strategies. Say maybe at happy hour everything is 50% off. And maybe on Tuesday’s all Wings are 50 cents each.

Perhaps Thursday seniors get a drink for free.

Each of these pricing strategies can be determined at run time and the bill can be printed correctly.

Other Design Patterns

There are a couple of other design patterns such as MVVM & MVC. Those design patterns are also great to learn and deserve their own article.

If you liked this post and want to learn more, check out The Complete iOS Developer Bootcamp. Speed up your learning curve - hundreds of students have already joined. Thanks for reading!

Eddy Chung

I teach iOS development on ZeroToAppStore.com.

Similar Posts