[2019] Design Patterns by Tutorials - Learning Design Patterns in Swift, 3rd Edition
[2019] Design Patterns by Tutorials - Learning Design Patterns in Swift, 3rd Edition
Notice of Rights
All rights reserved. No part of this book or corresponding materials (such as text,
images, or source code) may be reproduced or distributed by any means without prior
written permission of the copyright owner.
Notice of Liability
This book and all corresponding materials (such as source code) are provided on an
“as is” basis, without warranty of any kind, express of implied, including but not
limited to the warranties of merchantability, fitness for a particular purpose, and
noninfringement. In no event shall the authors or copyright holders be liable for any
claim, damages or other liability, whether in action of contract, tort or otherwise,
arising from, out of or in connection with the software or the use of other dealing in
the software.
Trademarks
All trademarks and registered trademarks appearing in this book are the property of
their own respective owners.
raywenderlich.com 2
Design Patterns by Tutorials
Dedications
"For my girls. I love you very much."
— Joshua Greene
— Jay Strawn
raywenderlich.com 3
Design Patterns by Tutorials
raywenderlich.com 4
Design Patterns by Tutorials
Aaron Douglas is a tech editor for this book. He was that kid
taking apart the mechanical and electrical appliances at five years
of age to see how they worked. He never grew out of that core
interest - to know how things work. He took an early interest in
computer programming, figuring out how to get past security to be
able to play games on his dad's computer. He's still that feisty nerd,
but at least now he gets paid to do it. Aaron works for Automattic
(WordPress.com, WooCommerce, SimpleNote) as a Mobile Maker/
Lead primarily on the WooCommerce mobile apps. Find Aaron on
Twitter as @astralbodies or at his blog at aaron.blog.
raywenderlich.com 5
Design Patterns by Tutorials
raywenderlich.com 6
Design Patterns by Tutorials
raywenderlich.com 7
Design Patterns by Tutorials
raywenderlich.com 8
Design Patterns by Tutorials
raywenderlich.com 9
Design Patterns by Tutorials
raywenderlich.com 10
Design Patterns by Tutorials
raywenderlich.com 11
Design Patterns by Tutorials
raywenderlich.com 12
Design Patterns by Tutorials
raywenderlich.com 13
L Book License
• You are allowed to use and/or modify the source code in Design Patterns by
Tutorials in as many apps as you want, with no attribution required.
• You are allowed to use and/or modify all art, images and designs that are included
in Design Patterns by Tutorials in as many apps as you want, but must include this
attribution line somewhere inside your app: “Artwork/images/designs: from Design
Patterns by Tutorials, available at www.raywenderlich.com”.
• The source code included in Design Patterns by Tutorials is for your personal use
only. You are NOT allowed to distribute or sell the source code in Design Patterns
by Tutorials without prior authorization.
• This book is for your personal use only. You are NOT allowed to sell this book
without prior authorization, or distribute it to friends, coworkers or students; they
would need to purchase their own copies.
All materials provided with this book are provided on an “as is” basis, without
warranty of any kind, express or implied, including but not limited to the warranties
of merchantability, fitness for a particular purpose and noninfringement. In no event
shall the authors or copyright holders be liable for any claim, damages or other
liability, whether in an action or contract, tort or otherwise, arising from, out of or in
connection with the software or the use or other dealings in the software.
All trademarks and registered trademarks appearing in this guide are the properties
of their respective owners.
raywenderlich.com 14
B Book Source Code &
Forums
• https://store.raywenderlich.com/products/design-patterns-by-tutorials-source-
code
Forums
We’ve also set up an official forum for the book at forums.raywenderlich.com. This is
a great place to ask questions about the book or to submit any errors you may find.
raywenderlich.com 15
Design Patterns by Tutorials Book Source Code & Forums
Buying the digital edition version of the book also has a few extra benefits: free
updates each time we update the book, access to older versions of the book, and you
can download the digital editions from anywhere, at anytime.
• hhttps://store.raywenderlich.com/products/design-patterns-by-tutorials.
And if you purchased the print version of this book, you’re eligible to upgrade to the
digital editions at a significant discount! Simply email support@razeware.com with
your receipt for the physical copy and we’ll get you set up with the discounted digital
edition version of the book.
raywenderlich.com 16
A About the Cover
Coral reefs contain some of the most amazing, colorful and diverse ecosystems on
Earth. Although coral reefs make up just a tiny fragment of the ocean’s underwater
area, they support over 25% of known marine life. It’s rather difficult to
underestimate the value that coral reefs add to the diversity and sustainability of our
oceans.
Although reefs are highly structured, they have many variants and perform a variety
of functions. More than just pretty “rocks”, coral reefs are truly the foundation of
their surrounding ecosystems. In that way, you could consider them the “design
patterns” of the ocean!
Unfortunately, coral reefs are in dramatic decline around the world. Potentially 90%
of known coral reefs may be in serious danger in as little as ten years. Various
organizations are actively working to find ways to mitigate the issues caused from
pollution, overfishing and physical damage done to reefs. For more information,
check out the following great resources:
• https://en.wikipedia.org/wiki/Coral_reef_protection
• https://coral.org/
raywenderlich.com 17
I Introduction
Design Patterns: Elements of Reusable, Object-Oriented Software, the first book to ever
describe design patterns, inspired the revolutionary idea of reusable, template
solutions to common software development problems. Design patterns aren’t specific
to a particular situation, but rather, they are solutions you can adapt and use in
countless projects.
Why should software design be hard? We’ve done everything we can to make it easy
and understandable, so anyone can learn it.
2. Make this book useful for both beginning and advanced developers.
We think we’ve done it! The only requirements for reading this book are a basic
understanding of Swift and iOS development.
If you’ve worked through our classic beginner books — the Swift Apprentice https://
store.raywenderlich.com/products/swift-apprentice and the iOS Apprentice https://
store.raywenderlich.com/products/ios-apprentice — or have similar development
experience, you’re ready to read this book.
raywenderlich.com 18
Design Patterns by Tutorials Introduction
And if you’re an advanced developer, we also have a lot of great advanced design
patterns for you as well!
As you work through this book, you’ll progress from beginning topics to more
advanced concepts.
You’ll also learn how to read and use class diagrams in this section. This will make it
much easier for you to learn design patterns, so it’s important to go over this first to
get the most out of the book.
These patterns work well in combinations, so all of the chapters in this section walk
you through building a single tutorial project from the ground up.
Many of these patterns work well together, but not all. You’ll create two projects in
this section as you explore these intermediate patterns.
raywenderlich.com 19
Design Patterns by Tutorials Introduction
Chapter structure
Each design pattern chapter in Sections II through IV follow a similar structure:
• What is it?
This section gives a class diagram and explains the design pattern.
This section describes the design pattern’s strengths and provides examples where
the design pattern works well.
• Playground example
This section shows you how to use the design pattern within a playground
example. This isn’t meant to be a complete project, but rather, it’s a standalone
example to teach you the basics of the design pattern.
• Tutorial project
This section guides you through using the design pattern in a tutorial app.
• Key points
This section provides a summary of what you learned and key points to remember
for the chapter.
raywenderlich.com 20
Section I: Hello, Design
Patterns!
This is a high-level introduction to what design patterns are, why they're important,
and how they will help you.
You'll also learn how to read and use class diagrams in this section. This will make it
much easier for you to learn design patterns, so it’s important to go over this first to
get the most out of the book.
raywenderlich.com 21
1 Chapter 1: What are
Design Patterns?
By Joshua Greene
“Feared by newcomers. Loved by architects. Read the inside story about design
patterns. The truth may surprise you!”
Did you know design patterns can make you a better developer? “Of course,” you say
— you are reading this book, after all!
Did you know design patterns can help you make more money? It’s true. You can save
time, work less and ultimately create more great things by using design patterns
correctly.
And did you know design patterns can help you fight vampires? OK, maybe not —
design patterns aren’t silver bullets, after all.
raywenderlich.com 22
Design Patterns by Tutorials Chapter 1: What are Design Patterns?
However, design patterns are incredibly useful, no matter what language or platform
you develop for, and every developer should absolutely know about them. They
should also know how and when to apply them. That's what you're going to learn in
this book!
A real-world example
The introduction told you that design patterns are reusable, template solutions to
common development problems. Design patterns aren’t concrete implementations,
but rather, serve as starting points for writing code. They describe generic solutions
to problems that experienced developers have encountered many times before.
What does this mean exactly...? Consider this non-development, real-world scenario:
You’re the proud owner of a gardening company, and your business is really, er,
blooming. You’ve only done a few small projects up to now - a tree planted here and
a few flowers there. However, you just landed a big client who wants several dozen
trees and flowers planted on their property.
Your standard procedure has been for your employees to carry each flower or tree
sapling into place individually. Once each has been temporarily placed, your
customer inspects and approves the arrangement before you plant everything in the
ground.
You’re worried it’s going to take forever to carry each flower and tree into place for
this large project. And you even need a few people to carry some of the bigger trees.
While you could hire lots of temporary employees, you wouldn’t make a profit on the
job. There’s got to be a better way!
You decide to ask other gardeners what they do, and you find out they use
wheelbarrows and carts. What a great idea! You tell your employees to use a cart to
move multiple flowers at the same time and a wheelbarrow to move the heavy trees.
In the meantime, you use a lounge chair chair to watch your workers go to it... isn’t
management great?
So now you know all about design patterns! Wait, you need more details? Okay, let’s
break it down...
raywenderlich.com 23
Design Patterns by Tutorials Chapter 1: What are Design Patterns?
Example explanation
The “design pattern” here is the use of wheelbarrows and carts. These are common,
best practice tools in gardening. Similarly, software design patterns form a set of best
practices in development. You could have chosen not to use wheelbarrows and carts,
but akin to avoiding software design patterns, you assume more risk by making the
project more time- and labor-intensive.
Back to the point of “asking other gardeners what they do.” Most design patterns
have been around for a long time — having started life in the 1970s and 1980s — and
they continue to work well to this day.
This longevity is partly due to the fact their use has been validated in many projects
over the decades, but it’s also because they aren’t concrete solutions.
In the gardening scenario, you decided that carts will be used to move flowers and
wheelbarrows will be used to move trees. These are implementation details: you could
have used carts to move both flowers and trees, only used wheelbarrows, or any other
combination that made the job easier.
Design patterns are generic, go-to solutions for solving common problems, like using
wheelbarrows and carts. They are starting points for concrete implementations, like
using carts for flowers and wheelbarrows for trees.
Make sense? Great! It's now time to leave the garden behind and head back to the
world of software design patterns.
1. Structural design pattern: Describes how objects are composed and combined
to form larger structures. Examples of structural design patterns include Model-
View-Controller (MVC), Model-View-ViewModel (MVVM) and Facade.
raywenderlich.com 24
Design Patterns by Tutorials Chapter 1: What are Design Patterns?
You may be wondering if knowing a design pattern’s type really matters. Well,
yes...and no.
It’s not useful to memorize all patterns by type. Most developers don’t do this.
However, if you’re not sure whether a particular pattern will work, it’s sometimes
useful to consider other patterns of the same type. You just might find one that
works better for your particular problem.
If you'd like to learn more about iOS architectural patterns, check out
Advanced iOS App Architecture (http://bit.ly/ios-app-arch).
raywenderlich.com 25
Design Patterns by Tutorials Chapter 1: What are Design Patterns?
However, if you already know a design pattern works well for a particular problem,
why should you reinvent the solution from scratch?
But, but...check out this thread on Twitter, which definitely shows that
design patterns are worthless!
Regardless of the particular criticism, design patterns have been around for a long
time, and they’ve been used in many apps. So at some point, you’re going to
encounter them.
We think it’s best to have an understanding of what they are before you run into
them, instead of trying to wing it on the fly, which in our experience is usually late
on a Sunday night, the day before the release deadline, right after discovering a
critical bug.
raywenderlich.com 26
Design Patterns by Tutorials Chapter 1: What are Design Patterns?
raywenderlich.com 27
Design Patterns by Tutorials Chapter 1: What are Design Patterns?
Key points
In this chapter, you learned what design patterns are and why you should care about
them. Here are the key points to remember:
• Design patterns aren't concrete implementations, but rather, they are a starting
point for writing code.
• Design patterns collectively form a set of best practices to help you write more
understandable and easier-to-maintain code.
• There are three main types of design patterns: structural, behavioral and
creational.
• There are both criticisms and benefits of design patterns. Ultimately, they are
commonplace in software development, and you're likely to encounter them.
Therefore, having a good grasp of them is important.
raywenderlich.com 28
2 Chapter 2: How to Read a
Class Diagram
By Joshua Greene
So now you know what design patterns are! In this chapter, you’re going to learn
about a fundamental concept to help you understand design patterns: the class
diagram.
Class diagrams are like engineering blueprints; they provide information about a
system through the medium of pictures, symbols and annotations.
You may have heard of Unified Modeling Language (UML), which is a standard
language for creating class diagrams, architectural drawings and other system
illustrations. A complete discussion of UML is beyond the scope of this book, but you
won’t need to understand a lot of UML in your day-to-day iOS development. Instead,
you’ll learn a subset of UML in this chapter that’s useful for creating class diagrams
and describing design patterns.
A box denotes a class. Here’s a very simple class diagram for a Dog class:
raywenderlich.com 29
Design Patterns by Tutorials Chapter 2: How to Read a Class Diagram
To indicate that one class inherits from another, use an open arrowhead:
But instead of reading this as “inherits from,” read this as “is a”. For example, to
show that SheepDog inherits from Dog, you’d draw the following diagram:
Class diagrams can be written from bottom to top, from left to right, or in any other
orientation you’d like. Regardless of the orientation, the direction of the arrows
define the meaning: Inheritance arrows always point at the superclass, and property
arrows always point at the property class.
raywenderlich.com 30
Design Patterns by Tutorials Chapter 2: How to Read a Class Diagram
You should read a property arrow as “has a.” For example, if a Farmer has a Dog,
you’d draw this:
You should always use the singular form of the class name in class diagrams, even if
you’re conveying a one-to-many relationship. In this case, you should write Dog, not
Dogs.
raywenderlich.com 31
Design Patterns by Tutorials Chapter 2: How to Read a Class Diagram
You can use as many arrows and boxes as you need in a single class diagram. For
example, here’s how you’d denote a Farmer has a SheepDog that is a Dog:
You also use a box to indicate a protocol. In order to distinguish it from a class,
however, you need to write <<protocol>> before its name.
Use an open arrowhead with a dashed line to indicate a class implements a protocol:
raywenderlich.com 32
Design Patterns by Tutorials Chapter 2: How to Read a Class Diagram
You may either read this as “implements” or “conforms to.” For example, you’d
indicate Farmer conforms to PetOwning like this:
Use a plain arrowhead with a dashed line to indicate “uses,” which is called a
“dependency” in UML terms:
UML is intentionally vague about what a dependency is. Consequently, whenever you
use a dependency arrow, you usually should annotate its purpose. For example, you
can use a dependency arrow to indicate the following things:
• An object that’s passed into a method as a parameter, but not held as a property.
raywenderlich.com 33
Design Patterns by Tutorials Chapter 2: How to Read a Class Diagram
You can also denote properties and methods in a class diagram. For example, you’d
indicate PetOwning has a name property and a petNeedsFood(_:) method like this:
If an arrow’s meaning is obvious, you can omit any explanatory text. You can
generally omit explanations for inheritance, properties and implements arrows.
However, you should usually keep text for “uses” arrows, as their meaning isn’t
always obvious.
Here’s the complete class diagram for a Farmer that has a SheepDog, which is a Dog
that delegates to a PetOwning object:
raywenderlich.com 34
Design Patterns by Tutorials Chapter 2: How to Read a Class Diagram
Challenges
Now that you’ve got the basics down, it’s time to test your knowledge!
On a piece of paper, draw class diagrams for each of the following challenges. When
you’re ready, check the next page for answers:
1. Dog and Cat inherit from Animal, which defines an eat method.
2. Vehicle protocol has one Motor and one or more Wheel objects.
There are many correct solutions to each of these challenges. For example, you don’t
have to draw the diagram from top to bottom. Instead, you can draw it from left to
right or another orientation. As long as your class diagram clearly conveys the
intended meaning, it’s correct!
raywenderlich.com 35
Design Patterns by Tutorials Chapter 2: How to Read a Class Diagram
Solution 1. You need three boxes: one for Cat, Dog and Animal. You need an open
arrowhead from Cat to Animal and another open arrowhead from Dog to Animal. You
should also indicate eat() on Animal.
Solution 2. You should have three boxes: one for <<protocol>> Vehicle, Motor
and Wheel. You should have a plain arrowhead from Vehicle to Motor and another
plain arrowhead from Vehicle to Wheel. You should also have 1 ... * next to the
arrowhead pointing at Wheel.
Solution 3. The wording for this problem was intentionally ambiguous. We could
have meant that either Teacher conforms to Person, or Professor conforms to
Person. Thereby, Professor would conform to Person either directly or indirectly
through Teacher.
raywenderlich.com 36
Design Patterns by Tutorials Chapter 2: How to Read a Class Diagram
If Teacher conforms to Person and Professor inherits from Teacher, the class
diagram looks like this:
If Professor conforms to Person, but Teacher does not, the class diagram looks like
this:
raywenderlich.com 37
Design Patterns by Tutorials Chapter 2: How to Read a Class Diagram
Key points
You learned the basics of class diagrams in this chapter. This is all you’ll need to
understand the diagrams in the rest of this book. You can always refer back to this
chapter if you need to do so!
• Class diagrams give a visual representation of class and protocol types, showing
their properties and methods.
• Class diagrams also show the relationship between the object types.
• Class diagrams can be drawn in any other orientation; the direction of the arrows
define the meaning.
• Boxes denote classes, and lines denote relationships: “implements,” “has a,” “uses“
and “conforms to” are the most common relations.
• Boxes can also denote protocols, which is indicated by <<protocol>> before the
name.
raywenderlich.com 38
Section II: Fundamental
Design Patterns
This section covers essential iOS design patterns. These patterns are frequently used
throughout iOS development, and every iOS developer should understand these well.
These patterns work well in combinations, so all of the chapters in this section walk
you through building a single tutorial project from the ground up.
raywenderlich.com 39
3 Chapter 3: Model-View-
Controller Pattern
By Joshua Greene
The model-view-controller (MVC) pattern separates objects into three distinct types.
Yep, you guessed it: the three types are models, views and controllers!
• Models hold application data. They are usually structs or simple classes.
• Views display visual elements and controls on screen. They are usually subclasses
of UIView.
• Controllers coordinate between models and views. They are usually subclasses of
UIViewController.
MVC is very common in iOS programming because it's the design pattern that Apple
chose to adopt in UIKit.
raywenderlich.com 40
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Controllers are allowed to have strong properties for their model and view so they
can be accessed directly. Controllers may also have more than one model and/or
view.
Conversely, models and views should not hold a strong reference to their owning
controller. This would cause a retain cycle.
Instead, models communicate to their controller via property observing, which you’ll
learn in depth in a later chapter, and views communicate to their controller via
IBActions.
This lets you reuse models and views between several controllers. Win!
Note: Views may have a weak reference to their owning controller through a
delegate (see Chapter 4, “Delegation Pattern”). For example, a UITableView
may hold a weak reference to its owning view controller for its delegate and/
or dataSource references. However, the table view doesn't know these are set
to its owning controller - they just happen to be.
Controllers are much harder to reuse since their logic is often very specific to
whatever task they are doing. Consequently, MVC doesn’t try to reuse them.
In nearly every app, you’ll likely need additional patterns besides MVC, but it’s okay
to introduce more patterns as your app requires them.
Playground example
Open FundamentalDesignPatterns.xcworkspace in the Starter directory. This is a
collection of playground pages, one for each fundamental design pattern you’ll learn.
By the end of this section, you’ll have a nice design patterns reference!
raywenderlich.com 41
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
MVC is a structural pattern because it’s all about composing objects as models, views
or controllers.
Next, open the Model-View-Controller page from the File hierarchy. For the Code
Example, you'll create an "Address Screen" using MVC.Can you guess what the three
parts of an Address Screen would be? A model, view and controller, of course! Add
this code after Code Example to create the model:
import UIKit
// MARK: - Address
public struct Address {
public var street: String
public var city: String
public var state: String
public var zipCode: String
}
// MARK: - AddressView
public final class AddressView: UIView {
@IBOutlet public var streetTextField: UITextField!
@IBOutlet public var cityTextField: UITextField!
@IBOutlet public var stateTextField: UITextField!
@IBOutlet public var zipCodeTextField: UITextField!
}
raywenderlich.com 42
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
In an actual iOS app instead of a playground, you’d also create a xib or storyboard
for this view and connect the IBOutlet properties to its subviews. You’ll practice
doing this later in the tutorial project for this chapter.
Lastly, you need to create the AddressViewController. Add this code next:
// MARK: - AddressViewController
public final class AddressViewController: UIViewController {
// MARK: - Properties
public var address: Address?
public var addressView: AddressView! {
guard isViewLoaded else { return nil }
return (view as! AddressView)
}
}
Here you have the controller holding a strong reference to the view and model that
it owns.
In an actual iOS app, you’d also need to specify the view’s class on the storyboard
or xib, to ensure the app correctly creates an AddressView instead of the default
UIView.
Recall that it’s the controller’s responsibility to coordinate between the model and
view. In this case, the controller should update its addressView using the values
from the address.
A good place to do this is whenever viewDidLoad is called. Add the following to the
end of the AddressViewController class:
raywenderlich.com 43
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
addressView.zipCodeTextField.text = address.zipCode
}
This is an example of how the model can tell the controller that something has
changed and that the views need updating.
What if you also want to allow the user to update the address from the view? That’s
right — you’d create an IBAction on the controller.
// MARK: - Actions
@IBAction public func updateAddressFromView(
_ sender: AnyObject) {
Finally, this is an example of how the view can tell the controller that something
has changed, and the model needs updating. In an actual iOS app, you’d also need to
connect this IBAction from a subview of AddressView, such as a valueChanged
event on a UITextField or touchUpInside event on a UIButton.
raywenderlich.com 44
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
All in all, this gives you a simple example for how the MVC pattern works. You've
seen how the controller owns the models and the views, and how each can interact
with each other, but always through the controller.
This can result in view controllers getting very big! There's a rather quaint term for
when this happens, called "Massive View Controller."
To solve this issue, you should introduce other design patterns as your app requires
them.
Tutorial project
Throughout this section, you’ll create a tutorial app called Rabble Wabble.
You’ll be creating the project from scratch, so open Xcode and select File ▸ New ▸
Project. Then select iOS ▸ Single View App, and press Next.
Enter RabbleWabble for the Product Name; select your Team or leave as None if
you don’t have one set up (it’s not required if you only use the simulator); set your
Organization Name and Organization Identifier to whatever you’d like; verify
Language is set to Swift; uncheck Use SwiftUI, Use Core Data, Include Unit Tests
and Include UI Tests; and click Next to continue.
raywenderlich.com 45
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Open ViewController.swift from the File hierarchy, and delete all of the boilerplate
code inside the curly braces. Then right-click on ViewController and select
Refactor ▸ Rename....
Type QuestionViewController as the new name, and press Enter to make the
change. Then, add the keyword public before class QuestionViewController like
this:
Throughout this book, you'll use public for types, properties and methods that
should be publicly accessible to other types; you'll use private if something should
only be accessible to the type itself; and you'll use internal if it should be accessible
to subclasses or related types but isn't intended for general use otherwise. This is
known as access control.
This is a "best practice" in iOS development. If you ever move these files into a
separate module, to create a shared library or framework for example, you'll find it
much easier to do if you follow this best practice.
Next, select the yellow RabbleWabble group in the File hierarchy, and press
Command + Option + N together to create a new group.
Select the new group and press Enter to edit its name. Input AppDelegate, and
press Enter again to confirm.
raywenderlich.com 46
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Repeat this process to create new groups for Controllers, Models, Resources and
Views.
Lastly, right-click on the yellow RabbleWabble group and select Sort by Name.
Are you curious about SceneDelegate? This is a new class that was introduced
in iOS 13. It's intended to allow multiple "scenes" for an app to coexist, even
supporting multiple windows running simultaneously for your app. This is
especially useful on larger screens, such as an iPad.
You won't use SceneDelegate for this project. If you'd like to learn more about
it, check out our book SwiftUI by Tutorials (http://bit.ly/swiftui-by-tutorials).
Since you moved Info.plist, you need to tell Xcode where its new location is. To do
so, select the blue RabbleWabble project folder; select the RabbleWabble target;
select the Build Settings tab; enter Info.plist into the Search box; double-click the
line for Info.plist under the Packaging section; and replace its text with the
following:
RabbleWabble/Resources/Info.plist
raywenderlich.com 47
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
This is a great start to using the MVC pattern! By simply grouping your files this way,
you’re telling other developers your project uses MVC. Clarity is good!
First, you need to create a Question model. Select the Models group in the File
hierarchy and press Command + N to create a new file. Select Swift File from the
list and click Next. Name the file Question.swift and click Create. Replace the entire
contents of Question.swift with the following:
import Foundation
You also need another model to act as a container for a group of questions.
Create another file in the Models group named QuestionGroup.swift, and replace
its entire contents with the following:
import Foundation
raywenderlich.com 48
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Next, you need to add the data for the QuestionGroups. This could amount to a lot of
retyping, so I’ve provided a file that you can simply drag and drop into the project.
Open Finder and navigate to where you have the projects downloaded for this
chapter. Alongside the Starter and Final directories, you’ll see a Resources directory
that contains QuestionGroupData.swift, Assets.xcassets and
LaunchScreen.storyboard.
Position the Finder window above Xcode and drag and drop
QuestionGroupData.swift into the Models group like this:
When prompted, check the option for Copy items if needed and press Finish to add
the file.
Since you already have the Resources directory open, you should copy over the other
files as well. First, select the existing Assets.xcassets in the app under Resources
and press Delete to remove it. Choose Move to Trash when prompted. Then, drag
and drop the new Assets.xcassets from Finder into the app’s Resources group,
checking Copy items if needed when prompted.
Next, select the existing LaunchScreen.storyboard in the app under Views and
press Delete to remove it. Again, make sure you pick Move to Trash when prompted.
Then, drag and drop the new LaunchScreen.storyboard from Finder into the app’s
Resources group, checking Copy items if needed when prompted.
Open QuestionGroupData.swift, and you’ll see there are several static methods
defined for basic phrases, numbers, and more. This dataset is in Japanese, but you
can tweak it to another language if you prefer. You’ll be using these soon!
Open LaunchScreen.storyboard, and you’ll see a nice layout that will be shown
whenever the app is launched.
raywenderlich.com 49
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Build and run to check out the sweet app icon and launch screen!
import UIKit
Next, open Main.storyboard and scroll to the existing scene. Hold down the option
key and press the Object library button to open it and prevent it from closing. Enter
label into the search field, and drag and drop three labels onto the scene without
overlapping them.
Double-click on the top-most label and set its text as Prompt. Set the middle label’s
text as Hint, and set the bottom label’s text as Answer.
raywenderlich.com 50
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Select the Prompt label, then open the Utilities pane and select the Attributes
inspector tab. Set the label’s Font to System 50.0, set its Alignment to center and
Lines to 0.
Set the Hint label’s Font to System 24.0, Alignment to center and Lines to 0.
Set the Answer label’s Font to System 48.0, Alignment to center and Lines to 0.
If needed, resize the labels to prevent clipping, and rearrange them to remain in the
same order without overlapping.
Next, select the Prompt label, select the icon for Add New Constraints and do the
following:
raywenderlich.com 51
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Select the Hint label, select the icon for Add New Constraints and do the following:
raywenderlich.com 52
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Select the Answer label, select the icon for Add New Constraints and do the
following:
raywenderlich.com 53
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Next, press the Object library button, enter UIButton into the search field and drag
a new button into the bottom left corner of the view.
Open the Attributes Inspector, set the button’s image to ic_circle_x, and delete the
Button default title.
Drag another button into the bottom right corner of the view. Delete the Button
default title, and set its Image to ic_circle_check.
Drag a new label onto the scene. Position this right below the red X button and set
its text to 0. Open the Attributes Inspector and set the Color to match the red
circle. Set the Font to System 32.0, and set the Alignment to center. Resize this
label as necessary to prevent clipping.
Drag another label onto the scene, position it below the green check button and set
its text to 0. Open the Attributes Inspector and set the Color to match the green
circle. Set the Font to System 32.0, and set the Alignment to center. Resize this
label as necessary to prevent clipping.
You next need to set the constraints on the buttons and labels.
Select the red circle button, select the icon for Add New Constraints and do the
following:
raywenderlich.com 54
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Select the red-colored label, select the icon for Add New Constraints and do the
following:
raywenderlich.com 55
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Select both the red circle image view and the red-colored label together, select the
icon for Align and do the following:
Select the green circle image view, select the icon for Add New Constraints and
do the following:
raywenderlich.com 56
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Select the green-colored label, select the icon for Add New Constraints and do the
following:
Select both the green circle image view and the green-colored label together, select
the icon for Align and do the following:
raywenderlich.com 57
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
To complete the QuestionView setup, you need to set the view’s class on the scene
and connect the properties.
Click on the view on the scene, being careful not to select any subview instead, and
open the Identity Inspector. Set the Class as QuestionView.
raywenderlich.com 58
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Open the Connections Inspector and drag from each of the Outlets to the
appropriate subviews as shown:
raywenderlich.com 59
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
You hardcode the questionGroup to basic phrases for now. In a future chapter you
will expand the app so that the user will be able to select the question group from a
listing.
The questionIndex is the index of the current question displayed. You’ll increment
this as the user goes through questions.
The correctCount is the count of correct responses. The user indicates a correct
response by pressing the green check button.
Likewise, the incorrectCount is the count of incorrect responses, which the user
will indicate by pressing the red X button.
You next need to add code to actually show a Question. Add the following right after
the properties you just added:
questionView.answerLabel.text = question.answer
questionView.promptLabel.text = question.prompt
questionView.hintLabel.text = question.hint
questionView.answerLabel.isHidden = true
questionView.hintLabel.isHidden = true
}
Notice here how you're writing code in the controller to manipulate the views based
on the data in the models. MVC FTW!
Right now, there isn’t any way to see the answer. You should probably fix this.
// MARK: - Actions
@IBAction func toggleAnswerLabels(_ sender: Any) {
raywenderlich.com 60
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
questionView.answerLabel.isHidden =
!questionView.answerLabel.isHidden
questionView.hintLabel.isHidden =
!questionView.hintLabel.isHidden
}
This will toggle whether the hint and answer labels are hidden. You set the answer
and hint labels to hidden in showQuestion() to reset the state each time a new
question is shown.
This is an example of a view notifying its controller about an action that has
happened. In response, the controller executes code for handling the action.
You also need to hook up this action on the view. Open Main.storyboard and press
the Object library button.
Enter tap into the search field, and drag and drop a Tap Gesture Recognizer onto
the view.
Make sure that you drag this onto the base view, and not one of the labels or buttons!
Control-drag from the Tap Gesture Recognizer object to the Question View
Controller object on the scene, and then select toggleAnswerLabels:.
Build and run and try tapping on the view to show/hide the answer and hint labels.
Next, you need to handle the case whenever the buttons are pressed.
raywenderlich.com 61
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Open QuestionViewController.swift and add the following at the end of the class:
// 1
@IBAction func handleCorrect(_ sender: Any) {
correctCount += 1
questionView.correctCountLabel.text = "\(correctCount)"
showNextQuestion()
}
// 2
@IBAction func handleIncorrect(_ sender: Any) {
incorrectCount += 1
questionView.incorrectCountLabel.text = "\(incorrectCount)"
showNextQuestion()
}
// 3
private func showNextQuestion() {
questionIndex += 1
guard questionIndex < questionGroup.questions.count else {
// TODO: - Handle this...!
return
}
showQuestion()
}
You just defined three more actions. Here’s what each does:
1. handleCorrect(_:) will be called whenever the user presses the green circle
button to indicate they got the answer correct. Here, you increase the
correctCount and set the correctCountLabel text.
2. handleIncorrect(_:) will be called whenever the user presses the red circle
button to indicate they got the answer incorrect. Here, you increase the
incorrectCount and set the incorrectCountLabel text.
You’ll handle the case that there aren’t any more questions in the next chapter.
Lastly, you need to connect the buttons on the view to these actions.
raywenderlich.com 62
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
Open Main.storyboard, select the red circle button, then Control-drag onto the
QuestionViewController object and select handleIncorrect:.
Likewise, select the green circle button, then Control-drag onto the
QuestionViewController object and select handleCorrect:.
Once again these are examples of views notifying the controller that something
needs to be handled. Build and run and try pressing each of the buttons.
Key points
You learned about the Model-View-Controller (MVC) pattern in this chapter. Here
are its key points:
• MVC separates objects into three categories: models, views and controllers.
• MVC promotes reusing models and views between controllers. Since controller
logic is often very specific, MVC doesn't usually reuse controllers.
• The controller is responsible for coordinating between the model and view: it sets
model values onto the view, and it handles IBAction calls from the view.
raywenderlich.com 63
Design Patterns by Tutorials Chapter 3: Model-View-Controller Pattern
• MVC is a good starting point, but it has limitations. Not every object will neatly fit
into the category of model, view or controller. You should use other patterns as
needed along with MVC.
You’ve gotten Rabble Wabble off to a great start! However, there’s still a lot of
functionality you need to add: letting the user pick question groups, handling what
happens when there aren’t any questions remaining and more!
Continue onto the next chapter to learn about the delegation design pattern and
continue building out Rabble Wabble.
raywenderlich.com 64
4 Chapter 4: Delegation
Pattern
By Joshua Greene
The delegation pattern enables an object to use another “helper” object to provide
data or perform a task rather than do the task itself. This pattern has three parts:
• An object needing a delegate, also known as the delegating object. It’s the
object that has a delegate. The delegate is usually held as a weak property to avoid
a retain cycle where the delegating object retains the delegate, which retains the
delegating object.
• A delegate, which is the helper object that implements the delegate protocol.
raywenderlich.com 65
Design Patterns by Tutorials Chapter 4: Delegation Pattern
Why isn’t there just one protocol, instead of two, in Apple frameworks?
Apple frameworks commonly use the term DataSource to group delegate methods
that provide data. For example, UITableViewDataSource is expected to provide
UITableViewCells to display.
Apple frameworks typically use protocols named Delegate to group methods that
receive data or events. For example, UITableViewDelegate is notified whenever a
row is selected.
It’s common for the dataSource and delegate to be set to the same object, such as
the view controller that owns a UITableView. However, they don’t have to be, and it
can be very beneficial at times to have them set to different objects.
Playground example
Let’s take a look at some code!
For the code example, you’ll create a MenuViewController that has a tableView and
acts as both the UITableViewDataSource and UITableViewDelegate.
First, create the MenuViewController class by adding the following code directly
after Code Example, ignoring any compiler errors for the moment:
import UIKit
// 1
@IBOutlet public var tableView: UITableView! {
didSet {
tableView.dataSource = self
tableView.delegate = self
}
}
// 2
private let items = ["Item 1", "Item 2", "Item 3"]
}
raywenderlich.com 66
Design Patterns by Tutorials Chapter 4: Delegation Pattern
1. In a real app, you’d also need to set the @IBOutlet for the tableView within
Interface Builder, or create the table view in code. You can optionally also set the
tableView.delegate and tableView.dataSource directly in Interface Builder,
or you can do this in code as shown here.
2. The items will be used as the menu titles displayed on the table view.
// MARK: - UITableViewDataSource
extension MenuViewController: UITableViewDataSource {
// MARK: - UITableViewDelegate
extension MenuViewController: UITableViewDelegate {
It’s easy to create your own delegates too. For example, you can create a delegate to
be notified whenever a user selects a menu item.
raywenderlich.com 67
Design Patterns by Tutorials Chapter 4: Delegation Pattern
Next, add the following property right above @IBOutlet var tableView:
The common convention in iOS is to set delegate objects after an object is created.
This is exactly what you do here: after MenuViewController is created (however this
may happen in the app), it expects that its delegate property will be set.
Lastly, you need to actually inform this delegate whenever the user selects an item.
delegate?.menuViewController(self,
didSelectItemAtIndex: indexPath.row)
It’s common convention to pass the delegating object, which in this case is the
MenuViewController, to each of its delegate method calls. This way, the delegate
can use or inspect the caller if needed.
So now you have created your own delegate protocol, to which the
MenuViewController delegates when an item in the list is selected. In a real app,
this would handle what to do when the item is selected, such as moving to a new
screen.
Easy, right?
If an object needs several delegates, this may be an indicator that it’s doing too
much. Consider breaking up the object’s functionality for specific use cases, instead
of one catch-all class.
raywenderlich.com 68
Design Patterns by Tutorials Chapter 4: Delegation Pattern
It’s hard to put a number on how many is too many; there’s no golden rule. However,
if you find yourself constantly switching between classes to understand what’s
happening, then that’s a sign you have too many. Similarly, if you cannot understand
why a certain delegate is useful, then that’s a sign it’s too small, and you’ve split
things up too much.
You should also be careful about creating retain cycles. Most often, delegate
properties should be weak. If an object must absolutely have a delegate set, consider
adding the delegate as an input to the object’s initializer and marking its type as
forced unwrapped using ! instead of optional via ?. This will force consumers to set
the delegate before using the object.
If you find yourself tempted to create a strong delegate, another design pattern may
be better suited for your use case. For example, you might consider using the strategy
pattern instead. See Chapter 5 for more details.
Tutorial project
The playground example has given you a small taste for what it looks like to
implement the delegation pattern. It’s now time to take that theory and make use of
it in an app. You’ll continue the RabbleWabble app from the previous chapter, and
add a menu controller to select the group of questions.
If you skipped the previous chapter, or you want a fresh start, open Finder and
navigate to where you downloaded the resources for this chapter, and then open
starter\RabbleWabble\RabbleWabble.xcodeproj in Xcode.
Instead of just showing the basic phrases questions, you’ll create a new view
controller to let the user select from a list of question group options.
In the File hierarchy, right-click on Controllers and select New File. Select the iOS
tab, pick Swift File from the list, and click Next. Enter
SelectQuestionGroupViewController.swift for the file name and click Create.
import UIKit
// MARK: - Outlets
raywenderlich.com 69
Design Patterns by Tutorials Chapter 4: Delegation Pattern
// MARK: - Properties
public let questionGroups = QuestionGroup.allGroups()
private var selectedQuestionGroup: QuestionGroup!
}
You’ll use the tableView to display a list of question groups. Whenever the
tableView is set, you set tableView.tableFooterView to a blank UIView. This trick
is to prevent the table view from drawing unnecessary empty table view cells, which
it does by default after all the other cells are drawn.
// MARK: - UITableViewDataSource
extension SelectQuestionGroupViewController:
UITableViewDataSource {
raywenderlich.com 70
Design Patterns by Tutorials Chapter 4: Delegation Pattern
Select the iOS tab, pick Swift File from the list, and click Next. Enter
QuestionGroupCell.swift for the file name and click Create.
import UIKit
You’ll create this view and connect the outlets soon, but for now, open
SelectQuestionGroupViewController.swift again.
Build and run to make sure you don’t have any compiler warnings. You shouldn’t
see anything different just yet, however, as you haven’t actually added
SelectQuestionGroupViewController to the app. You’ll do this next.
raywenderlich.com 71
Design Patterns by Tutorials Chapter 4: Delegation Pattern
Hold the Option key to prevent the window from closing and drag and drop a new
View Controller to the left of the existing scene.
Next, enter UITableView into the search field on the Object library window, and
drag and drop a new Table View onto the new view controller.
raywenderlich.com 72
Design Patterns by Tutorials Chapter 4: Delegation Pattern
Select the table view, then select the Add New Constraints icon, and do the
following:
Enter UITableViewCell into the search field on the Object library window, and
drag and drop a Table View Cell onto the table view.
Lastly, enter label into the search field on the Object library window, and drag two
new labels onto the table view cell. Then, press the red X on the Object library
window to close it.
Double-click the first label and set its text to Title. Position it to the far left of the
cell aligned with the top and left margins (it should show blue indicators).
Double-click the second label and set its text to 0%. Position it to the far right of the
cell aligned with the top and right margins.
raywenderlich.com 73
Design Patterns by Tutorials Chapter 4: Delegation Pattern
Select the Title label, then select the Add New Constraints icon, and do the
following:
raywenderlich.com 74
Design Patterns by Tutorials Chapter 4: Delegation Pattern
Select the 0% label, then select the Add New Constraints icon and do the following:
Lastly, select the Percent label, go to the Size Inspector, scroll down to Content
Hugging Priority and set Horizontal to 750.
raywenderlich.com 75
Design Patterns by Tutorials Chapter 4: Delegation Pattern
Great! You’ve got the views all set up. You next need to set the class identity, reuse
identifier and hookup IBOutlets.
Select the table view cell, go to the Identity Inspector and set the Class to
QuestionGroupCell.
With the cell still selected, switch to the Attributes Inspector and set the Identifier
to QuestionGroupCell.
Switch to the Connections Inspector, and drag the titleLabel outlet on to the Title
label and the percentageLabel on to the 0% label.
Next, select the yellow view controller object on the scene, go to the Identity
Inspector and set the Class to SelectQuestionGroupViewController.
raywenderlich.com 76
Design Patterns by Tutorials Chapter 4: Delegation Pattern
Next, select the table view in the scene, go to the Connections Inspector and drag
and drop both the dataSource and delegate outlets onto the yellow view
controller object.
raywenderlich.com 77
Design Patterns by Tutorials Chapter 4: Delegation Pattern
Build and run to see the question groups displayed on the table view. Sweet!
Tap on a cell, however, and the app does nothing. Your next job is to fix this.
raywenderlich.com 78
Design Patterns by Tutorials Chapter 4: Delegation Pattern
raywenderlich.com 79
Design Patterns by Tutorials Chapter 4: Delegation Pattern
To do so, select the QuestionGroupCell, then Control-drag and drop it onto the
QuestionViewController scene.
Select Show in the popup window that appears. This creates a segue that will be
triggered whenever the user taps a table view cell.
Build and run and try clicking on the first table view cell. Awesome; you can see
questions!
Press the back button and try clicking on the second cell. Oh wait... are those the
same questions?! Yes, they are indeed. You need to set the selected QuestionGroup
on the QuestionViewController. To do so, you’ll need to make
SelectQuestionGroupViewController conform to UITableViewDelegate to be
notified of taps on the table view.
// MARK: - UITableViewDelegate
extension SelectQuestionGroupViewController: UITableViewDelegate
{
// 1
public func tableView(_ tableView: UITableView,
willSelectRowAt indexPath: IndexPath)
-> IndexPath? {
selectedQuestionGroup = questionGroups[indexPath.row]
return indexPath
raywenderlich.com 80
Design Patterns by Tutorials Chapter 4: Delegation Pattern
// 2
public func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
// 3
public override func prepare(for segue: UIStoryboardSegue,
sender: Any?) {
guard let viewController = segue.destination
as? QuestionViewController else { return }
viewController.questionGroup = selectedQuestionGroup
}
}
1. This sets the selectedQuestionGroup to the one that was selected. You have to
do this here instead of in tableView(_:, didSelectRowAt:), because
didSelectRowAt: is triggered after the segue is performed. If you set
selectedQuestionGroup in didSelectRowAt: then the app would crash on the
line viewController.questionGroup = selectedQuestionGroup as
selectedQuestionGroup would still be nil.
Build and run and try selecting the first and then the second table view cells as you
did before to verify its working as expected.
Great job!
1. Wouldn’t it be nice to actually show the title of the question group on the
QuestionViewController? You bet it would!
raywenderlich.com 81
Design Patterns by Tutorials Chapter 4: Delegation Pattern
2. You also can’t see how many questions are remaining in the
QuestionViewController. It’d be great if this showed up!
4. Lastly, it’s common convention for a “presented” controller to notify its caller,
typically via a delegate, whenever “Cancel” is pressed. There’s no option to cancel
at the moment, but there is a back button. It’d be great to replace this with a
custom bar button item instead!
While it sounds like a bit of work, all of these are actually just a few lines of coding.
You can do it!
Build and run, and voila, the title shows on the navigation bar!
raywenderlich.com 82
Design Patterns by Tutorials Chapter 4: Delegation Pattern
To resolve the second issue, add the following right after the other properties:
Build and run and try clicking through the questions. Cool, right?
Addressing the last two issues is a bit trickier. You need to create a custom delegate
for them. Fortunately, this is also pretty easy to do.
raywenderlich.com 83
Design Patterns by Tutorials Chapter 4: Delegation Pattern
// 1
func questionViewController(
_ viewController: QuestionViewController,
didCancel questionGroup: QuestionGroup,
at questionIndex: Int)
// 2
func questionViewController(
_ viewController: QuestionViewController,
didComplete questionGroup: QuestionGroup)
}
You also need a property to hold onto the delegate. Add the following right
below // MARK: - Instance Properties:
viewController.delegate = self
To fix that, add the following extension to the end of the file:
// MARK: - QuestionViewControllerDelegate
extension SelectQuestionGroupViewController:
QuestionViewControllerDelegate {
raywenderlich.com 84
Design Patterns by Tutorials Chapter 4: Delegation Pattern
navigationController?.popToViewController(self,
animated: true)
}
navigationController?.popToViewController(self,
animated: true)
}
}
raywenderlich.com 85
Design Patterns by Tutorials Chapter 4: Delegation Pattern
Build and run to try out your new rockin’ cancel button!
delegate?.questionViewController(self,
didComplete: questionGroup)
Build and run and select the “Basic Phrases” cell, since this has only a few questions.
Press the red X or green check buttons until you reach the end, and check out how
the app now pops back to the SelectQuestionGroupViewController. Nice!
raywenderlich.com 86
Design Patterns by Tutorials Chapter 4: Delegation Pattern
Key points
You learned about the delegation pattern in this chapter, including how to use
Apple-provided delegates and how to create your own delegates as well. Here are the
key points you learned:
• The delegation pattern has three parts: an object needing a delegate, a delegate
protocol and a delegate.
• This pattern allows you to break up large classes and create generic, reusable
components.
RabbleWabble is starting to come along! However, there’s still a lot to do to make this
the next App Store success.
Continue onto the next chapter to learn about the strategy design pattern and
continue building out RabbleWabble.
raywenderlich.com 87
5 Chapter 5: Strategy
Pattern
By Joshua Greene
The strategy pattern defines a family of interchangeable objects that can be set or
switched at runtime. This pattern has three parts:
• The object using a strategy. This is most often a view controller when the pattern
is used in iOS app development, but it can technically be any kind of object that
needs interchangeable behavior.
• The strategy protocol defines methods that every strategy must implement.
raywenderlich.com 88
Design Patterns by Tutorials Chapter 5: Strategy Pattern
This pattern is similar to the delegation pattern: both patterns rely on a protocol
instead of concrete objects for increased flexibility. Consequently, any object that
implements the strategy protocol can be used as a strategy at runtime.
Delegates are often fixed at runtime. For example, the dataSource and delegate for
a UITableView can be set from Interface Builder, and it’s rare for these to change
during runtime.
Playground example
Open FundamentalDesignPatterns.xcworkspace in the Starter directory and then
open the Overview page.
You’ll see that Strategy is listed under Behavioral Patterns. This is because the
strategy pattern is about one object using another to do something.
For the code example, consider an app that uses several “movie rating services” such
as Rotten Tomatoes®, IMDb and Metacritic. Instead of writing code for each of these
services directly within a view controller, and likely having complex if-else
statements therein, you can use the strategy pattern to simplify things by creating a
protocol that defines a common API for every service.
First, you need to create a strategy protocol. Add the following right after Code
example:
import UIKit
// 2
func fetchRating(for movieTitle: String,
raywenderlich.com 89
Design Patterns by Tutorials Chapter 5: Strategy Pattern
1. You’ll use ratingServiceName to display which service provided the rating. For
example, this would return “Rotten Tomatoes.”
raywenderlich.com 90
Design Patterns by Tutorials Chapter 5: Strategy Pattern
For example, add the following code at the end of the file:
// MARK: - Properties
public var movieRatingClient: MovieRatingStrategy!
// MARK: - Outlets
@IBOutlet public var movieTitleTextField: UITextField!
@IBOutlet public var ratingServiceNameLabel: UILabel!
@IBOutlet public var ratingLabel: UILabel!
@IBOutlet public var reviewLabel: UILabel!
// MARK: - Actions
@IBAction public func searchButtonPressed(sender: Any) {
guard let movieTitle = movieTitleTextField.text
else { return }
movieRatingClient.fetchRating(for: movieTitle) {
(rating, review) in
self.ratingLabel.text = rating
self.reviewLabel.text = review
}
}
}
Whenever this view controller is instantiated within the app (however that happens),
you’d need to set the movieRatingClient. Notice how the view controller doesn’t
know about the concrete implementations of MovieRatingStrategy.
raywenderlich.com 91
Design Patterns by Tutorials Chapter 5: Strategy Pattern
Tutorial project
You’ll continue the RabbleWabble app from the previous chapter. If you skipped the
previous chapter, or you want a fresh start, open Finder and navigate to where you
downloaded the resources for this chapter, and then open starter ▸ RabbleWabble ▸
RabbleWabble.xcodeproj in Xcode.
Instead of always showing the questions in the same order each time, wouldn’t it be
great if they were randomized? However, some users may also want to study the
questions in order. You'll use the strategy pattern to allow both options!
Right-click on the yellow RabbleWabble group, select New Group and name it
Strategies.
Right-click again on the yellow RabbleWabble group and select Sort by Name.
Right-click on your newly-added Strategies group and select New File. Under the
iOS tab, select Swift File and press Next. Enter QuestionStrategy.swift for the
name and press Create.
// 2
var correctCount: Int { get }
var incorrectCount: Int { get }
// 3
func advanceToNextQuestion() -> Bool
// 4
func currentQuestion() -> Question
raywenderlich.com 92
Design Patterns by Tutorials Chapter 5: Strategy Pattern
// 5
func markQuestionCorrect(_ question: Question)
func markQuestionIncorrect(_ question: Question)
// 6
func questionIndexTitle() -> String
}
This creates the protocol at the heart of the strategy pattern you’re going to use.
1. title will be the title for which set of questions is selected, such as "Basic
Phrases."
6. questionIndexTitle() will return the “index title” for the current question to
indicate progress, such as "1 / 10" for the first question out of ten total.
raywenderlich.com 93
Design Patterns by Tutorials Chapter 5: Strategy Pattern
// MARK: - QuestionStrategy
public var title: String {
return questionGroup.title
}
// 1
import GameplayKit.GKRandomSource
raywenderlich.com 94
Design Patterns by Tutorials Chapter 5: Strategy Pattern
// 2
let randomSource = GKRandomSource.sharedRandom()
self.questions =
randomSource.arrayByShufflingObjects(
in: questionGroup.questions) as! [Question]
}
// MARK: - QuestionStrategy
public var title: String {
return questionGroup.title
}
raywenderlich.com 95
Design Patterns by Tutorials Chapter 5: Strategy Pattern
questionView.answerLabel.text = question.answer
questionView.promptLabel.text = question.prompt
questionView.hintLabel.text = question.hint
questionView.answerLabel.isHidden = true
questionView.hintLabel.isHidden = true
// 2
questionIndexItem.title =
questionStrategy.questionIndexTitle()
}
Here you use the questionStrategy to get the (1) currentQuestion() and (2)
questionIndexTitle() instead of getting these from the questionGroup.
raywenderlich.com 96
Design Patterns by Tutorials Chapter 5: Strategy Pattern
questionStrategy.markQuestionCorrect(question)
questionView.correctCountLabel.text =
String(questionStrategy.correctCount)
showNextQuestion()
}
questionView.incorrectCountLabel.text =
String(questionStrategy.incorrectCount)
showNextQuestion()
}
The last method you need to update is showNextQuestion(). However, this is a bit
trickier because you call the delegate method, and this takes in a questionGroup
parameter.
You’re faced with a choice now: You can either add questionGroup to the
QuestionStrategy protocol, or update the QuestionViewControllerDelegate
method to use QuestionStrategy instead of QuestionGroup.
When faced with a choice like this in your own apps, you should try to consider the
consequences of each:
• If you expose the QuestionGroup, will this make the overall app design messier
and harder to maintain?
Depending on your answers, you’d need to choose one or the other... fortunately, you
have a 50/50 shot of being right (or wrong)!
raywenderlich.com 97
Design Patterns by Tutorials Chapter 5: Strategy Pattern
In this case, another developer (ahem, one who knows what’s coming up in the next
few chapters), advises that you update the QuestionViewControllerDelegate and
change the QuestionGroup to QuestionStrategy instead.
func questionViewController(
_ viewController: QuestionViewController,
didComplete questionStrategy: QuestionStrategy)
}
delegate?.questionViewController(self,
didCancel: questionStrategy)
}
Since you’ve updated all places that use questionGroup directly, delete the
questionGroup property.
At this point, you shouldn’t see any compiler errors or warnings on the
QuestionViewController. However, if you try to build and run, you'll still get
compiler errors.
raywenderlich.com 98
Design Patterns by Tutorials Chapter 5: Strategy Pattern
viewController.questionGroup = selectedQuestionGroup
viewController.questionStrategy =
RandomQuestionStrategy(questionGroup: selectedQuestionGroup)
extension SelectQuestionGroupViewController:
QuestionViewControllerDelegate {
Build and run your project. Select any cell, press the green check or red X buttons a
few times, press Back, then press the same cell again and repeating the process. You
should see that the questions are now randomized!
viewController.questionStrategy =
RandomQuestionStrategy(questionGroup: selectedQuestionGroup)
viewController.questionStrategy =
SequentialQuestionStrategy(questionGroup: selectedQuestionGroup)
Build and run and try working through the same set of questions again. This time,
they should now be in the same order.
raywenderlich.com 99
Design Patterns by Tutorials Chapter 5: Strategy Pattern
How cool is that? You can now easily swap out different strategies as necessary!
Key points
You learned about the strategy pattern in this chapter. Here are its key points:
• The strategy pattern defines a family of interchangeable objects that can be set or
switched at runtime.
• This pattern has three parts: an object using a strategy, a strategy protocol, and a
family of strategy objects.
• The strategy pattern is similar to the delegation pattern: Both patterns use a
protocol for flexibility. Unlike the delegation pattern, however, strategies are meant
to be switched at runtime, whereas delegates are usually fixed.
You've laid the groundwork for Rabble Wabble to switch question strategies at
runtime. However, you haven't actually created a means for the user to do this while
running the app just yet! There's another pattern you'll use to hold onto user
preferences like this: the singleton design pattern.
Continue onto the next chapter to learn about the singleton design pattern and
continue building out Rabble Wabble.
raywenderlich.com 100
6 Chapter 6: Singleton
Pattern
By Joshua Greene
The singleton pattern restricts a class to only one instance. Every reference to the
class refers to the same underlying instance. This pattern is extremely common in
iOS app development, as Apple makes extensive use of it.
The “singleton plus” pattern is also common, which provides a shared singleton
instance that allows other instances to be created, too.
Use the singleton plus pattern if a shared instance is useful most of the time, but you
also want to allow custom instances to be created. An example of this is
raywenderlich.com 101
Design Patterns by Tutorials Chapter 6: Singleton Pattern
Playground example
Open FundamentalDesignPatterns.xcworkspace in the Starter directory and then
open the Overview page.
You’ll see that Singleton is listed under Creational Patterns. This is because
singleton is all about creating a shared instance.
Both singleton and singleton plus are common throughout Apple frameworks. For
example, UIApplication is a true singleton.
import UIKit
// MARK: - Singleton
let app = UIApplication.shared
// let app2 = UIApplication()
If you try to uncomment the let app2 line, you’ll get a compiler error!
UIApplication doesn’t allow more than one instance to be created. This proves it’s
a singleton! You can also create your own singleton class. Add the following right
after the previous code:
1. You first declare a public static property called shared, which is the singleton
instance.
raywenderlich.com 102
Design Patterns by Tutorials Chapter 6: Singleton Pattern
Next, add the following singleton plus example below your MySingleton example:
You’re also allowed to create new instances of FileManager. This proves that it’s
using the singleton plus pattern!
It’s easy to create your own singleton plus class, too. Add the following below the
FileManager example:
// 4
let singletonPlus2 = MySingletonPlus()
1. You declare a shared static property just like a singleton. This is sometimes
called default instead, but it’s simply a preference for whichever name you
prefer.
2. Unlike a true singleton, you declare init as public to allow additional instances
to be created.
raywenderlich.com 103
Design Patterns by Tutorials Chapter 6: Singleton Pattern
If you encounter a situation where you’re tempted to use a singleton, first consider
other ways to accomplish your task.
For example, singletons are not appropriate if you’re simply trying to pass
information from one view controller to another. Instead, consider passing models
via an initializer or property.
If you determine you actually do need a singleton, consider whether a singleton plus
makes more sense.
Will having more than one instance cause problems? Will it ever be useful to have
custom instances? Your answers will determine whether its better for you to use a
true singleton or singleton plus.
A very most common reason why singletons are problematic is testing. If you have
state being stored in a global object like a singleton then order of tests can matter,
and it can be painful to mock them. Both of these reasons make testing a pain.
Lastly, beware of “code smell” indicating your use case isn’t appropriate as a
singleton at all. For example, if you often need many custom instances, your use case
may be better as a regular object.
Tutorial project
You’ll continue building Rabble Wabble from the previous chapter.
raywenderlich.com 104
Design Patterns by Tutorials Chapter 6: Singleton Pattern
If you skipped the previous chapter, or you want a fresh start, open Finder, navigate
to where you downloaded the resources for this chapter and open
starter\RabbleWabble\RabbleWabble.xcodeproj in Xcode.
In the previous chapter, you hardcoded which strategy to use for showing questions:
either randomized or sequential. That means it’s not possible for the user to change
this. Your task is to let the user choose how they want the questions displayed.
Right-click on Models in the File hierarchy and select New File.... Under the iOS
tab, select Swift File and press Next. Enter AppSettings.swift for the name and
click Create.
import Foundation
You’ll ultimately use this to manage app-wide settings. For Rabble Wabble’s
purposes, it doesn’t make sense to have multiple, app-wide settings, so you make
this a true singleton, instead of a singleton plus.
Next, add the following code to the end of the file, after the final closing brace for
AppSettings:
// MARK: - QuestionStrategyType
public enum QuestionStrategyType: Int, CaseIterable {
case random
case sequential
raywenderlich.com 105
Design Patterns by Tutorials Chapter 6: Singleton Pattern
case .random:
return "Random"
case .sequential:
return "Sequential"
}
}
Here, you declared a new enum named QuestionStrategyType, which has cases for
every possible type of QuestionStrategy in the app.
Since you’ve used the CaseIterable protocol available since Swift 4.2 you also get a
free static property generated by the compiler automatically called allCases to
use later to display a listing of all possible strategies. When doing so, you’ll use
title() for the title text to represent the strategy.
However, you actually still haven’t addressed the main issue at hand: letting the user
set the desired strategy type.
Add the following code inside AppSettings, right after the opening class curly brace:
// MARK: - Keys
private struct Keys {
static let questionStrategy = "questionStrategy"
}
raywenderlich.com 106
Design Patterns by Tutorials Chapter 6: Singleton Pattern
You’ll use questionStrategyType to hold onto the user’s desired strategy. Instead
of just a simple property, which would be lost whenever the user terminates the app,
you override the getter and setter to get and set the integer value using
userDefaults.
Right-click on Controllers in the File hierarchy and select New file.... Under the
iOS tab, select Swift File and press Next. Enter AppSettingsViewController.swift
for the name and press Create.
raywenderlich.com 107
Design Patterns by Tutorials Chapter 6: Singleton Pattern
import UIKit
// 1
public class AppSettingsViewController: UITableViewController {
// 2
// MARK: - Properties
public let appSettings = AppSettings.shared
private let cellIdentifier = "basicCell"
// 3
tableView.tableFooterView = UIView()
// 4
tableView.register(UITableViewCell.self,
forCellReuseIdentifier: cellIdentifier)
}
}
2. You create a property for appSettings, which you’ll use to get and set the
questionStrategyType.
3. You set the tableFooterView to a new UIView. This way, you won’t have extra
blank cells at the bottom of the table view.
Next, add the following code at the end of the file, after the closing curly brace of the
class:
// MARK: - UITableViewDataSource
extension AppSettingsViewController {
raywenderlich.com 108
Design Patterns by Tutorials Chapter 6: Singleton Pattern
// 1
return QuestionStrategyType.allCases.count
}
// 2
let questionStrategyType =
QuestionStrategyType.allCases[indexPath.row]
// 3
cell.textLabel?.text = questionStrategyType.title()
// 4
if appSettings.questionStrategyType ==
questionStrategyType {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
}
}
Next, add this last extension to the end of the file, after the last closing curly brace:
// MARK: - UITableViewDelegate
extension AppSettingsViewController {
raywenderlich.com 109
Design Patterns by Tutorials Chapter 6: Singleton Pattern
let questionStrategyType =
QuestionStrategyType.allCases[indexPath.row]
appSettings.questionStrategyType = questionStrategyType
tableView.reloadData()
}
}
Whenever a cell is selected, you get the questionStrategyType for the given cell’s
indexPath.row, set this as appSettings.questionStrategyType and reload the
table view.
Nice work! This takes care of the code for letting the user select the question
strategy.
You now need a way for the user to get to this view controller.
From the File hierarchy, open Views ▸ Main.storyboard. Next, press the Object
Library button and then select the Show Image Library tab:
Drag and drop the ic_settings image onto the Select Question Group scene’s left
navigation bar item.
raywenderlich.com 110
Design Patterns by Tutorials Chapter 6: Singleton Pattern
Next, select the Object Library button, select the Show the Objects Library tab,
enter UITableViewController into the search field and drag and drop a new Table
View Controller just below the Select Question Group scene.
Select the yellow class object for the new table view scene, open the Identity
Inspector and set the Class as AppSettingsViewController.
raywenderlich.com 111
Design Patterns by Tutorials Chapter 6: Singleton Pattern
Next, open the Attributes Inspector and set the Title as App Settings.
Then, Control-drag and drop from the Settings button onto the App Settings
scene. In the dialog that appears, select Show. This creates a new segue to this scene.
Lastly, select the existing prototype cell on the App Settings scene, and press
Delete.
raywenderlich.com 112
Design Patterns by Tutorials Chapter 6: Singleton Pattern
This isn’t strictly required, but you’re not going to use it and can rid of a compiler
warning by deleting it.
Build and run the app, tap on the Settings button, and you’ll see your brand-
spanking-new AppSettingsViewController!
Try selecting an option and navigating to and from this screen. You’ll see your
selection persist!
If you tap a cell from the Select Question Group listing, however, it may not
actually reflect your choice. What’s up with that?
Remember how you hardcoded the QuestionStrategy used in the previous chapter?
Yep, you also need to update this code to use your new AppSettings instead!
viewController.questionStrategy =
SequentialQuestionStrategy(
questionGroup: selectedQuestionGroup)
raywenderlich.com 113
Design Patterns by Tutorials Chapter 6: Singleton Pattern
viewController.questionStrategy =
appSettings.questionStrategy(for: selectedQuestionGroup)
Build and run the app, and it will now always use your selected QuestionStrategy.
Key points
You learned about the singleton pattern in this chapter. Here are its key points:
• The singleton plus pattern provides a “default” shared instance but also allows
other instances to be created too.
• Be careful about overusing this pattern! Before you create a singleton, consider
other ways to solve the problem without it. If a singleton really is best, prefer to
use a singleton plus over a singleton.
RabbleWabble is really coming along! However, it’s still missing a key functionality:
the ability to remember your score.
Continue onto the next chapter to learn about the memento design pattern and add
this functionality to the app.
raywenderlich.com 114
7 Chapter 7: Memento
Pattern
By Joshua Greene
The memento pattern allows an object to be saved and restored. It has three parts:
3. The caretaker requests a save from the originator and receives a memento in
response. The caretaker is responsible for persisting the memento and, later on,
providing the memento back to the originator to restore the originator’s state.
While not strictly required, iOS apps typically use an Encoder to encode an
originator’s state into a memento, and a Decoder to decode a memento back to an
originator. This allows encoding and decoding logic to be reused across originators.
For example, JSONEncoder and JSONDecoder allow an object to be encoded into and
decoded from JSON data respectively.
raywenderlich.com 115
Design Patterns by Tutorials Chapter 7: Memento Pattern
For example, you can use this pattern to implement a save game system, where the
originator is the game state (such as level, health, number of lives, etc), the memento
is saved data, and the caretaker is the gaming system.
You can also persist an array of mementos, representing a stack of previous states.
You can use this to implement features such as undo/redo stacks in IDEs or graphics
software.
Playground example
Open FundamentalDesignPattern.xcworkspace in the Starter directory, or
continue from your own playground workspace from the last chapter, and then open
the Overview page.
You’ll see Memento is listed under Behavioral Patterns. This is because this
pattern is all about save and restoration behavior. Click on the Memento link to
open that page.
You’ll create a simple gaming system for this example. First, you need to define the
originator. Enter the following right after Code Example:
import Foundation
// MARK: - Originator
public class Game: Codable {
Here, you define a Game: it has an internal State that holds onto game properties,
and it has methods to handle in-game actions. You also declare Game and State
conform to Codable.
raywenderlich.com 116
Design Patterns by Tutorials Chapter 7: Memento Pattern
Apple introduced Codable in Swift 4. Any type that conforms to Codable can, in
Apple’s words, “convert itself into and out of an external representation.” Essentially,
it’s a type that can save and restore itself. Sound familiar? Yep, it’s exactly what you
want the originator to be able to do.
Since all of the properties that Game and State use already conform to Codable, the
compiler automatically generates all required Codable protocol methods for you.
String, Int, Double and most other Swift-provided types conform to Codable out of
the box. How awesome is that?
More formally, Codable is a typealias that combines the Encodable and Decodable
protocols. It's declared like this:
Great! Now that you’ve got the theory under your belt, you can continue coding.
You next need a memento. Add the following after the previous code:
// MARK: - Memento
typealias GameMemento = Data
Technically, you don’t need to declare this line at all. Rather, it’s here to inform you
the GameMemento is actually Data. This will be generated by the Encoder on save,
and used by the Decoder on restoration.
Next, you need a caretaker. Add the following after the previous code:
// MARK: - CareTaker
public class GameSystem {
// 1
private let decoder = JSONDecoder()
private let encoder = JSONEncoder()
private let userDefaults = UserDefaults.standard
raywenderlich.com 117
Design Patterns by Tutorials Chapter 7: Memento Pattern
// 2
public func save(_ game: Game, title: String) throws {
let data = try encoder.encode(game)
userDefaults.set(data, forKey: title)
}
// 3
public func load(title: String) throws -> Game {
guard let data = userDefaults.data(forKey: title),
let game = try? decoder.decode(Game.self, from: data)
else {
throw Error.gameNotFound
}
return game
}
1. You first declare properties for decoder, encoder and userDefaults. You’ll use
decoder to decode Games from Data, encoder to encode Games to Data, and
userDefaults to persist Data to disk. Even if the app is re-launched, saved Game
data will still be available.
2. save(_:title:) encapsulates the save logic. You first use encoder to encode the
passed-in game. This operation may throw an error, so you must prefix it with
try. You then save the resulting data under the given title within
userDefaults.
3. load(title:) likewise encapsulates the load logic. You first get data from
userDefaults for the given title. You then use decoder to decode the Game
from the data. If either operation fails, you throw a custom error for
Error.gameNotFound. If both operations succeed, you return the resulting game.
// MARK: - Example
var game = Game()
game.monstersEatPlayer()
game.rackUpMassivePoints()
raywenderlich.com 118
Design Patterns by Tutorials Chapter 7: Memento Pattern
Here you simulate playing a game: the player gets eaten by a monster, but she makes
a comeback and racks up massive points!
Next, add the following code to the end of the playground page:
// Save Game
let gameSystem = GameSystem()
try gameSystem.save(game, title: "Best Game Ever")
Here, you simulate the player triumphantly saving her game, likely boasting to her
friends shortly thereafter.
Of course, she will want to try to beat her own record, so she’ll start a new Game. Add
the following code to the end of the playground page:
// New Game
game = Game()
print("New Game Score: \(game.state.score)")
Here, you create a new Game instance and print out the game.state.score. This
should print the following to the console:
The player can also resume her previous game. Add the following code to the end of
the playground page:
// Load Game
game = try! gameSystem.load(title: "Best Game Ever")
print("Loaded Game Score: \(game.state.score)")
Here, you load the player’s previous Game, and print the game’s score. You should see
this in your output:
raywenderlich.com 119
Design Patterns by Tutorials Chapter 7: Memento Pattern
To mitigate this problem, avoid using try! unless you’re absolutely sure the
operation will succeed. You should also plan ahead when changing your models.
For example, you can version your models or use a versioned database. However,
you’ll need to carefully consider how to handle version upgrades. You might choose
to delete old data whenever you encounter a new version, create an upgrade path to
convert from old to new data, or even use a combination of these approaches.
Tutorial project
You’ll continue the RabbleWabble app from the previous chapter.
If you skipped the previous chapter, or you want a fresh start, open Finder and
navigate to where you downloaded the resources for this chapter. Then, open starter
▸ RabbleWabble ▸ RabbleWabble.xcodeproj in Xcode.
You’ll use the memento pattern to add an important app feature: the ability to save
QuestionGroup scores.
Open QuestionGroup.swift, and add the following right after the opening class
curly brace:
Here you a create a new class called Score, which you’ll use to hold on to score info.
Then, add the following property right after questions, ignoring the compiler errors
for now:
raywenderlich.com 120
Design Patterns by Tutorials Chapter 7: Memento Pattern
To fix the compiler errors, you need to declare a new initializer. Add the following
right before the ending class curly brace:
This initializer has a default value for the score property, creating a blank Score
object. That means everywhere in the app that was creating a QuestionGroup before
using init(questions:title:) can still do so and they will get this blank Score
object created for them.
Lastly, replace public struct QuestionGroup with the following, again, ignoring
the resulting compiler error for now:
QuestionGroup will act as the originator. You change this from a struct to a class
to make this to a reference type instead of a value type, so you can pass around and
modify QuestionGroup objects instead of copying them. You also make it conform to
Codable to enable encoding and decoding.
Since Question doesn’t currently conform to Codable, the compiler can’t generate
the required protocol methods automatically for you. Fortunately, this is easy to fix.
You change Question from a struct to a class to make this a reference type, and
you also make it conform to Codable.
You also need to add an initializer for this class. Add the following before the
closing class curly brace:
raywenderlich.com 121
Design Patterns by Tutorials Chapter 7: Memento Pattern
Build your project to verify you’ve resolved all of the compiler errors.
Next, right-click on the yellow RabbleWabble group, select New Group and name
it Caretakers.
Right-click again on the yellow RabbleWabble group and select Sort by Name.
Right-click on your newly-added Caretakers group and select New File. Under the
iOS tab, select Swift File and click Next. Enter DiskCaretaker.swift for the name
and click Create.
import Foundation
DiskCaretaker will ultimately provide methods for saving and retrieving Codable
objects from the device’s Documents directory. You’ll use JSONEncoder to encode
objects into JSON data and JSONDecoder to decode from JSON data into objects.
Add the next block of code before the closing class curly brace:
raywenderlich.com 122
Design Patterns by Tutorials Chapter 7: Memento Pattern
You’ll use this method to create a document URL given a fileName. This method
simply finds the Documents directory and then appends the given file name.
// 1
public static func save<T: Codable>(
_ object: T, to fileName: String) throws {
do {
// 2
let url = createDocumentURL(withFileName: fileName)
// 3
let data = try encoder.encode(object)
// 4
try data.write(to: url, options: .atomic)
} catch (let error) {
// 5
print("Save failed: Object: `\(object)`, " +
"Error: `\(error)`")
throw error
}
}
1. You first declare a generic method that takes any object that conforms to
Codable.
2. You then call createDocumentURL to create a document URL for the given
fileName.
3. You use encoder to encode the object into data. This operation may throw an
error, so you prefix it with try.
4. You call data.write to write the data to the given url. You use the atomic
operator to instruct iOS to create a temporary file and then move it to the desired
path. This has a small performance cost, but it ensures the file data will never be
corrupted. It’s possible this operation may throw an error, so you must prefix it
with try.
5. If you catch an error, you print the object and error to the console and then
throw the error.
raywenderlich.com 123
Design Patterns by Tutorials Chapter 7: Memento Pattern
// 1
public static func retrieve<T: Codable>(
_ type: T.Type, from fileName: String) throws -> T {
let url = createDocumentURL(withFileName: fileName)
return try retrieve(T.self, from: url)
}
// 2
public static func retrieve<T: Codable>(
_ type: T.Type, from url: URL) throws -> T {
do {
// 3
let data = try Data(contentsOf: url)
// 4
return try decoder.decode(T.self, from: data)
} catch (let error) {
// 5
print("Retrieve failed: URL: `\(url)`, Error: `\(error)`")
throw error
}
}
1. You declare a method for retrieving objects given a type and fileName, which is
a String. This method first creates a file URL and calls retrieve(_:from:).
You’ll soon see how it can be useful to pass either a String or URL at times to
retrieve persisted objects.
2. You also declare a method which takes a URL rather than a String, which does
the actual loading. The previous method simply calls through to this one. You’ll
need both, so both are public.
3. Here you attempt to create a Data instance from the given file url. It’s possible
this operation may fail, so you prefix this call with try.
4. You then use decoder to decode the object into data. This operation may throw
an error, so you prefix it with try.
5. If you catch an error, you print the url and error to the console and then
throw the error.
Great start! You’ll soon see how useful this helper class is. However, you need to
create another file first.
raywenderlich.com 124
Design Patterns by Tutorials Chapter 7: Memento Pattern
Right-click on the Caretakers group and select New File. Under the iOS tab, select
Swift File and click Next. Enter QuestionGroupCaretaker.swift for the name and
click Create.
import Foundation
// 1
public final class QuestionGroupCaretaker {
// MARK: - Properties
// 2
private let fileName = "QuestionGroupData"
public var questionGroups: [QuestionGroup] = []
public var selectedQuestionGroup: QuestionGroup!
// 4
private func loadQuestionGroups() {
if let questionGroups =
try? DiskCaretaker.retrieve([QuestionGroup].self,
from: fileName) {
self.questionGroups = questionGroups
} else {
let bundle = Bundle.main
let url = bundle.url(forResource: fileName,
withExtension: "json")!
self.questionGroups = try!
DiskCaretaker.retrieve([QuestionGroup].self, from: url)
try! save()
}
}
1. You declare a new class called QuestionGroupCaretaker. You’ll use this to save
and retrieve QuestionGroup objects.
raywenderlich.com 125
Design Patterns by Tutorials Chapter 7: Memento Pattern
2. You declare three properties: fileName defines the file where you’ll save and
retrieve QuestionGroup objects; questionGroups will hold onto the
QuestionGroups that are in use; and selectedQuestionGroup will hold onto
whichever selection the user makes.
3. You call loadQuestionGroups() inside init(), which loads the question groups.
In the case of a failure, you load the QuestionGroups from Bundle.main and
then call save() to write this file to the user’s Documents directory.
Open Finder and navigate to where you have the projects downloaded for this
chapter. Alongside the Starter and Final directories, you’ll see a Resources directory
that contains QuestionGroupData.json.
Position the Finder window above Xcode and drag and drop
QuestionGroupData.json into the Resources group like so:
In the new window that appears, make sure Copy items if needed is checked and
click Finish to copy the file.
raywenderlich.com 126
Design Patterns by Tutorials Chapter 7: Memento Pattern
Since you’re no longer using QuestionGroupData.swift, select this file within the
File navigator and click Delete. In the new window that appears, select Move to
Trash.
raywenderlich.com 127
Design Patterns by Tutorials Chapter 7: Memento Pattern
The very first time you run the app, you’ll see an error printed containing this text:
Build and run again, and you shouldn’t see any errors logged to the console.
Everything works so far. However, what about saving the QuestionGroup’s score?
With this:
Rather than using stored properties for correctCount and incorrectCount, you get
and set the questionGroup.score.correctCount and
questionGroup.score.incorrectCount respectively.
But wait, isn’t there similar logic in RandomQuestionStrategy.swift too? Yes, there
is! While you could try to copy this logic over as well, you’d end up duplicating a lot
of code.
This brings up an important point: when you add new design patterns and
functionality to your app, you’ll need to refactor your code occasionally. In this case,
you’ll pull out a base class to move your shared logic into.
Right-click on the Strategies group and select New file.... Under the iOS tab, select
Swift File and click Next. Enter BaseQuestionStrategy.swift for the name and click
Create.
raywenderlich.com 128
Design Patterns by Tutorials Chapter 7: Memento Pattern
// MARK: - Properties
// 1
public var correctCount: Int {
get { return questionGroup.score.correctCount }
set { questionGroup.score.correctCount = newValue }
}
public var incorrectCount: Int {
get { return questionGroup.score.incorrectCount }
set { questionGroup.score.incorrectCount = newValue }
}
private var questionGroupCaretaker: QuestionGroupCaretaker
// 2
private var questionGroup: QuestionGroup {
return questionGroupCaretaker.selectedQuestionGroup
}
private var questionIndex = 0
private let questions: [Question]
// 4
self.questionGroupCaretaker.selectedQuestionGroup.score =
QuestionGroup.Score()
}
// MARK: - QuestionStrategy
public var title: String {
return questionGroup.title
}
raywenderlich.com 129
Design Patterns by Tutorials Chapter 7: Memento Pattern
correctCount += 1
}
4. Here, you reset the score to a new instance, Score(), so scoring always starts
over whenever you start a QuestionGroup.
import GameplayKit.GKRandomSource
raywenderlich.com 130
Design Patterns by Tutorials Chapter 7: Memento Pattern
This code is much shorter than before, isn’t it? This is because most of the logic is
handled within BaseQuestionStrategy.
Next, you need to fix the compiler errors caused by these changes.
raywenderlich.com 131
Design Patterns by Tutorials Chapter 7: Memento Pattern
}
}
viewController.questionStrategy =
appSettings.questionStrategy(for: selectedQuestionGroup)
with this:
viewController.questionStrategy =
appSettings.questionStrategy(for: questionGroupCaretaker)
Build and run and select a QuestionGroup cell to verify everything works.
raywenderlich.com 132
Design Patterns by Tutorials Chapter 7: Memento Pattern
try? questionGroupCaretaker.save()
Build and run; select any QuestionGroup cell you’d like; and tap the green
checkmark and red X buttons a few times to mark the questions as correct and
incorrect. Then, stop the app and build and run again. You should see output like this
in the console:
Excellent! This shows the scores are saved across app launches.
raywenderlich.com 133
Design Patterns by Tutorials Chapter 7: Memento Pattern
Key points
You learned about the memento pattern in this chapter. Here are its key points:
• The memento pattern allows an object to be saved and restored. It involves three
types: the originator, memento and caretaker.
• The originator is the object to be saved; the memento is a saved state; and the
caretaker handles, persists and retrieves mementos.
• iOS provides Encoder for encoding a memento to, and Decoder for decoding from,
a memento. This allows encoding and decoding logic to be used across originators.
Rabble Wabble is really coming along, and you can now save and restore scores!
However, the app doesn’t show the score to the user yet. You’ll use another pattern
to do this: The observer pattern.
raywenderlich.com 134
8 Chapter 8: Observer
Pattern
By Joshua Greene
The observer pattern lets one object observe changes on another object. Apple added
language-level support for this pattern in Swift 5.1 with the addition of Publisher in
the Combine framework.
raywenderlich.com 135
Design Patterns by Tutorials Chapter 8: Observer Pattern
This pattern is often used with MVC, where the view controller has subscriber(s) and
the model has publisher(s). This allows the model to communicate changes back to
the view controller without needing to know anything about the view controller's
type. Thereby, different view controllers can use and observe changes on the same
model type.
Playground example
Open FundamentalDesignPattern.xcworkspace in the Starter directory, or
continue from your own playground workspace from the last chapter, and then open
the Overview page.
You’ll see Observer is listed under Behavioral Patterns. This is because Observer is
about one object observing another object.
// 1
import Combine
// 2
public class User {
// 3
@Published var name: String
// 4
public init(name: String) {
self.name = name
}
}
1. First, you import Combine, which includes the @Published annotation and
Publisher & Subscriber types.
2. Next, you declare a new User class; @Published properties cannot be used on
structs or any other types besides classes.
raywenderlich.com 136
Design Patterns by Tutorials Chapter 8: Observer Pattern
3. Next, you create a var property for name and mark it as @Published. This tells
Xcode to automatically generate a Publisher for this property. Note that you
cannot use @Published for let properties, as by definition they cannot be
changed.
4. Finally, you create an initializer that sets the initial value of self.name.
// 1
let user = User(name: "Ray")
// 2
let publisher = user.$name
// 3
var subscriber: AnyCancellable? = publisher.sink() {
print("User's name is \($0)")
}
// 4
user.name = "Vicki"
2. Next, you access the publisher for broadcasting changes to the user's name via
user.$name. This returns an object of type Published<String>.Publisher.
This object is what can be listened to for updates.
3. Next, you create a subscriber by calling sink on the publisher. This takes a
closure for which is called for the initial value and anytime the value changes.
subscriber = nil
user.name = "Ray has left the building"
raywenderlich.com 137
Design Patterns by Tutorials Chapter 8: Observer Pattern
By setting the subscriber to nil, it will no longer receive updates from the
publisher. To prove this, you change the user's name a final time, but you won't see
any new output in the console.
Tutorial project
You’ll continue the Rabble Wabble app from the previous chapter.
If you skipped the previous chapter, or you want a fresh start, open Finder and
navigate to where you downloaded the resources for this chapter. Then, open starter
▸ RabbleWabble ▸ RabbleWabble.xcodeproj in Xcode.
You’ll use the observer pattern to display the user’s latest score on the “Select
Question Group” screen.
Open QuestionGroup.swift from the File hierarchy. This already has a Score, but
it’s not currently possible to observe changes on it. Add the following below import
Foundation:
import Combine
This imports Apple's new Combine framework to do all the heavy lifting for you.
Next, add the following to the end of the Score class (which is inside the
QuestionGroup class) after its other properties, ignorning the compiler error for
now:
The runningPercentage property will allow the question group's latest “running
percentage score” to be observed.
raywenderlich.com 138
Design Patterns by Tutorials Chapter 8: Observer Pattern
To fix this, add the following code right after init for Score:
// 1
private enum CodingKeys: String, CodingKey {
case correctCount
case incorrectCount
}
// 2
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy:
CodingKeys.self)
self.correctCount = try container.decode(Int.self,
forKey: .correctCount)
self.incorrectCount = try container.decode(Int.self,
forKey: .incorrectCount)
updateRunningPercentage()
}
// 3
private func updateRunningPercentage() {
let totalCount = correctCount + incorrectCount
guard totalCount > 0 else {
runningPercentage = 0
return
}
runningPercentage = Double(correctCount) / Double(totalCount)
}
1. You first declare an enum for CodingKeys and cases for correctCount and
incorrectCount. This tells the compiler to ignore runningPercentage in the
encoder and decoder methods it automatically generates.
raywenderlich.com 139
Design Patterns by Tutorials Chapter 8: Observer Pattern
Next, replace the var correctCount and var incorrectCount lines with the
following:
Before you can start creating subscribers for runningPercentage, you need to make
a few small changes. First, add the following method to the end of the Score class
before the closing curly brace:
This method “resets” Score. You’ll use it whenever the user restarts a
QuestionGroup.
Next, replace the var score line with the following, ignoring the resulting compiler
error:
This prevents all outside classes from setting score directly. This ensures any
runningPercentage subscribers aren’t accidentally wiped out, which would happen
if score was set directly.
There’s currently one place that does set score directly. Open
BaseQuestionStrategy.swift and replace the following line:
self.questionGroupCaretaker.selectedQuestionGroup.score =
QuestionGroup.Score()
self.questionGroupCaretaker.selectedQuestionGroup.score.reset()
raywenderlich.com 140
Design Patterns by Tutorials Chapter 8: Observer Pattern
Build and run to ensure you don’t have any compiler errors. Nothing appears to have
changed so far, but you’re now ready to register your observers!
You first need somewhere to hold onto the subscriber object. Ideally, this should be
tied to the life of the object that it's related. In this case, this is the
QuestionGroupCell itself. Open QuestionGroupCell.swift and add the following
below import UIKit:
import Combine
cell.percentageSubscriber =
questionGroup.score.$runningPercentage // 1
.receive(on: DispatchQueue.main) // 2
.map() { // 3
return String(format: "%.0f %%", round(100 * $0))
}.assign(to: \.text, on: cell.percentageLabel) // 4
Build and run, pick any question group cell you’d like, and tap the “Correct” and
“Incorrect” buttons a few times. When you press the “Menu” button, the score will
now be visible. Even better, if you quit the app and restart it, the scores will be
persisted thanks to your implementation of the memento pattern from the previous
chapter.
raywenderlich.com 141
Design Patterns by Tutorials Chapter 8: Observer Pattern
Key points
You learned about the observer pattern in this chapter. Here are its key points:
• The observer pattern lets one object observe changes on another object. It involves
three types: the subscriber, publisher and value.
• The subscriber is the observer; the publisher is the observable object; and the
value is the object that's being changed.
• Swift 5.1 makes it easy to implement the observer pattern using @Published
properties.
RabbleWabble is becoming ever more feature-rich. However, there’s one feature that
would be really great: the ability for users to create their own QuestionGroups.
You’ll use another pattern to do this: the builder design pattern.
Continue onto the next chapter to learn about the builder pattern and complete the
RabbleWabble app.
raywenderlich.com 142
9 Chapter 9: Builder Pattern
By Joshua Greene
The builder pattern allows you to create complex objects by providing inputs step-
by-step, instead of requiring all inputs upfront via an initializer. This pattern
involves three main types:
1. The director accepts inputs and coordinates with the builder. This is usually a
view controller or a helper class that’s used by a view controller.
2. The product is the complex object to be created. This can be either a struct or a
class, depending on desired reference semantics. It’s usually a model, but it can
be any type depending on your use case.
3. The builder accepts step-by-step inputs and handles the creation of the product.
This is often a class, so it can be reused by reference.
raywenderlich.com 143
Design Patterns by Tutorials Chapter 9: Builder Pattern
This pattern works especially well when a product requires multiple inputs. The
builder abstracts how these inputs are used to create the product, and it accepts
them in whatever order the director wants to provide them.
For example, you can use this pattern to implement a “hamburger builder.” The
product could be a hamburger model, which has inputs such as meat selection,
toppings and sauces. The director could be an employee object, which knows how to
build hamburgers, or it could be a view controller that accepts inputs from the user.
The “hamburger builder” can thereby accept meat selection, toppings and sauces in
any order and create a hamburger upon request.
Playground example
Open FundamentalDesignPattern.xcworkspace in the Starter directory, or
continue from your own playground workspace from the last chapter, and then open
the Overview page.
You’ll see Builder is listed under Creational Patterns. This is because this pattern is
all about creating complex products. Click on the Builder link to open that page.
You’ll implement the “hamburger builder” example from above. You first need to
define the product. Enter the following right after Code Example:
import Foundation
// MARK: - Product
// 1
public struct Hamburger {
public let meat: Meat
public let sauce: Sauces
public let toppings: Toppings
}
raywenderlich.com 144
Design Patterns by Tutorials Chapter 9: Builder Pattern
// 2
public enum Meat: String {
case beef
case chicken
case kitten
case tofu
}
// 3
public struct Sauces: OptionSet {
public static let mayonnaise = Sauces(rawValue: 1 << 0)
public static let mustard = Sauces(rawValue: 1 << 1)
public static let ketchup = Sauces(rawValue: 1 << 2)
public static let secret = Sauces(rawValue: 1 << 3)
// 4
public struct Toppings: OptionSet {
public static let cheese = Toppings(rawValue: 1 << 0)
public static let lettuce = Toppings(rawValue: 1 << 1)
public static let pickles = Toppings(rawValue: 1 << 2)
public static let tomatoes = Toppings(rawValue: 1 << 3)
1. You first define Hamburger, which has properties for meat, sauce and toppings.
Once a hamburger is made, you aren’t allowed to change its components, which
you codify via let properties. You also make Hamburger conform to
CustomStringConvertible, so you can print it later.
2. You declare Meat as an enum. Each hamburger must have exactly one meat
selection: sorry, no beef-chicken-tofu burgers allowed. You also specify an exotic
meat, kitten. Who doesn’t like nom nom kitten burgers?
3. You define Sauces as an OptionSet. This will allow you to combine multiple
sauces together. My personal favorite is ketchup-mayonnaise-secret sauce.
4. You likewise define Toppings as an OptionSet. You’re gonna need more than
pickles for a good burger!
raywenderlich.com 145
Design Patterns by Tutorials Chapter 9: Builder Pattern
// MARK: - Builder
public class HamburgerBuilder {
// 1
public private(set) var meat: Meat = .beef
public private(set) var sauces: Sauces = []
public private(set) var toppings: Toppings = []
// 2
public func addSauces(_ sauce: Sauces) {
sauces.insert(sauce)
}
// 3
public func build() -> Hamburger {
return Hamburger(meat: meat,
sauce: sauces,
toppings: toppings)
}
}
1. You declare properties for meat, sauces and toppings, which exactly match the
inputs for Hamburger. Unlike a Hamburger, you declare these using var to be able
to change them. You also specify private(set) for each to ensure only
HamburgerBuilder can set them directly.
2. Since you declared each property using private(set), you need to provide
public methods to change them. You do so via addSauces(_:),
removeSauces(_:), addToppings(_:), removeToppings(_:) and setMeat(_:).
raywenderlich.com 146
Design Patterns by Tutorials Chapter 9: Builder Pattern
3. Lastly, you define build() to create the Hamburger from the selections.
private(set) forces consumers to use the public setter methods. This allows the
builder to perform validation before setting the properties.
If a meat is sold out, you’ll throw an error whenever setMeat(_:) is called. You’ll
need to declare a custom error type for this. Add the following code right after the
opening curly brace for HamburgerBuilder:
If you now attempt to set kitten for the meat, you will receive an error that it’s
soldOut. It’s really popular, after all!
raywenderlich.com 147
Design Patterns by Tutorials Chapter 9: Builder Pattern
Next, you need to declare the director. Add the following at the end of the
playground:
// MARK: - Director
public class Employee {
// MARK: - Example
let burgerFlipper = Employee()
Here, you create an instance of Employee called burgerFlipper and request combo1
be created. You should see this printed to the console:
} else {
print("Sorry, no kitten burgers here... :[")
}
raywenderlich.com 148
Design Patterns by Tutorials Chapter 9: Builder Pattern
Here, you request a kitten-special burger. Since kitten is sold out, you’ll see this
printed to the console:
Aww man, you’re going to have to go somewhere else to satisfy your kitten burger
cravings!
Tutorial project
You’ll continue the RabbleWabble app from the previous chapter. Specifically, you’ll
add the capability to create a new QuestionGroup using the builder pattern.
If you skipped the previous chapter, or you want a fresh start, open Finder and
navigate to where you downloaded the resources for this chapter. Then, open
starter\RabbleWabble\RabbleWabble.xcodeproj in Xcode. You should then skip to
Implementing the builder pattern, as the starter project already has all the files
you need within it.
If you instead choose to continue building your project from the last chapter, you’ll
need to add a few files. The contents of these files aren’t significant to understand
the builder pattern. Rather, they provide a simple starting point, so you won’t need
to do tedious view setup.
Open Finder and navigate to where you have the projects downloaded for this
chapter. Alongside the Starter and Final directories, you’ll see a Resources directory
that contains Controllers and Views subdirectories.
raywenderlich.com 149
Design Patterns by Tutorials Chapter 9: Builder Pattern
Position the Finder window above Xcode and drag and drop
Controllers\CreateQuestionGroupViewController.swift into the app’s
Controllers group like this:
When prompted, check the option for Copy items if needed and press Finish to add
the file.
Likewise, drag and drop all of the files from resources\Views into the app’s Views.
Then, right-click on Views and select Sort by Name. Afterwards, your File
hierarchy should look like this:
raywenderlich.com 150
Design Patterns by Tutorials Chapter 9: Builder Pattern
To fix this, open Main.storyboard and pan to the Select Question Group scene.
Then, press the Object library button, select the Show the Objects Library tab,
enter bar button into the search field. Then, drag and drop a new bar button item
as the right bar button for the Select Question Group scene.
Select the newly added bar button item, go to the Attributes Inspector and set
System Item as Add.
raywenderlich.com 151
Design Patterns by Tutorials Chapter 9: Builder Pattern
Next, press the Object library button, enter storyboard into the search field, and
drag and drop a new storyboard reference above the Question View Controller
scene.
Open NewQuestionGroup.storyboard, and you’ll see its initial view controller is set
to a UINavigationController, which has CreateQuestionGroupViewController
set as its root view controller.
raywenderlich.com 152
Design Patterns by Tutorials Chapter 9: Builder Pattern
// MARK: - CreateQuestionGroupViewControllerDelegate
extension SelectQuestionGroupViewController:
CreateQuestionGroupViewControllerDelegate {
questionGroupCaretaker.questionGroups.append(questionGroup)
raywenderlich.com 153
Design Patterns by Tutorials Chapter 9: Builder Pattern
try? questionGroupCaretaker.save()
To handle cancellation, you simply dismiss the view controller. To handle creation,
you append the new QuestionGroup to the
questionGroupCaretaker.questionGroups, request it to save(), dismiss the view
controller and refresh the table view.
You also need to actually set the delegate property when the segue to
CreateQuestionGroupViewController is triggered. Replace prepare(for
segue:sender:) with the following:
// 2
} else if let navController =
segue.destination as? UINavigationController,
let viewController =
navController.topViewController as?
CreateQuestionGroupViewController {
viewController.delegate = self
}
// 3
// Whatevs... skip anything else
}
raywenderlich.com 154
Design Patterns by Tutorials Chapter 9: Builder Pattern
Build and run, tap + and then tap Cancel. The view controller will now be dismissed
correctly.
If you press Save, though, nothing happens! This is because you haven’t added code
to actually create a QuestionGroup yet. You need to use the builder pattern to do
this.
To start, right-click on the yellow RabbleWabble group and select New Group.
Enter Builders for its name and move it below the AppDelegate group. This makes
it clear to other developers that you’re using the builder pattern.
Right-click on your newly-added Builders group, select New File. Then choose iOS ▸
Swift File and click Next. Then enter QuestionGroupBuilder.swift for its name and
press Create to add the new file.
raywenderlich.com 155
Design Patterns by Tutorials Chapter 9: Builder Pattern
What can you use to create these complex child object? Another builder, of course!
You’ll create this builder first. Replace the contents of QuestionGroupBuilder.swift
with the following:
QuestionBuilder has properties for all of the inputs needed to create a Question:
answer, hint and prompt. Initially, each of these is set to an empty string. Whenever
you call build(), it validates that answer and prompt have been set. If either aren’t
set, it throws a custom error; hint is optional within the app, so it’s okay if its empty.
Otherwise, it returns a new Question.
// 1
public var questions = [QuestionBuilder()]
public var title = ""
// 2
public func addNewQuestion() {
let question = QuestionBuilder()
questions.append(question)
}
// 3
public func build() throws -> QuestionGroup {
guard self.title.count > 0 else {
throw Error.missingTitle
raywenderlich.com 156
Design Patterns by Tutorials Chapter 9: Builder Pattern
2. As its name implies, you’ll use addNewQuestion() to create and append a new
QuestionBuilder onto questions. Similarly, removeQuestion(at:) will remove
a QuestionBuilder by index from questions.
3. Whenever you call build(), the QuestionBuilder validates that title has been
set and there’s at least one QuestionBuilder within questions. If not, it throws
an error. If both conditions pass, it attempts to create Questions by calling
build() on each QuestionBuilder. This too can fail and result in an error
thrown by an invalid Question. If everything goes well, it returns a new
QuestionGroup.
Since you set all of the properties of QuestionGroupBuilder to default values, you
don’t have to pass anything to create a QuestionGroupBuilder. Nice and easy!
raywenderlich.com 157
Design Patterns by Tutorials Chapter 9: Builder Pattern
return questionGroupBuilder.questions.count + 2
} else if row == 1 {
The previous code assumed there was only one QuestionBuilder cell. Here, you
update this to take into account that there could be several.
cell.titleTextField.text = questionGroupBuilder.title
return questionGroupBuilder.questions[indexPath.row - 1]
}
This is a helper method to get the QuestionBuilder for a given index path. You’ll
need this a few times hereafter, so it’s beneficial to define this in only one place.
raywenderlich.com 158
Design Patterns by Tutorials Chapter 9: Builder Pattern
You can now add additional QuestionBuilder instances and cells to the table view.
Awesome!
raywenderlich.com 159
Design Patterns by Tutorials Chapter 9: Builder Pattern
If you input text for several questions and create many new cells thereafter, you’ll
notice that your text is gone after scrolling the table view. This is because you
haven’t actually persisted the text input into the cells onto each QuestionBuilder.
You’ll use this helper to determine the QuestionBuilder for a given cell, which you
do so by finding the cell’s indexPath and then using the helper method you wrote
earlier for questionBuilder(for indexPath:).
This sets the answer on the QuestionBuilder for the given cell.
These set the hint and prompt on the QuestionBuilder for the given cell.
raywenderlich.com 160
Design Patterns by Tutorials Chapter 9: Builder Pattern
questionGroupBuilder.title = text
However, the Save button still doesn’t do anything. It’s time for you to fix this.
Replace savePressed(_:) with the following:
} catch {
displayMissingInputsAlert()
}
}
If it throws an error, you alert the user to input all required fields. Build and run,
navigate to CreateQuestionGroupViewController, enter a title, and create a
couple of questions.
raywenderlich.com 161
Design Patterns by Tutorials Chapter 9: Builder Pattern
Tap Save, and you’ll then see your brand-new QuestionGroup added to the Select
Question Group listing!
Key points
You learned the builder pattern in this chapter. Here are its key points:
• The builder pattern is great for creating complex objects in a step-by-step fashion.
It involves three objects: the director, product and builder.
• The director accepts inputs and coordinates with the builder; the product is the
complex object that’s created; and the builder takes step-by-step inputs and
creates the product.
raywenderlich.com 162
Design Patterns by Tutorials Chapter 9: Builder Pattern
Each of these are possible using the existing patterns you learned in this
“Fundamental Design Patterns” section. Feel free to continue building out Rabble
Wabble as much as you like.
If you’ve worked through this entire first section, congratulations are in order:
You’ve learned many of the most commonly used iOS design patterns!
But your design patterns journey doesn’t stop here. Continue onto the next section
to learn about intermediate design patterns, including MVVM, Adapter, Factory and
more!
raywenderlich.com 163
Section III: Intermediate
Design Patterns
This section covers design patterns that are also common, but are used less
frequently than the fundamental design patterns in Section II.
Many of these patterns work well together, but not all. You'll create two projects in
this section as you explore these intermediate patterns.
raywenderlich.com 164
10 Chapter 10: Model-View-
ViewModel Pattern
By Jay Strawn
• Views display visual elements and controls on the screen. They’re typically
subclasses of UIView.
• View models transform model information into values that can be displayed on a
view. They’re usually classes, so they can be passed around as references.
Does this pattern sound familiar? Yep, it’s very similar to Model-View-Controller
(MVC). Note that the class diagram at the top of this page includes a view controller;
view controllers do exist in MVVM, but their role is minimized.
In this chapter, you’ll learn how to implement view models and organize your
projects to include them. You’ll start with a simple example on what a view model
does, then you’ll take a MVC project and refactor it into MVVM.
raywenderlich.com 165
Design Patterns by Tutorials Chapter 10: Model-View-ViewModel Pattern
This pattern compliments MVC especially well. Without view models, you’d likely
put model-to-view transformation code in your view controller. However, view
controllers are already doing quite a bit: handling viewDidLoad and other view
lifecycle events, handling view callbacks via IBActions and several other tasks as
well.
This leads to what developers jokingly refer to as “MVC: Massive View Controller".
How can you avoid overstuffing your view controllers? It’s easy — use other patterns
besides MVC! MVVM is a great way to slim down massive view controllers that
require several model-to-view transformations.
Playground example
Open IntermediateDesignPatterns.xcworkspace in the Starter directory, and then
open the MVVM page.
For the example, you’ll make a “Pet View” as part of an app that adopts pets. Add the
following after Code Example:
import PlaygroundSupport
import UIKit
// MARK: - Model
public class Pet {
public enum Rarity {
case common
case uncommon
raywenderlich.com 166
Design Patterns by Tutorials Chapter 10: Model-View-ViewModel Pattern
case rare
case veryRare
}
Here, you define a model named Pet. Every pet has a name, birthday, rarity and
image. You need to show these properties on a view, but birthday and rarity aren’t
directly displayable. They’ll need to be transformed by a view model first.
// MARK: - ViewModel
public class PetViewModel {
// 1
private let pet: Pet
private let calendar: Calendar
// 2
public var name: String {
return pet.name
}
// 3
public var ageText: String {
let today = calendar.startOfDay(for: Date())
let birthday = calendar.startOfDay(for: pet.birthday)
raywenderlich.com 167
Design Patterns by Tutorials Chapter 10: Model-View-ViewModel Pattern
// 4
public var adoptionFeeText: String {
switch pet.rarity {
case .common:
return "$50.00"
case .uncommon:
return "$75.00"
case .rare:
return "$150.00"
case .veryRare:
return "$500.00"
}
}
}
1. First, you created two private properties called pet and calendar, setting both
within init(pet:).
2. Next, you declared two computed properties for name and image, where you
return the pet’s name and image respectively. This is the simplest transformation
you can perform: returning a value without modification. If you wanted to change
the design to add a prefix to every pet’s name, you could easily do so by
modifying name here.
3. Next, you declared ageText as another computed property, where you used
calendar to calculate the difference in years between the start of today and the
pet’s birthday and return this as a String followed by "years old". You’ll be
able to display this value directly on a view without having to perform any other
string formatting.
Now you need a UIView to display the pet’s information. Add the following code to
the end of the playground:
// MARK: - View
public class PetView: UIView {
raywenderlich.com 168
Design Patterns by Tutorials Chapter 10: Model-View-ViewModel Pattern
childFrame.origin.y += childFrame.height + 16
childFrame.size.height = 30
nameLabel = UILabel(frame: childFrame)
nameLabel.textAlignment = .center
childFrame.origin.y += childFrame.height
ageLabel = UILabel(frame: childFrame)
ageLabel.textAlignment = .center
childFrame.origin.y += childFrame.height
adoptionFeeLabel = UILabel(frame: childFrame)
adoptionFeeLabel.textAlignment = .center
super.init(frame: frame)
backgroundColor = .white
addSubview(imageView)
addSubview(nameLabel)
addSubview(ageLabel)
addSubview(adoptionFeeLabel)
}
@available(*, unavailable)
public required init?(coder: NSCoder) {
fatalError("init?(coder:) is not supported")
}
}
Here, you create a PetView with four subviews: an imageView to display the pet’s
image and three other labels to display the pet’s name, age and adoption fee.
You create and position each view within init(frame:). Lastly, you throw a
fatalError within init?(coder:) to indicate it’s not supported.
raywenderlich.com 169
Design Patterns by Tutorials Chapter 10: Model-View-ViewModel Pattern
You’re ready to put these classes into action! Add the following code to the end of
the playground:
// MARK: - Example
// 1
let birthday = Date(timeIntervalSinceNow: (-2 * 86400 * 366))
let image = UIImage(named: "stuart")!
let stuart = Pet(name: "Stuart",
birthday: birthday,
rarity: .veryRare,
image: image)
// 2
let viewModel = PetViewModel(pet: stuart)
// 3
let frame = CGRect(x: 0, y: 0, width: 300, height: 420)
let view = PetView(frame: frame)
// 4
view.nameLabel.text = viewModel.name
view.imageView.image = viewModel.image
view.ageLabel.text = viewModel.ageText
view.adoptionFeeLabel.text = viewModel.adoptionFeeText
// 5
PlaygroundPage.current.liveView = view
To see this in action, select Editor ▸ Live View to check out the rendered view.
raywenderlich.com 170
Design Patterns by Tutorials Chapter 10: Model-View-ViewModel Pattern
What type of pet is Stuart exactly? He’s a cookie monster, of course! They’re very
rare.
There’s one final improvement you can make to this example. Add the following
extension right after the class closing curly brace for PetViewModel:
extension PetViewModel {
public func configure(_ view: PetView) {
view.nameLabel.text = name
view.imageView.image = image
view.ageLabel.text = ageText
view.adoptionFeeLabel.text = adoptionFeeText
}
}
You’ll use this method to configure the view using the view model instead of doing
this inline.
// 4
view.nameLabel.text = viewModel.name
view.imageView.image = viewModel.image
view.ageLabel.text = viewModel.ageText
view.adoptionFeeLabel.text = viewModel.adoptionFeeText
viewModel.configure(view)
This is a neat way to put all of the view configuration logic into the view model. You
may or may not want to do this in practice. If you’re only using the view model with
one view, then it can be useful to put the configure method into the view model.
raywenderlich.com 171
Design Patterns by Tutorials Chapter 10: Model-View-ViewModel Pattern
However, if you’re using the view model with more than one view, then you might
find that putting all that logic in the view model clutters it. Having the configure
code separately for each view may be simpler in that case.
Hey Stuart, are you going to share that cookie? No? Aww, come on...!
Furthermore, MVVM may not be very useful when you first create your application.
MVC may be a better starting point. As your app’s requirements change, you’ll likely
need to choose different design patterns based on your changing requirements. It’s
okay to introduce MVVM later in an app’s lifetime when you really need it.
Tutorial project
Throughout this section, you’ll add functionality to an app called Coffee Quest.
This app displays nearby coffee shops provided by Yelp. It uses CocoaPods to pull in
YelpAPI, a helper library for searching Yelp. If you haven’t used CocoaPods before,
that’s OK! Everything you need has been included for you in the starter project. The
only thing you need to remember is to open CoffeeQuest.xcworkspace, instead of
the CoffeeQuest.xcodeproj file.
Note: If you’d like to learn more about CocoaPods, read our tutorial about it
here: http://bit.ly/cocoapods-tutorial.
Before you can run the app, you’ll first need to register for a Yelp API key.
raywenderlich.com 172
Design Patterns by Tutorials Chapter 10: Model-View-ViewModel Pattern
• https://www.yelp.com/developers/v3/manage_app
Create an account if you don’t have one, or sign in. Next, enter the following in the
Create App form (or if you’ve created an app before, use your existing API Key):
• I have read and accepted the Yelp API Terms: check this
Press Create New App to continue, and you should see a success message:
raywenderlich.com 173
Design Patterns by Tutorials Chapter 10: Model-View-ViewModel Pattern
The simulator’s default location is set to San Francisco. Wow, there are a lot of coffee
shops in that city!
Note: You can change the location of the simulator by clicking Debug ▸
Location and then selecting a different option.
These map pins are kind of boring. Wouldn’t it be great if they showed which coffee
shops were actually good?
First, you need to give this class a better name. Right click on MapPin at the top of
the file and select Refactor ▸ Rename.
raywenderlich.com 174
Design Patterns by Tutorials Chapter 10: Model-View-ViewModel Pattern
Click Rename and enter BusinessMapViewModel for the new name. This will
rename both the class name and file name in the File hierarchy.
Next, select the Models group in the File hierarchy and press Enter to edit its
name. Rename this to ViewModels.
Finally, click on the yellow CoffeeQuest group and select Sort by name. Ultimately,
your File hierarchy should look like this:
This makes it obvious to other developers you’re using the MVVM pattern. Clarity is
good!
import UIKit
You import UIKit here since you’re going to add components which require UIKit.
Next, add the following properties after the existing ones; ignore the resulting
compiler errors for now:
You’ll use image instead of the default pin image, and you’ll display
ratingDescription as a subtitle whenever the user taps the annotation.
raywenderlich.com 175
Design Patterns by Tutorials Chapter 10: Model-View-ViewModel Pattern
self.rating = rating
self.image = image
self.ratingDescription = "\(rating) stars"
}
You accept image via this initializer and set ratingDescription from the rating.
Next, add the following computed property to the end of the MKAnnotation
extension:
This tells the map to use ratingDescription as the subtitle shown on annotation
callout when one is selected. Now you can fix the compiler error. Open
ViewController.swift and scroll down to the end of the file.
// 1
switch rating {
case 0.0..<3.5:
image = UIImage(named: "bad")!
case 3.5..<4.0:
image = UIImage(named: "meh")!
case 4.0..<4.75:
image = UIImage(named: "good")!
case 4.75...5.0:
image = UIImage(named: "great")!
default:
image = UIImage(named: "bad")!
}
raywenderlich.com 176
Design Patterns by Tutorials Chapter 10: Model-View-ViewModel Pattern
coordinate: coordinate,
name: name,
rating: rating,
image: image)
mapView.addAnnotation(annotation)
}
}
This method is similar to before, except now you’re switching on rating (see // 1)
to determine which image to use. High-quality caffeine is like catnip for developers,
so you label anything less than 3.5 stars as “bad”. You gotta have high standards,
right? ;]
Build and run your app. It should now look... the same? What gives?
The map doesn’t know about image. Rather, you’re expected to override a delegate
method to provide custom pin annotation images. That’s why it looks the same as
before.
annotationView.image = viewModel.image
annotationView.canShowCallout = true
return annotationView
}
This simply creates an MKAnnotationView, which shows the correct image for the
given annotation; this is one of your BusinessMapViewModel objects.
raywenderlich.com 177
Design Patterns by Tutorials Chapter 10: Model-View-ViewModel Pattern
Build and run, and you should see the custom images! Tap on one, and you’ll see the
coffee shop’s name and rating.
It appears most San Francisco coffee shops are actually 4 stars or above, and you can
now find the very best shops at a glance.
Key points
You learned about the Model-View-ViewModel (MVVM) pattern in this chapter. Here
are its key points:
• MVVM helps slim down view controllers, making them easier to work with. Thus
combatting the "Massive View Controller" problem.
• View models are classes that take objects and transform them into different
objects, which can be passed into the view controller and displayed on the view.
They’re especially useful for converting computed properties such as Date or
Decimal into a String or something else that actually can be shown in a UILabel
or UIView.
• If you’re only using the view model with one view, it can be good to put all the
configurations into the view model. However, if you’re using more than one view,
you might find that putting all the logic in the view model clutters it. Having the
configure code separated into each view may be simpler.
raywenderlich.com 178
Design Patterns by Tutorials Chapter 10: Model-View-ViewModel Pattern
• MVC may be a better starting point if your app is small. As your app’s requirements
change, you’ll likely need to choose different design patterns based on your
changing requirements.
You've added a really nice feature to Coffee Quest that shows coffee shops by rating!
However, there's still a lot more you can do with this app. Continue onto the next
chapter to learn about the factory pattern and continue building out Coffee Quest.
raywenderlich.com 179
11 Chapter 11: Factory
Pattern
By Jay Strawn
The factory pattern is a creational pattern that provides a way to make objects
without exposing creation logic. It involves two types:
Technically, there are multiple “flavors” of this pattern, including simple factory,
abstract factory and others. However, each of these share a common goal: to isolate
object creation logic within its own construct.
In this chapter, you’ll be adding onto the previous chapter’s project, Coffee Quest, to
learn about a simple factory. It creates objects of a common type or protocol, and
the factory’s type itself is known and used by consumers directly.
raywenderlich.com 180
Design Patterns by Tutorials Chapter 11: Factory Pattern
A factory is very useful when you have a group of related products, such as
polymorphic subclasses or several objects that implement the same protocol. For
example, you can use a factory to inspect a network response and turn it into a
concrete model subtype.
A factory is also useful when you have a single product type, but it requires
dependencies or information to be provided to create it. For example, you can use a
factory to create a “job applicant response” email: The factory can generate email
details depending on whether the candidate was accepted, rejected or needs to be
interviewed.
Playground example
Open IntermediateDesignPattern.xcworkspace in the Starter directory, or
continue from your own playground workspace from the last chapter, then open the
Factory page. As mentioned above, you’ll create a factory to generate job applicant
response emails. Add the following after Code Example:
import Foundation
raywenderlich.com 181
Design Patterns by Tutorials Chapter 11: Factory Pattern
Here, you’ve defined JobApplicant and Email models. An applicant has a name,
email, and four types of status. The email’s subject and messageBody will be
different depending on an applicant’s status.
// 1
public struct EmailFactory {
// 2
public let senderEmail: String
// 3
public func createEmail(to recipient: JobApplicant) -> Email {
let subject: String
let messageBody: String
switch recipient.status {
case .new:
subject = "We Received Your Application"
messageBody =
"Thanks for applying for a job here! " +
"You should hear from us in 17-42 business days."
case .interview:
subject = "We Want to Interview You"
messageBody =
"Thanks for your resume, \(recipient.name)! " +
"Can you come in for an interview in 30 minutes?"
case .hired:
subject = "We Want to Hire You"
messageBody =
"Congratulations, \(recipient.name)! " +
"We liked your code, and you smelled nice. " +
"We want to offer you a position! Cha-ching! $$$"
case .rejected:
subject = "Thanks for Your Application"
messageBody =
"Thank you for applying, \(recipient.name)! " +
"We have decided to move forward " +
"with other candidates. " +
"Please remember to wear pants next time!"
}
raywenderlich.com 182
Design Patterns by Tutorials Chapter 11: Factory Pattern
2. Create a public property for senderEmail. You set this property within the
EmailFactory initializer.
Now the email templates have been constructed, it’s time to use your factory on a
prospective applicant!
let emailFactory =
EmailFactory(senderEmail: "RaysMinions@RaysCoffeeCo.com")
// New
print(emailFactory.createEmail(to: jackson), "\n")
// Interview
jackson.status = .interview
print(emailFactory.createEmail(to: jackson), "\n")
// Hired
jackson.status = .hired
print(emailFactory.createEmail(to: jackson), "\n")
Here, you’re creating a new JobApplicant named “Jackson Smith”. Next, you create
a new EmailFactory instance, and finally, you use the instance to generate emails
based on the JobApplicant object status property.
Looks like Jackson will be getting a job soon. He probably set himself apart from
other applicants by impressing Ray’s Coffee Co. with his extensive knowledge of
design patterns!
raywenderlich.com 183
Design Patterns by Tutorials Chapter 11: Factory Pattern
Alternatively, if your object requires a series of steps to build it, you may be better off
using the builder pattern or another pattern instead.
Tutorial project
You’ll continue the Coffee Quest app from the previous chapter. If you skipped the
previous chapter, or you want a fresh start, open Finder and navigate to where you
downloaded the resources for this chapter. Then, open
starter\CoffeeQuest\CoffeeQuest.xcworkspace (not .xcodeproj) in Xcode.
Note: If you opt to start fresh, then you’ll need to open up APIKeys.swift and
add your Yelp API key. See Chapter 10, “Model-View-ViewModel Pattern” for
instructions on how to generate this.
You’ll use the factory pattern to improve the mechanism behind changing icons
based on their Yelp rating.
First, right-click on the CoffeeQuest group and create a new group named Factories.
Next, right-click on the Factories group and select New File.... Select iOS ▸ Swift
File and click Next. Call it AnnotationFactory.swift and click Create. Your folder
structure should look similar to the following:
raywenderlich.com 184
Design Patterns by Tutorials Chapter 11: Factory Pattern
import UIKit
import MapKit
import YelpAPI
guard
let yelpCoordinate = business.location.coordinate else {
return nil
}
let coordinate =
CLLocationCoordinate2D(
latitude: yelpCoordinate.latitude,
longitude: yelpCoordinate.longitude)
This should look familiar (if you’ve read the previous chapters!). It’s the code added
in the previous chapter where you create the BusinessMapViewModel for the given
coffee shop.
This is your first factory! When you employ the factory pattern, it will often feel like
you’re factoring out code, like you are here. Any other component of your app that
wants to create a BusinessMapViewModel from a coffee shop model can do so now.
raywenderlich.com 185
Design Patterns by Tutorials Chapter 11: Factory Pattern
This means when the project gets larger, changing map annotations is less likely to
break coupled modules because all the transformation logic is contained in one
place!
Add a new level of coffee rating to your factory called "terrible" for anything less
than 3 stars. I know; I’m a coffee snob! Your switch statement should look like the
following:
switch rating {
case 0.0..<3.0:
image = UIImage(named: "terrible")!
case 3.0..<3.5:
image = UIImage(named: "bad")!
case 3.5..<4.0:
image = UIImage(named: "meh")!
case 4.0..<4.75:
image = UIImage(named: "good")!
case 4.75...5.0:
image = UIImage(named: "great")!
default:
image = UIImage(named: "bad")!
}
This is an example of how factories cannot be closed for modification, as you need to
add and remove cases to make different objects.
Just as you did in the view controller, you’re switching on rating to determine which
image to use.
raywenderlich.com 186
Design Patterns by Tutorials Chapter 11: Factory Pattern
Time for your view controller to actually use this factory! The factory creates a
businessMapViewModel for each business returned in the Yelp search.
Key points
You learned about the factory pattern in this chapter. Here are its key points:
• A factory’s goal is to isolate object creation logic within its own construct.
• A factory is most useful if you have a group of related products, or if you cannot
create an object until more information is supplied (such as completing a network
call, or waiting on user input).
• The factory method adds a layer of abstraction to create objects, which reduces
duplicate code.
You’ve once again slimmed down the view controller. Not much has changed visually
in your app, but implementing a factory allows for easy changes as projects
inevitably grow larger.
You might have noticed that your factory can only take a YLPBusiness from the Yelp
API. What if you wanted to switch to a different service, such as Google Places? It
would be a good idea to rewrite your code so you can take any third-party class and
convert it into a more generic Business type. You’ll do this in the next chapter using
an adapter pattern.
raywenderlich.com 187
12 Chapter 12: Adapter
Pattern
By Jay Strawn
The adapter pattern is a behavioral pattern that allows incompatible types to work
together. It involves four components:
1. An object using an adapter is the object that depends on the new protocol.
raywenderlich.com 188
Design Patterns by Tutorials Chapter 12: Adapter Pattern
3. A legacy object existed before the protocol was made and cannot be modified
directly to conform to it.
4. An adapter is created to conform to the protocol and passes calls onto the legacy
object.
A great example of a physical adapter comes to mind when you consider the latest
iPhone — there’s no headphone jack! If you want to plug your 3.5mm headphones
into the lightning port, you need an adapter with a lightning connector on one end
and a 3.5mm jack on the other.
This is essentially what the Adapter Pattern is about: connecting two elements that
otherwise won’t “fit” with each other.
You can create an adapter either by extending an existing class, or creating a new
adapter class. This chapter will show you how to do both.
raywenderlich.com 189
Design Patterns by Tutorials Chapter 12: Adapter Pattern
Playground example
Open IntermediateDesignPattern.xcworkspace in the Starter directory, or
continue from your own playground workspace from the last chapter, then open the
Adapter page.
For this example, you’ll adapt a third-party authentication service to work with an
app’s internal authentication protocol. Add the following code, after Code Example:
import UIKit
The login function returns a GoogleUser that has a string property called token. You
might pass this token via a GET request like this:
• https://api.example.com/items/id123?token=special-token-value
Or you might use this via Bearer authentication, such as a JSON Web Token (see
https://jwt.io/). If you're not familiar with these formats, that's okay! They aren't
required knowledge for this chapter, but rather, they simply illustrate common use
cases.
raywenderlich.com 190
Design Patterns by Tutorials Chapter 12: Adapter Pattern
This is the authentication protocol for your app which acts as the new protocol. It
requires an email and password. If login succeeds, it calls success with a User and
Token. Otherwise, it calls failure with an Error.
The app will use this protocol instead of GoogleAuthenticator directly, and it gains
many benefits by doing so. For example, you can easily support multiple
authentication mechanisms – Google, Facebook and others – simply by having them
all conform to the same protocol.
// MARK: - Adapter
// 1
public class GoogleAuthenticatorAdapter: AuthenticationService {
// 2
private var authenticator = GoogleAuthenticator()
// 3
public func login(email: String,
password: String,
success: @escaping (User, Token) -> Void,
failure: @escaping (Error?) -> Void) {
raywenderlich.com 191
Design Patterns by Tutorials Chapter 12: Adapter Pattern
// 4
guard let googleUser = googleUser else {
failure(error)
return
}
// 5
let user = User(email: googleUser.email,
password: googleUser.password)
5. Otherwise, you create user and token from the googleUser and call success.
// MARK: - Properties
public var authService: AuthenticationService!
// MARK: - Views
var emailTextField = UITextField()
var passwordTextField = UITextField()
raywenderlich.com 192
Design Patterns by Tutorials Chapter 12: Adapter Pattern
// 3
public func login() {
guard let email = emailTextField.text,
let password = passwordTextField.text else {
print("Email and password are required inputs!")
return
}
authService.login(
email: email,
password: password,
success: { user, token in
print("Auth succeeded: \(user.email), \(token.value)")
},
failure: { error in
print("Auth failed with error: no error provided")
})
}
}
3. Lastly, you create a login method that calls authService.login with the e-mail
and password from the text fields.
// MARK: - Example
let viewController = LoginViewController.instance(
with: GoogleAuthenticatorAdapter())
viewController.emailTextField.text = "user@example.com"
viewController.passwordTextField.text = "password"
viewController.login()
raywenderlich.com 193
Design Patterns by Tutorials Chapter 12: Adapter Pattern
If you wanted to support other APIs like Facebook login, you could easily make
adapters for them as well and have the LoginViewController use them exactly the
same way without requiring any code changes.
Be careful about implementing the adapter pattern unless you recognize there's a
real possibility for change. If there isn't, consider if it makes sense to use the
underlying type directly.
Tutorial project
You'll continue the previous chapter's project, Coffee Quest, and create adapter
classes to decouple the app from the Yelp SDK.
If you skipped the previous chapter, or you want a fresh start, open Finder and
navigate to where you downloaded the resources for this chapter. Then, open
starter\CoffeeQuest\CoffeeQuest.xcworkspace (not .xcodeproj) in Xcode.
Note: If you opt to start fresh, then you’ll need to open up APIKeys.swift and
add your Yelp API key. See Chapter 10, “Model-View-ViewModel Pattern” for
instructions on how to generate this.
raywenderlich.com 194
Design Patterns by Tutorials Chapter 12: Adapter Pattern
If the SDK ever changed, you'd need to update the app in multiple places. This isn't a
big problem right now because the app is small. However, it's likely to cause
problems later if you continued developing the app and using the SDK directly in
many places.
You'll first create new groups and files to organize your new types.
Right click on the CoffeeQuest group, select New Group and name it Adapters.
Repeat this to create a second group named Models and a third group named
Protocols.
Next, right click on Adapters and select New File.... Select iOS ▸ Swift File and
click Next. Call it YLPClient+BusinessSearchClient.swift and click Create.
Repeat this proccess to create a new filed called Business.swift under Models and
BusinessSearchClient.swift under Protocols.
raywenderlich.com 195
Design Patterns by Tutorials Chapter 12: Adapter Pattern
You'll need to use Business in the protocol and adapter, so you'll create this type
first. Replace the contents of Business.swift with the following:
import MapKit
You'll use this model instead of YLPBusiness directly. By doing so, you could later
easily use other APIs like Google Places or MapKit and map their outputs to
Business objects.
import MapKit
How exactly will you use YLPSearch? That's where the adapter comes in. Finally,
replace the contents of YLPClient+BusinessSearchClient.swift with the following:
import MapKit
import YelpAPI
// 1
extension YLPClient: BusinessSearchClient {
// 2
let yelpCoordinate = YLPCoordinate(
latitude: coordinate.latitude,
longitude: coordinate.longitude)
raywenderlich.com 196
Design Patterns by Tutorials Chapter 12: Adapter Pattern
search(
with: yelpCoordinate,
term: term,
limit: limit,
offset: offset,
sort: .bestMatched,
completionHandler: { (searchResult, error) in
// 3
guard let searchResult = searchResult,
error == nil else {
failure(error)
return
}
// 4
let businesses =
searchResult.businesses.adaptToBusinesses()
success(businesses)
})
}
}
// 5
extension Array where Element: YLPBusiness {
raywenderlich.com 197
Design Patterns by Tutorials Chapter 12: Adapter Pattern
5. You extend Array where Element is YLPBusiness. This allows you to create a
convenience method to convert a YLPBusiness array into a Business array.
1. You changed private to public. Thereby, if you later wanted to use a different
type of BusinessSearchClient, you'd be able to get a reference to the view
controller and set its client.
By making this change, you've actually decoupled the view controller from
YLPClient! It now depends on BusinessSearchClient, and you can easily replace
this with any other conforming type.
This is another step towards decoupling from from the Yelp SDK. However, you now
have to fix the resulting compiler errors from these changes.
raywenderlich.com 198
Design Patterns by Tutorials Chapter 12: Adapter Pattern
// 1
client.search(
with: mapView.userLocation.coordinate,
term: "coffee",
limit: 35, offset: 0,
success: { [weak self] businesses in
guard let self = self else { return }
// 2
self.businesses = businesses
DispatchQueue.main.async {
self.addAnnotations()
}
}, failure: { error in
// 3
print("Search failed: \(String(describing: error))")
})
2. If the search succeeds, you set self.businesses to the fetched businesses. You
then dispatch to the main queue to add the annotations to the map.
3. If the search fails, you simply print the error to the console.
raywenderlich.com 199
Design Patterns by Tutorials Chapter 12: Adapter Pattern
3. You get the coordinate from the business.location, which doesn't require a
guard because it's not an optional type.
There's just one final change you need to make to get the project to compile again.
Open ViewController.swift, and you'll find there's a compiler error within
addAnnoations().
let viewModel =
annotationFactory.createBusinessMapViewModel(for: business)
This removes the guard because the return type is no longer optional.
Now that you’ve adapted your code, build and run to confirm everything works as
expected.
raywenderlich.com 200
Design Patterns by Tutorials Chapter 12: Adapter Pattern
Key points
You learned about the adapter pattern in this chapter. Here are its key points:
• The adapter pattern is useful when working with classes from third-party libraries
that cannot be modified. You can use protocols to have them work with the
project’s custom classes.
• To use an adapter, you can either extend the legacy object, or make a new
adapter class.
• The adapter pattern allows you to reuse a class even if it lacks required
components or has incompatible components with required objects.
Coffee Quest is getting better with every refactor! Once again, not much has changed
in the app from a visual perspective, but now it will be so much easier to add other
APIs and have them work seamlessly with your Business objects.
In the next chapter, you’ll learn about the iterator pattern. The current mechanism
of iterating through businesses in the view controller is less than ideal. You’ll learn
how to extend classes to make them iterable and easier to manage.
raywenderlich.com 201
13 Chapter 13: Iterator
Pattern
By Jay Strawn
The iterator pattern is a behavioral pattern that provides a standard way to loop
through a collection. This pattern involves two types:
1. The Swift IteratorProtocol defines a type that can be iterated using a for in
loop.
2. The iterator object is the type you want to make iterable. Instead of conforming
to IteratorProtocol directly, however, you can conform to Sequence, which
itself conforms to IteratorProtocol. By doing so, you’ll get many higher-order
functions, including map, filter and more, for free.
What does “for free” mean? It means these useful built-in functions can be used on
any object that conforms to Sequence, which can save you from writing your own
sorting, splitting and comparing algorithms.
raywenderlich.com 202
Design Patterns by Tutorials Chapter 13: Iterator Pattern
Playground example
Open IntermediateDesignPattern.xcworkspace in the Starter directory, or
continue from your own playground workspace from the last chapter, then open the
Iterator page.
import Foundation
// 1
public struct Queue<T> {
private var array: [T?] = []
// 2
private var head = 0
// 3
public var isEmpty: Bool {
return count == 0
}
// 4
public var count: Int {
return array.count - head
}
// 5
public mutating func enqueue(_ element: T) {
raywenderlich.com 203
Design Patterns by Tutorials Chapter 13: Iterator Pattern
array.append(element)
}
// 6
public mutating func dequeue() -> T? {
guard head < array.count,
let element = array[head] else {
return nil
}
array[head] = nil
head += 1
return element
}
}
Here, you’ve created a queue containing an array. Here’s a breakdown of the code:
2. The head of the queue will be the index of the first element in the array.
5. You have created an enqueue function for adding elements to the queue.
6. The dequeue function is for removing the first element of the queue. This
function’s logic is set up to help keep you from having nil objects in your array.
Next, add the following code to the end of the playground to test the queue:
enum PriorityType {
case low
case medium
case high
}
raywenderlich.com 204
Design Patterns by Tutorials Chapter 13: Iterator Pattern
The queue has four items, which becomes three once you’ve successfully dequeued
the first ticket.
In a real use-case scenario, you’ll definitely want to be able to sort these tickets by
priority. With the way things are now, you’d need to write a sorting function with a
lot of if statements. Save yourself some time and instead use one of Swift’s built-in
sorting functions.
Currently, if you attempt to use a for in loop or sorted() on queue, you’ll get an
error. You need to make your Queue struct conform to the Sequence protocol. Add
the following beneath your Queue struct:
Like with dequeue, you want to make sure you’re not exposing nil objects and only
iterate through non-empty values.
There are two required parts when conforming the Sequence protocol. The first is
your associated type, which is your Iterator. In the code above, IndexingIterator is
your associated type, which is the default iterator for any collection that doesn’t
declare its own.
raywenderlich.com 205
Design Patterns by Tutorials Chapter 13: Iterator Pattern
The second part is the Iterator protocol, which is the required makeIterator
function. It constructs an iterator for your class or struct.
Before you use a sequence-specific sort function, scroll back up and add the
following extension underneath the Ticket struct:
extension Ticket {
var sortIndex : Int {
switch self.priority {
case .low:
return 0
case .medium:
return 1
case .high:
return 2
}
}
}
Assigning numeric values to the priority levels will make sorting easier. Sort the
tickets using their sortIndex as reference, add the following code at the end of the
file:
print("\n")
print("Tickets sorted by priority:")
for ticket in sortedQueue {
print(ticket?.description ?? "No Description")
}
raywenderlich.com 206
Design Patterns by Tutorials Chapter 13: Iterator Pattern
The sorting function returns a regular array, so to have a sorted queue, you enqueue
each array item into a new queue. The ability to sort through groups so easily is a
powerful feature, and becomes more valuable as your lists and queues get larger.
Even if you need a custom iterator, it’s almost always better to conform to Sequence
and provide custom next() logic, instead of conforming to IteratorProtocol
directly.
You can find more information about IteratorProtocol and how it works with
Sequence at http://bit.ly/iterator-protocol.
Tutorial project
You’ll continue building onto Coffee Quest from the previous chapter. You’ll finally
be adding functionality to the switch in the upper-right corner!
If you skipped the previous chapter, or you want a fresh start, open Finder and
navigate to where you downloaded the resources for this chapter. Then, open
starter\CoffeeQuest\CoffeeQuest.xcworkspace (not .xcodeproj!) in Xcode.
Note: If you opt to start fresh, then you’ll need to open up APIKeys.swift and
add your Yelp API key. See Chapter 10, “Model-View-ViewModel Pattern” for
instructions on how to generate this.
Go to the Models group and select File ▸ New ▸ File... and choose Swift File. Name
the new file Filter.swift. Create a Filter struct by adding the following code
underneath import Foundation:
raywenderlich.com 207
Design Patterns by Tutorials Chapter 13: Iterator Pattern
This struct holds an array of Business objects and a filter closure. You can
instantiate the class with identity(), adjust the filter’s parameters with
starRating(), and apply the filter with filterBusinesses().
You also make Filter conform to Sequence via an extentension, wherein you create
an iterator from the return value of filterBusinesses().
With your filter wrapper set up, you can now use this logic in the ViewController.
Open ViewController.swift. Add the following line of code to the list of properties
at the top:
This creates a new property for filter and sets its default value to
Filter.identity().
Next, in the searchForBusinesses function, add the following right underneath the
line that reads self.businesses = searchResult.businesses and above the line
with DispatchQueue.main.async:
self.filter.businesses = businesses
raywenderlich.com 208
Design Patterns by Tutorials Chapter 13: Iterator Pattern
Next, you'll set the filter based on the top-right switch. Add the following code
inside businessFilterToggleChanged(_:):
if sender.isOn {
// 1
filter = Filter.starRating(atLeast: 4.0)
} else {
// 2
filter = Filter.identity()
}
// 3
filter.businesses = businesses
// 4
addAnnotations()
2. If the switch isn't on, you set filter to Filter.identity(). This will show all
coffee shops.
3. In either case, you set filter.businesses to the existing businesses that were
previously fetched.
You also need to actually use the filter whenever you update the map. Replace the
contents of addAnnotations() with the following:
// 1
mapView.removeAnnotations(mapView.annotations)
// 2
for business in filter {
// 3
let viewModel =
annotationFactory.createBusinessMapViewModel(for: business)
mapView.addAnnotation(viewModel)
}
1. You first remove the existing annotations from the map. This prevents duplicates
from being shown, which is possible because this method is called whenever the
user toggles the switch.
raywenderlich.com 209
Design Patterns by Tutorials Chapter 13: Iterator Pattern
2. You loop through each business in filter. Under the hood, this calls
makeIterator() on Filter, which calls filterBusinesses().makeIterator,
and this is what actually filters the businesses.
3. You create a viewModel for each business and add this to the map.
Build and run the app, and try toggling the switch in the top-right corner a few
times. When it's turned on, only highly-rated coffee shops will be shown. When it's
off, all of the coffee shops will be shown.
Key points
You learned about the iterator pattern in this chapter. Here are its key points:
• The iterator pattern provides a standard way to loop through a collection using a
for in syntax.
• By conforming to Sequence, you will get higher-order functions like map and
filter for free.
Each of these are possible using the existing patterns you’ve learned so far. Feel free
to continue building out Coffee Quest as much as you like.
When you're ready, continue onto the next chapter to learn about the prototype
design pattern and build a new example app.
raywenderlich.com 210
14 Chapter 14: Prototype
Pattern
By Joshua Greene
The prototype pattern is a creational pattern that allows an object to copy itself. It
involves two types:
There are actually two different types of copies: shallow and deep.
raywenderlich.com 211
Design Patterns by Tutorials Chapter 14: Prototype Pattern
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!
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:
extension Copying {
// 2
public func copy() -> Self {
return type(of: self).init(self)
}
}
raywenderlich.com 212
Design Patterns by Tutorials Chapter 14: Prototype Pattern
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.
// 1
public class Monster: Copying {
// 2
public required convenience init(_ monster: Monster) {
self.init(health: monster.health, level: monster.level)
}
}
1. This declares a simple Monster type, which conforms to Copying and has
properties for health and level.
// 1
public class EyeballMonster: Monster {
raywenderlich.com 213
Design Patterns by Tutorials Chapter 14: Prototype Pattern
// 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)
}
}
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:
Here, you create a new monster, create a copy named monster2 and then print
monster2.level. You should see this output in the console:
raywenderlich.com 214
Design Patterns by Tutorials Chapter 14: Prototype Pattern
You here prove that you can indeed create a copy of EyeBallMonster. You should see
this output in the console:
What happens if you try to create an EyeballMonster from a Monster ? Enter the
following last:
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.
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:):
Then, uncomment the line for eyeballMonster3, and you’ll get this error message in
the playground console:
Great, this prevents calling this method directly! Go ahead and comment out the line
again so the playground can run.
raywenderlich.com 215
Design Patterns by Tutorials Chapter 14: Prototype Pattern
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.
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.
raywenderlich.com 216
Design Patterns by Tutorials Chapter 14: Prototype Pattern
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:
raywenderlich.com 217
Design Patterns by Tutorials Chapter 14: Prototype Pattern
fillColor = nil
lineWidth = prototype.lineWidth
path = bezierPath.cgPath
strokeColor = prototype.strokeColor
}
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:
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(_:):
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.
raywenderlich.com 218
Design Patterns by Tutorials Chapter 14: Prototype Pattern
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!
raywenderlich.com 219
15 Chapter 15: State Pattern
By Joshua Greene
The state pattern is a behavioral pattern that allows an object to change its behavior
at runtime. It does so by changing its current state. Here, “state” means the set of
data that describes how a given object should behave at a given time.
1. The context is the object that has a current state and whose behavior changes.
raywenderlich.com 220
Design Patterns by Tutorials Chapter 15: State Pattern
Even if a base class is used, it’s not intended to be instantiated directly. Rather,
it’s defined for the sole purpose of being subclassed. In other languages, this
would be an abstract class. Swift currently doesn’t have abstract classes,
however, so this class isn’t instantiated by convention only.
3. Concrete states conform to the state protocol, or if a base class is used instead,
they subclass the base. The context holds onto its current state, but it doesn’t
know its concrete state type. Instead, the context changes behavior using
polymorphism: concrete states define how the context should act. If you ever
need a new behavior, you define a new concrete state.
An important question remains, however: where do you actually put the code to
change the context’s current state? Within the context itself, the concrete states, or
somewhere else?
You may be surprised to find out that the state pattern doesn’t tell you where to put
state change logic! Instead, you’re responsible for deciding this. This is both a
strength and weakness of this pattern: It permits designs to be flexible, but at the
same time, it doesn’t provide complete guidance on how to implement this pattern.
You’ll learn two ways to implement state changes in this chapter. In the playground
example, you’ll put change logic within the context, and in the tutorial project, you’ll
let the concrete states themselves handle changes.
raywenderlich.com 221
Design Patterns by Tutorials Chapter 15: State Pattern
Both open- and closed-set implementations of the state pattern use polymorphism
to change behavior. As a result, you can often eliminate switch and if-else
statements using this pattern.
Instead of keeping track of complex conditions within the context, you pass through
calls to the current state; you’ll see how this works in both the playground example
and tutorial project. If you have a class with several switch or if-else statements,
try to define it using the state pattern instead. You’ll likely create a more flexible and
easier maintain system as a result.
Playground example
Open IntermediateDesignPatterns.xcworkspace in the Starter directory, and then
open the State page.
You’ll implement the “traffic light” system mentioned above. Specifically, you’ll use
Core Graphics to draw a traffic light and change its "current state" from green to
yellow to red to green again.
import UIKit
import PlaygroundSupport
// MARK: - Context
public class TrafficLight: UIView {
raywenderlich.com 222
Design Patterns by Tutorials Chapter 15: State Pattern
// 3
public init(canisterCount: Int = 3,
frame: CGRect =
CGRect(x: 0, y: 0, width: 160, height: 420)) {
super.init(frame: frame)
backgroundColor =
UIColor(red: 0.86, green: 0.64, blue: 0.25, alpha: 1)
createCanisterLayers(count: canisterCount)
}
// 4
private func createCanisterLayers(count: Int) {
}
}
1. You first define a property for canisterLayers. This will hold onto the “traffic
light canister” layers. These layers will hold onto the green/yellow/red states as
sublayers.
// 1
let paddingPercentage: CGFloat = 0.2
let yTotalPadding = paddingPercentage * bounds.height
let yPadding = yTotalPadding / CGFloat(count + 1)
// 2
let canisterHeight = (bounds.height - yTotalPadding) /
CGFloat(count)
let xPadding = (bounds.width - canisterHeight) / 2.0
var canisterFrame = CGRect(x: xPadding,
y: yPadding,
width: canisterHeight,
height: canisterHeight)
// 3
for _ in 0 ..< count {
let canisterShape = CAShapeLayer()
canisterShape.path = UIBezierPath(
raywenderlich.com 223
Design Patterns by Tutorials Chapter 15: State Pattern
ovalIn: canisterFrame).cgPath
canisterShape.fillColor = UIColor.black.cgColor
layer.addSublayer(canisterShape)
canisterLayers.append(canisterShape)
Taking it comment-by-comment:
Here, you create an instance of trafficLight and set it as the liveView for the
playground’s current page, which outputs to the Live View. If you don't see the
output, press Editor ▸ Live View.
raywenderlich.com 224
Design Patterns by Tutorials Chapter 15: State Pattern
To prevent compiler errors as you continue modify this class, delete the two lines of
code you just added.
To show the light states, you need to define a state protocol. Add the following at
the bottom of the playground page:
// MARK: - Properties
// 1
var delay: TimeInterval { get }
1. You first declare a delay property, which defines the time interval a state should
be shown.
2. You then declare apply(to:), which each concrete state will need to implement.
As the names imply, you’ll use currentState to hold onto the traffic light’s current
TrafficLightState, and states to hold onto all TrafficLightStates for the
traffic light. You denote both of these properties as private(set) to ensure only the
TrafficLight itself can set them.
// 1
guard !states.isEmpty else {
fatalError("states should not be empty")
}
self.currentState = states.first!
self.states = states
// 2
raywenderlich.com 225
Design Patterns by Tutorials Chapter 15: State Pattern
super.init(frame: frame)
backgroundColor =
UIColor(red: 0.86, green: 0.64, blue: 0.25, alpha: 1)
createCanisterLayers(count: canisterCount)
}
1. You’ve added states to this initializer. Since it doesn’t make logical sense for
states to be empty, you throw a fatalError if it is. Otherwise, you set the
currentState to the first object within states and set self.states to the
passed-in states.
Next, add the following code right before the ending class curly brace for
TrafficLight:
transition(to: currentState)
This ensures the currentState is added to the view when it’s initialized.
raywenderlich.com 226
Design Patterns by Tutorials Chapter 15: State Pattern
Now you need to create the concrete states. Add the following code to the end of the
playground:
// MARK: - Properties
public let canisterIndex: Int
public let color: UIColor
public let delay: TimeInterval
Within apply(to:), you create a new CAShapeLayer for the state: you set its path to
match the canisterLayer for its designated canisterIndex, set its fillPath and
strokeColor using its color, and ultimately, add the shape to the canister layer.
raywenderlich.com 227
Design Patterns by Tutorials Chapter 15: State Pattern
You’re finally ready to put this code into action! Add the following to the end of the
playground:
raywenderlich.com 228
Design Patterns by Tutorials Chapter 15: State Pattern
This creates a typical green/yellow/red traffic light and sets it to the current
playground page’s liveView.
But wait! Shouldn’t the traffic light be switching from one state to the next? Oh
— you haven’t actually implemented this functionality yet. The state pattern doesn’t
actually tell you where or how to perform state changes. In this case, you actually
have two choices: you can put state change logic within TrafficLight, or you can
put this within TrafficLightState.
In a real application, you should evaluate which of these choices is better for your
expected use cases and what’s better in the long run. For this playground example,
“another developer” (i.e., your humble author) has told you the logic is better suited
in the TrafficLight, so this is where you’ll put the changing code.
First, add the following extension after the closing curly brace for
TrafficLightState:
// MARK: - Transitioning
extension TrafficLightState {
public func apply(to context: TrafficLight,
after delay: TimeInterval) {
let queue = DispatchQueue.main
let dispatchTime = DispatchTime.now() + delay
queue.asyncAfter(deadline: dispatchTime) {
[weak self, weak context] in
guard let self = self, let context = context else {
return
}
context.transition(to: self)
}
}
}
raywenderlich.com 229
Design Patterns by Tutorials Chapter 15: State Pattern
This extension adds “apply after” functionality to every type that conforms to
TrafficLightState. In apply(to:after:), you dispatch to DispatchQueue.main
after a passed-in delay, at which point you transition to the current state. In order to
break potential retain cycles, you specify both self and context as weak within the
closure.
This creates a convenience computed property for the nextState, which you
determine by finding the index representing the currentState. If there are states
after the index, which you determine by index + 1 < states.count, you return
that next state. If there aren’t states after the currentState, you return the first
state to go back to the start.
This tells the nextState to apply itself to the traffic light after the current state’s
delay has passed.
Check out the Assistant editor, and you’ll now see it cycling states!
If you choose to implement state change logic within the states themselves, be
careful about tight coupling from one state to the next.
raywenderlich.com 230
Design Patterns by Tutorials Chapter 15: State Pattern
Will you ever want to transition from state to another state instead? In this case,
consider passing in the next state via an initializer or property.
Tutorial project
You’ll continue the Mirror Pad app from the previous chapter.
If you skipped the previous chapter, or you want a fresh start, open Finder and
navigate to where you downloaded the resources for this chapter. Then, open
starter\MirrorPad\MirrorPad.xcodeproj in Xcode.
Build and run the app, and draw several lines into the top-left view. Then press
Animate to watch the app animate the mirrored drawings. Before the animation
completes, try drawing more lines into the top-left view. The app lets you do this, but
it’s a poor user experience.
Open DrawView.swift and check out this class. It’s currently doing a lot of work:
accepting user inputs, performing copying, drawing, animation and more. If you
continue to expand Mirror Pad’s functionality over time, you’d likely struggle to
maintain this class. It’s simply doing too much!
You’ll fix both of these using the state pattern, but you already guessed that, right?
You’ll turn DrawView into the context, create a new DrawViewState as the base
state class and create several concrete states that subclass DrawViewState to
perform required behavior.
First, you need to add new groups and files. Create a new group called DrawView
inside the Views group. Then, move DrawView.swift and LineShape.swift into your
newly-created DrawView group.
Create another new group called States inside the DrawView group. Within the
States group, create new Swift files for each of these:
• AcceptInputState.swift
• AnimateState.swift
• ClearState.swift
• CopyState.swift
• DrawViewState.swift
raywenderlich.com 231
Design Patterns by Tutorials Chapter 15: State Pattern
Your Views group should now look like this in the File hierarchy:
import UIKit
// MARK: - Actions
// 3
public func animate() { }
public func copyLines(from source: DrawView) { }
public func clear() { }
public func touchesBegan(_ touches: Set<UITouch>,
with event: UIEvent?) { }
public func touchesMoved(_ touches: Set<UITouch>,
with event: UIEvent?) { }
raywenderlich.com 232
Design Patterns by Tutorials Chapter 15: State Pattern
1. You first declare a class property called identifier. You’ll later use this to
switch between states.
2. You then declare an unowned instance property called drawView, which will be
the context in the state pattern. You pass in the context via the designated
initializer init(drawView:).
This creates a tight coupling between DrawViewState and DrawView. In this app,
you’ll only use DrawViewState along with DrawView, so this coupling isn’t a
problem. In your own app, however, you should consider whether or not you’d
ever want to reuse DrawViewState with a different context.
3. You then declare methods for all of the possible actions and provide empty
implementations for each. Concrete state subclasses will need to override
whichever actions they support. If a concrete state doesn’t override an action, it
will inherit this empty implementation and do nothing.
4. At the end, you declare a method to change between states. This has a return
value of DrawViewState to enable you to call an action on the new state after
switching to it. You need to make changes to DrawView before you can complete
this method, however, so you add a TODO comment and return self as a
placeholder for now.
You’ll next stub out each of the concrete states. Essentially, you’ll be moving code
from DrawView into the states to facilitate the refactoring.
import UIKit
raywenderlich.com 233
Design Patterns by Tutorials Chapter 15: State Pattern
import UIKit
import UIKit
import UIKit
Great! You can now begin to refactor DrawView. Open DrawView.swift and add the
following properties, right after the existing ones:
As its name implies, you’ll use currentState to hold onto the current concrete
state.
You’ll hold onto all possible states within states. This is a dictionary that uses the
computed value from identifier defined on DrawViewState for keys and concrete
state instances as values. Why is this a dictionary and not a array? This is because
concrete states don’t have one transition order! Rather, state transitions depend on
user interaction. Here’s how it will work:
raywenderlich.com 234
Design Patterns by Tutorials Chapter 15: State Pattern
Remember the method you stubbed out on DrawViewState before? Now that
DrawView has a currentState and states defined, you can complete this method!
This looks up the state from drawView.states using the passed-in identifier,
sets the value to drawView.currentState and returns the state.
All that’s left to do is move logic from DrawView into the appropriate concrete states.
You’re going to be editing DrawView a lot, so it will be useful to open this in a new
Editor window. To do so, hold down Option and left-click on DrawView.swift in the
File hierarchy. This will let you easily edit DrawView at the same time as the
concrete state classes.
Click anywhere within the first editor window and then left-click
AcceptInputState.swift to open it within this window. Add the following methods to
this class:
// 1
public override func animate() {
let animateState = transitionToState(
matching: AnimateState.identifier)
animateState.animate()
}
raywenderlich.com 235
Design Patterns by Tutorials Chapter 15: State Pattern
// 2
public override func touchesBegan(_ touches: Set<UITouch>,
with event: UIEvent?) {
guard let point = touches.first?.location(in: drawView) else {
return
}
let line = LineShape(color: drawView.lineColor,
width: drawView.lineWidth,
startPoint: point)
drawView.lines.append(line)
drawView.layer.addSublayer(line)
}
raywenderlich.com 236
Design Patterns by Tutorials Chapter 15: State Pattern
Here you simply forward these method calls onto the currentState. If the
currentState is an instance of AcceptInputState, which it is by default, the app
will behave exactly as before.
Build and run and draw into the top-left view to verify the app still works as
expected.
raywenderlich.com 237
Design Patterns by Tutorials Chapter 15: State Pattern
This code is nearly identical to DrawView, but there are two main changes:
You should also take note of the methods that you didn’t override, especially
touchesBegan(_:with:) and touchesMoved(_:with:). Consequently, whenever
the currentState is set to AnimateState, you won’t do anything if the user
attempts to draw into the view. Essentially, you fixed a bug by doing nothing. How
awesome is that!
Of course, you need to make sure DrawView passes the call to its animate() onto the
currentState instead. Thereby, replace animate() within DrawView with this:
raywenderlich.com 238
Design Patterns by Tutorials Chapter 15: State Pattern
You have just two more states to go! Open ClearState.swift, and add the following
method to the class:
This is just like DrawView’s code. The only addition is that once “clearing” is
complete, you transition back to AcceptInputState.
You also need to update DrawView; replace its clear() with this instead:
Open CopyState.swift from the File hierarchy, and add this method within the
class:
Again, this is just like DrawView, and the only addition is that you transition back to
AcceptInputState once copying is complete.
Of course, you also need to update copyLines(from:) within DrawView with this:
Build and run, and validate that everything works as it did before.
Take a look at how much shorter DrawView is now! You’ve shifted its responsibilities
to its concrete states instead. And if you ever wanted to add new logic, you’d simply
create a new DrawViewState.
raywenderlich.com 239
Design Patterns by Tutorials Chapter 15: State Pattern
Key points
You learned about the state pattern in this chapter. Here are its key points:
• The state pattern permits an object to change its behavior at runtime. It involves
three types: the context, state protocol and concrete states.
• The context is the object that has a current state; the state protocol defines
required methods and properties; and the concrete states implement the state
protocol and actual behavior that changes at runtime.
• The state pattern doesn’t actually tell you where to put transition logic between
states. Rather, this is left for you to decide: you can put this logic either within the
context or within the concrete states.
Mirror Pad is also really coming along! It’s pretty cool that you can see the drawings
get rendered when you press “Animate." However, wouldn’t it be better if you could
also see them added in real time while you draw? You bet it would!
Continue onto the next chapter to learn about the multicast delegate design
pattern and add the above real-time feature to Mirror Pad!
raywenderlich.com 240
16 Chapter 16: Multicast
Delegate Pattern
By Joshua Greene
raywenderlich.com 241
Design Patterns by Tutorials Chapter 16: Multicast Delegate Pattern
4. The multicast delegate is a helper class that holds onto delegates and allows
you to notify each whenever a delegate-worthy event happens.
The main difference between the multicast delegate pattern and the delegate pattern
is the presence of a multicast delegate helper class. Swift doesn’t provide you this
class by default. However, you can easily create your own, which you’ll do in this
chapter.
Multicast is an advanced topic in the Combine framework, and it's beyond the
scope of this chapter. If you'd like to learn more about Combine, check out our
book about it, Combine: Asynchronous Programming with Swift (http://bit.ly/
swift-combine).
For example, you can use this pattern to inform multiple objects whenever a change
has happened to another object. Each delegate can then update its own state or
perform relevant actions in response.
Playground example
Open IntermediateDesignPattern.xcworkspace in the Starter directory, or
continue from your own playground workspace from the last chapter, and then open
the MulticastDelegate page from the File hierarchy.
raywenderlich.com 242
Design Patterns by Tutorials Chapter 16: Multicast Delegate Pattern
Before you can write the Code example for this page, you need to create the
MulticastDelegate helper class.
// 1
public class MulticastDelegate<ProtocolType> {
// MARK: - DelegateWrapper
// 2
private class DelegateWrapper {
2. You define DelegateWrapper as an inner class. You’ll use this to wrap delegate
objects as a weak property. This way, the multicast delegate can hold onto strong
wrapper instances, instead of the delegates directly.
Unfortunately, here you have to declare the delegate property as AnyObject instead
of ProtocolType. That’s because weak variables have to be AnyObject (i.e., a class).
You'd think you could just declare ProtocolType as AnyObject in the generic
definition. However that won’t work because you’ll need to pass a protocol as the
type, which itself isn't an object.
Next, add the following right before the closing class curly brace for
MulticastDelegate:
// 2
public var delegates: [ProtocolType] {
delegateWrappers = delegateWrappers
raywenderlich.com 243
Design Patterns by Tutorials Chapter 16: Multicast Delegate Pattern
2. You then add a computed property for delegates. This filters out delegates
from delegateWrappers that have already been released and then returns an
array of definitely non-nil delegates.
3. You lastly create an initializer that accepts an array of delegates and maps these
to create delegateWrappers.
You also need a means to add and remove delegates after a MulticastDelegate
has been created already. Add the following instance methods after the previous code
to do this:
// 2
public func removeDelegate(_ delegate: ProtocolType) {
guard let index = delegateWrappers.firstIndex(where: {
$0.delegate === (delegate as AnyObject)
}) else {
return
}
delegateWrappers.remove(at: index)
}
raywenderlich.com 244
Design Patterns by Tutorials Chapter 16: Multicast Delegate Pattern
1. As its name implies, you’ll use addDelegate to add a delegate instance, which
creates a DelegateWrapper and appends it to the delegateWrappers.
Lastly, you need a means to actually invoke all of the delegates. Add the following
method after the previous ones:
You iterate through delegates, the computed property you defined before that
automatically filters out nil instances, and call the passed-in closure on each
delegate instance.
Fantastic — you now have a very useful MulticastDelegate helper class and are
ready to try it out!
Open the MulticastDelegate page from the File hierarchy, and enter the following
after Code example:
// MARK: - Delegates
public class FireStation: EmergencyResponding {
raywenderlich.com 245
Design Patterns by Tutorials Chapter 16: Multicast Delegate Pattern
}
}
For simplicity, you simply print out messages whenever a method is called on these.
Next, add the following code to the end of the playground:
// MARK: - Example
let dispatch = DispatchSystem()
var policeStation: PoliceStation! = PoliceStation()
var fireStation: FireStation! = FireStation()
dispatch.multicastDelegate.addDelegate(policeStation)
dispatch.multicastDelegate.addDelegate(fireStation)
raywenderlich.com 246
Design Patterns by Tutorials Chapter 16: Multicast Delegate Pattern
dispatch.multicastDelegate.invokeDelegates {
$0.notifyFire(at: "Ray’s house!")
}
In the event that a delegate becomes nil, it should not be notified of any future calls
on multicast delegate. Finally, add the following next to verify that this works as
intended:
print("")
fireStation = nil
dispatch.multicastDelegate.invokeDelegates {
$0.notifyCarCrash(at: "Ray's garage!")
}
You set fireStation to nil, which in turn will result in its related
DelegateWrapper on MulticastDelegate having its delegate set to nil as well.
When you then call invokeDelegates, this will result in said DelegateWrapper
being filtered out, so its delegate’s code will not be invoked.
Ray must have skidded off the driveway when he was trying to get out of the fire! Get
out of there, Ray!
If delegates need to provide data, this pattern doesn’t work well. That’s because
multiple delegates would be asked to provide the data, which could result in
duplicated information or wasted processing.
raywenderlich.com 247
Design Patterns by Tutorials Chapter 16: Multicast Delegate Pattern
Tutorial project
You’ll continue the Mirror Pad app from the previous chapter.
If you skipped the previous chapter, or you want a fresh start, open Finder and
navigate to where you downloaded the resources for this chapter. Then, open
starter/MirrorPad/MirrorPad.xcodeproj in Xcode.
Build and run the app. Draw several lines into the top-left view and press Animate
to see the lines drawn to each view. This is pretty neat, but wouldn’t it be cool if the
lines were added as you drew? You bet it would! This is exactly what you’ll be adding
in this chapter.
To do so, you’re going to need the MulticastDelegate.Swift file you created in the
Playground example. If you skipped the Playground example, open Finder,
navigate to where you downloaded the resources for this chapter, and open final/
IntermediateDesignPatterns.xcworkspace. Otherwise, feel free to use your own
file from the playground.
Next, open DrawView.swift from the File hierarchy and add the following at the
top of the file, after the imports:
Next, add the following right before the closing class curly brace for DrawView:
raywenderlich.com 248
Design Patterns by Tutorials Chapter 16: Multicast Delegate Pattern
// 2
drawView.multicastDelegate.invokeDelegates {
$0.drawView(drawView, didAddLine: line)
}
}
1. Instead of appending the new line and adding it to the drawView.layer directly
within touchesBegan(_:event:), you move this logic into a new helper method,
addLine(_:). This will allow you to call addLine(_:) separately from
touchesBegan(_:event:) later on.
raywenderlich.com 249
Design Patterns by Tutorials Chapter 16: Multicast Delegate Pattern
// 1
addPoint(point)
// 2
drawView.multicastDelegate.invokeDelegates {
$0.drawView(drawView, didAddPoint: point)
}
}
2. You notify all delegates whenever a new point has been created.
Great, this takes care of the delegate notifications! You next need to actually conform
to the new DrawViewDelegate protocol somewhere.
Before you can do so, it’s important you understand how MirrorPad actually uses
DrawView. It has multiple DrawView instances that displays “mirrors” of the input
DrawView. The difference between each mirror DrawView instance is their
layer.sublayerTransform, which determines their mirror transformations.
In order to update the mirror DrawView objects whenever the master DrawView
object is updated, you’ll need to make DrawView itself conform to
DrawViewDelegate. However, DrawView should only accept new lines and points
when its currentState is set to AcceptInputState. This prevents potential issues
resulting from things such as adding lines or points while the animation is running.
Consequently, you also need to make DrawViewState, the base state used by
DrawView, conform to DrawViewDelegate. This lets AcceptInputState override the
delegate methods and handle the new lines and points correctly.
raywenderlich.com 250
Design Patterns by Tutorials Chapter 16: Multicast Delegate Pattern
Note: DrawView uses the state pattern to accept input and animate, among
other things. The state pattern is covered in the previous chapter.
All this theory here may sound a bit complex, but essentially DrawView will forward
calls to add new lines or points to its currentState. If the currentState is
AcceptInputState, the new lines and points will be added. If not, the calls will be
ignored.
Open DrawViewState.swift and add the following to the end of the file:
// MARK: - DrawViewDelegate
extension DrawViewState: DrawViewDelegate {
public func drawView(_ source: DrawView,
didAddLine line: LineShape) { }
Next, open AcceptInputState.swift and add the following to the end of the file:
// MARK: - DrawViewDelegate
extension AcceptInputState {
raywenderlich.com 251
Design Patterns by Tutorials Chapter 16: Multicast Delegate Pattern
// MARK: - DrawViewDelegate
extension DrawView: DrawViewDelegate {
You’re almost ready to try this out! The last thing you need to do is actually register
the “mirror” DrawViews as delegates of the input DrawView.
Open ViewController.swift and add the following after the existing properties:
You simply iterate through each mirrorDrawView and add them as delegates to
inputDrawView. Build and run, and try drawing into the top-left draw view. Each of
the other views should now be updated in real time as you draw!
raywenderlich.com 252
Design Patterns by Tutorials Chapter 16: Multicast Delegate Pattern
Key points
You learned about the multicast delegate pattern in this chapter. Here are its key
points:
• An object needing a delegate has one or more delegates; the delegate protocol
defines the methods a delegate should implement; the delegates implement the
delegate protocol; and the multicast delegate is a helper class for holding onto
and notifying the delegates.
• Swift doesn’t provide a multicast delegate object for you. However, it’s easy to
implement your own to support this pattern.
Mirror Pad is really functional now! However, there’s no way to share your amazing
creations with the world... yet!
raywenderlich.com 253
17 Chapter 17: Facade
Pattern
By Joshua Greene
1. The facade provides simple methods to interact with the system. This allows
consumers to use the facade instead of knowing about and interacting with
multiple classes in the system.
2. The dependencies are objects owned by the facade. Each dependency performs a
small part of a complex task.
raywenderlich.com 254
Design Patterns by Tutorials Chapter 17: Facade Pattern
For example, a product ordering system involves several components: customers and
products, inventory in stock, shipping orders and others.
Instead of requiring the consumer to understand each of these components and how
they interact, you can provide a facade to expose common tasks such as placing and
fulfilling a new order.
Playground example
Open IntermediateDesignPatterns.xcworkspace in the Starter directory, and then
open the Facade page.
You’ll implement part of the ordering system mentioned above. Specifically, you’ll
create an OrderFacade that allows a user to place an order.
import Foundation
// MARK: - Dependencies
public struct Customer {
public let identifier: String
public var address: String
public var name: String
}
raywenderlich.com 255
Design Patterns by Tutorials Chapter 17: Facade Pattern
Here you define two simple models: a Customer represents a user that can place an
order, and a Product sold by the system. You make both of these types conform to
Hashable to enable you to use them as keys within a dictionary.
// MARK: - Facade
public class OrderFacade {
public let inventoryDatabase: InventoryDatabase
raywenderlich.com 256
Design Patterns by Tutorials Chapter 17: Facade Pattern
Here, you declare OrderFacade and add two properties, inventoryDatabase and
shippingDatabase, which you pass into this via its initializer,
init(inventoryDatabase:shippingDatabase:).
Next, add the following method to the end of the OrderFacade class you just added:
// 2
let count = inventoryDatabase.inventory[product, default: 0]
guard count > 0 else {
print("'\(product.name)' is out of stock!")
return
}
// 3
inventoryDatabase.inventory[product] = count - 1
// 4
var shipments =
shippingDatabase.pendingShipments[customer, default: []]
shipments.append(product)
shippingDatabase.pendingShipments[customer] = shipments
// 5
print("Order placed for '\(product.name)' " +
"by '\(customer.name)'")
}
This is a simple method that consumers of the facade will call to place orders for a
given Product and Customer.
raywenderlich.com 257
Design Patterns by Tutorials Chapter 17: Facade Pattern
2. Before fulfilling the order, you guard that there’s at least one of the given
product in the inventoryDatabase.inventory. If there isn’t any, you print that
the product is out of stock.
3. Since there’s at least one of the product available, you can fulfill the order. You
thereby reduce the count of the product in inventoryDatabase.inventory by
one.
Great, you’re ready to try out the facade! Add the following code at the end of the
playground:
// MARK: - Example
// 1
let rayDoodle = Product(
identifier: "product-001",
name: "Ray's doodle",
cost: 0.25)
// 2
let inventoryDatabase = InventoryDatabase(
inventory: [rayDoodle: 50, vickiPoodle : 1]
)
// 3
let orderFacade = OrderFacade(
inventoryDatabase: inventoryDatabase,
shippingDatabase: ShippingDatabase())
// 4
let customer = Customer(
identifier: "customer-001",
address: "1600 Pennsylvania Ave, Washington, DC 20006",
name: "Johnny Appleseed")
raywenderlich.com 258
Design Patterns by Tutorials Chapter 17: Facade Pattern
1. First, you set up two products. rayDoodle are drawings from Ray, and
vickiPoodle is a prized pet poodle by Vicki. Don’t even get me started about the
poodle doodles!
2. Next, you create inventoryDatabase using the products. There are a lot of
rayDoodles (he likes to doodle, apparently) and only one vickiPoodle. It’s her
prized poodle, after all!
3. Then, you create the orderFacade using the inventoryDatabase and a new
ShippingDatabase.
Doodles and poodles aside, you’ve just created a nice start for an ordering system!
It’s okay to create more than one facade for different use cases. For example, if you
notice a facade has functionality that some classes use and other functionality that
other classes use, consider splitting it into two or more facades.
Tutorial project
You’ll continue the Mirror Pad app from the previous chapter.
If you skipped the previous chapter, or you want a fresh start, open Finder and
navigate to where you downloaded the resources for this chapter. Then, open
starter\MirrorPad\MirrorPad.xcodeproj in Xcode.
raywenderlich.com 259
Design Patterns by Tutorials Chapter 17: Facade Pattern
You’ll implement a share button in this chapter and make use of a facade. To keep
the focus on the design pattern, and to save you a lot of typing, the facade’s
dependencies have been provided for you.
Open Finder and navigate to wherever you downloaded the resources for this
chapter. Alongside the Starter and Final directories, you’ll see a Resources directory
that contains DrawingSelectionViewController.swift,
DrawingSelectionViewController.xib and ImageRenderer.swift.
Position the Finder window above Xcode and drag and drop
DrawingSelectionViewController.swift and ImageRenderer.swift into the
app’s Facades group.
When prompted, check the option for Copy items if needed and press Finish to add
the files.
You’ll pass the selected view to ImageRenderer to convert it into a UIImage. You’ll
then use the resulting image to create a UIActivityViewController.
raywenderlich.com 260
Design Patterns by Tutorials Chapter 17: Facade Pattern
Your job is to create a new facade called ShareFacade. You’ll use this to provide a
simple interface to allow consumers to select which view to share, turn the view into
an image and share this via whichever app the user chooses.
Create a new Swift File called ShareFacade.swift within the app’s Facades group
and replace its contents with the following:
import UIKit
// 2
private var imageRenderer = ImageRenderer()
}
}
raywenderlich.com 261
Design Patterns by Tutorials Chapter 17: Facade Pattern
2. Next, you declare a property for imageRenderer, which you’ll use later.
Still in ShareFacade.swift, add the following to the bottom of the file after the
closing class curly brace:
// MARK: - DrawingSelectionViewControllerDelegate
extension ShareFacade: DrawingSelectionViewControllerDelegate {
// 1
public func drawingSelectionViewControllerDidCancel(
_ viewController: DrawingSelectionViewController) {
parentViewController.dismiss(animated: true)
}
// 2
public func drawingSelectionViewController(
_ viewController: DrawingSelectionViewController,
didSelectView view: UIView) {
parentViewController.dismiss(animated: false)
let image = imageRenderer.convertViewToImage(view)
raywenderlich.com 262
Design Patterns by Tutorials Chapter 17: Facade Pattern
Next, you immediately create an image from the given view by passing this to
imageRenderer.
Ultimately, this will have the nice effect of immediately hiding the
DrawingSelectionViewController and animating in the new
UIActivityViewController.
// 1
let selectionViewController =
DrawingSelectionViewController.createInstance(
entireDrawing: entireDrawing,
inputDrawing: inputDrawing,
delegate: self)
// 2
parentViewController.present(selectionViewController,
animated: true)
raywenderlich.com 263
Design Patterns by Tutorials Chapter 17: Facade Pattern
Open ViewController.swift and add the following right after the opening class curly
brace:
// MARK: - Properties
public lazy var shareFacade: ShareFacade =
ShareFacade(entireDrawing: drawViewContainer,
inputDrawing: inputDrawView,
parentViewController: self)
Here, you create a new property called shareFacade. Since you pass self as the
parentViewController, you make this a lazy property to ensure that the
ViewController itself is fully created first.
shareFacade.presentShareController()
With this single line, you’ve added sharing capabilities to ViewController. Aren’t
facades great?
Build and run the app. Draw several lines in the top-left view, press the Share
button, and you’ll be presented with the DrawingSelectionViewController.
raywenderlich.com 264
Design Patterns by Tutorials Chapter 17: Facade Pattern
Press the red Share button, and you’ll see the UIActivityViewController, where
you can pick an app to use to share the image.
If you’re using the simulator, you’ll only see a few apps available. If you use a real
device instead, you’ll see several more depending on the apps you have installed.
Key points
You learned about the facade pattern in this chapter. Here are its key points:
• The facade pattern provides a simple interface to a complex system. It involves two
types: the facade and its dependencies.
• The facade provides simple methods to interact with the system. Behind the
scenes, it owns and interacts with its dependencies, each of which performs a
small part of a complex task.
raywenderlich.com 265
Design Patterns by Tutorials Chapter 17: Facade Pattern
Mirror Pad has come a long way, and the code is much more maintainable! There’s
still a lot of functionality you can add:
Each of these are possible using the existing patterns you’ve learned in the
Intermediate and Fundamental Design Patterns sections. Feel free to continue
building out Mirror Pad as much as you like.
But you still have more learning to do. Continue onto the next section to learn about
advanced design patterns, including mediator, composite, command and more!
raywenderlich.com 266
Section IV: Advanced Design
Patterns
This section covers design patterns that are very useful but only in rare or specific
circumstances. These patterns may be exactly what you need for a particular case,
but they may not be useful on every project. However, it’s best to be aware of them as
you’ll undoubtedly run across them at some point in your development career.
raywenderlich.com 267
18 Chapter 18: Flyweight
Pattern
By Jay Strawn
The flyweight pattern is a structural design pattern that minimizes memory usage
and processing.
This pattern provides objects that all share the same underlying data, thus saving
memory. They are usually immutable to make sharing the same underlying data
trivial.
The flyweight pattern has objects, called flyweights, and a static method to return
them.
Does this sound familiar? It should! The flyweight pattern is a variation on the
singleton pattern. In the flyweight pattern, you usually have multiple different
objects of the same class. An example is the use of colors, as you will experience
shortly. You need a red color, a green color and so on. Each of these colors are a
single instance that share the same underlying data.
raywenderlich.com 268
Design Patterns by Tutorials Chapter 18: Flyweight Pattern
Playground example
Open AdvancedDesignPatterns.xcworkspace in the starter directory and then
click on the Flyweight link to open the page. Here, you’ll use UIKit. Flyweights are
very common in UIKit. UIColor, UIFont, and UITableViewCell are all examples of
classes with flyweights.
import UIKit
This code proves that UIColor uses flyweights. Comparing the colors with ===
statements shows that each variable has the same memory address, which
means .red is a flyweight and is only instantiated once.
Of course, not all UIColor objects are flyweights. Add the following below:
This time, your console will log false! Custom UIColor objects aren’t flyweights.
This method takes red, green and blue and returns a new UIColor every time it’s
called.
If UIColor checked the values to see if a color was already made, it could return
flyweight instances instead. Why don’t you do that? Extend the UIColor class with
the following code:
extension UIColor {
// 1
raywenderlich.com 269
Design Patterns by Tutorials Chapter 18: Flyweight Pattern
// 2
public class func rgba(_ red: CGFloat,
_ green: CGFloat,
_ blue: CGFloat,
_ alpha: CGFloat) -> UIColor {
// 3
let color = UIColor(red: red,
green: green,
blue: blue,
alpha: alpha)
colorStore[key] = color
return color
}
}
2. You wrote your own method that takes red green, blue and alpha like the UIColor
method. You store the RGB values in a string called key. If a color with that key
already exists in colorStore, use that one instead of creating a new one.
3. If the key does not already exist in the colorStore, create the UIColor and store
it along with its key.
This tests the extension method. You’ll see that the console prints true, which
means you’ve successfully implemented the flyweight pattern!
raywenderlich.com 270
Design Patterns by Tutorials Chapter 18: Flyweight Pattern
usage for the same color, but you can still use too much memory in the flyweight
store.
To mitigate this, set bounds on how much memory you use or register for memory
warnings and respond by removing some flyweights from memory. You could use a
LRU (Least Recently Used) cache to handle this.
Also be mindful that your flyweight shared instance must be a class and not a struct.
Structs use copy semantics, so you don’t get the benefits of shared underlying data
that comes with reference types.
Tutorial project
Throughout this section, you’ll create a tutorial app called YetiJokes.
It’s a joke-reading app that uses custom fonts and snowcases some great puns. ;] For
the purposes of this tutorial, most of the setup has been done already.
raywenderlich.com 271
Design Patterns by Tutorials Chapter 18: Flyweight Pattern
Build and run. At the bottom of the screen, you’ll see a toolbar with the following
options:
The goal of this project is to use the buttons on the segmented control to change the
font to large, medium and small sizes. These fonts will be dynamically loaded as...
you guessed it, flyweights!
Return to Finder and you’ll see that there are two folders in the Starter directory:
YetiJokes and YetiTheme. YetiTheme is a framework with a custom font inside.
import Foundation
// 1
public static let large = loadFont(name: fontName,
size: 30.0)
public static let medium = loadFont(name: fontName,
size: 25.0)
public static let small = loadFont(name: fontName,
size: 18.0)
// 2
private static let fontName = "coolstory-regular"
// 3
raywenderlich.com 272
Design Patterns by Tutorials Chapter 18: Flyweight Pattern
// 4
guard
let url = bundle.url(forResource: name,
withExtension: "ttf"),
let fontData = NSData(contentsOf: url),
let provider = CGDataProvider(data: fontData),
let cgFont = CGFont(provider),
let fontName = cgFont.postScriptName as String? else {
preconditionFailure("Unable to load font named \(name)")
}
CTFontManagerRegisterGraphicsFont(cgFont, nil)
// 5
return UIFont(name: fontName, size: size)!
}
}
1. You create three flyweights, each one a font with a different size.
2. You create a private constant for the font file name to use.
3. You create the method that loads a font of the given name at a certain size.
4. In this guard statement, you load the font as a CGFont, then register it to the app
with CTFontManagerRegisterGraphicsFont. If the font has already been
registered, it will not be registered again.
5. Now that it’s registered, you can load your custom font as a UIFont by name.
“Why load a font this way?” you may be thinking. “Why not just include the font in
the main bundle?”
Yes, there’s an easier way of doing this in the app’s main bundle. However, if
YetiTheme is a shared library between several apps, you may not want each app to
add this font to the main bundle. In a real-world example, you may have many fonts
and don’t want consuming apps to have the hassle of adding them whenever new
ones are added or existing fonts changes.
raywenderlich.com 273
Design Patterns by Tutorials Chapter 18: Flyweight Pattern
If your framework provides trademarked fonts, you could even be required to encrypt
the font data. You aren’t doing this here, but if you needed to, you could more easily
do so since the font is in a separate bundle.
Now that you can load fonts, close YetiTheme and go back to YetiJokes.xcodeproj.
It’s time to actually add this framework to the app.
Right-click on the top of the navigation tree and select Add files to "YetiJokes"...
and add YetiTheme.xcodeproj:
Once you’ve added YetiTheme.xcodeproj, your project structure will look similar to
the following:
Next, click on YetiJokes and select General. Scroll to the bottom and add
YetiTheme.framework to Frameworks, Libraries and Embedded Content as
shown below.
OK cool, can you use YetiTheme yet? Not yet-i! You need to import the framework
first.
Open ViewController.swift and import the Framework at the top of the file:
import YetiTheme
Next, you’ll want to use the new font when the View controller loads. Add the
following below your IBOutlet definitions:
raywenderlich.com 274
Design Patterns by Tutorials Chapter 18: Flyweight Pattern
textLabel.font = Fonts.small
}
This code will set the textLabel font to the small custom font on the views initial
load. Finally, time to set up the segmented control. Add the following to the existing
segmentedControlValueChanged(_:).
switch sender.selectedSegmentIndex {
case 0:
textLabel.font = Fonts.small
case 1:
textLabel.font = Fonts.medium
case 2:
textLabel.font = Fonts.large
default:
textLabel.font = Fonts.small
}
Build and run the app. You can now switch between fonts quickly and easily! Each
font is only loaded once, and the font won’t be registered more than once. You’ve
successfully cut back on processing and load times in your app. Build and run the app
to verify this functionality.
Key points
You learned about the flyweight pattern in this chapter. Here are its key points:
• This pattern has objects, called flyweights, and a static method to return them. It’s
a variation on the singleton pattern.
• When creating flyweights, be careful about the size of your flyweight memory. If
you’re storing several flyweights, it’s still possible to use too much memory in the
flyweight store.
Feel free to add functionality to YetiJokes and even change up the jokes; you can only
get so many laughs with dad puns!
raywenderlich.com 275
19 Chapter 19: Mediator
Pattern
By Joshua Greene
The mediator pattern is a behavioral design pattern that encapsulates how objects
communicate with one another. It involves four types:
1. The colleagues are the objects that want to communicate with each other. They
implement the colleague protocol.
2. The colleague protocol defines methods and properties that each colleague
must implement.
3. The mediator is the object that controls the communication of the colleagues. It
implements the mediator protocol.
raywenderlich.com 276
Design Patterns by Tutorials Chapter 19: Mediator Pattern
4. The mediator protocol defines methods and properties that the mediator must
implement.
Each colleague contains a reference to the mediator, via the mediator protocol. In
lieu of interacting with other colleagues directly, each colleague communicates
through the mediator.
This pattern is especially useful when you need one or more colleagues to act upon
events initiated by another colleague, and, in turn, have this colleague generate
further events that affect other colleagues.
Playground example
Open AdvancedDesignPattern.xcworkspace in the Starter directory, or continue
from your own playground workspace you’ve been continuing to work on throughout
the book, and then open the Mediator page from the File hierarchy.
Before you can write the Code example for this page, you need to create a base
Mediator class.
Note: You can technically implement the mediator pattern without using a
base Mediator, but if you do, you’ll likely write a lot more boilerplate code.
// 1
open class Mediator<ColleagueType> {
raywenderlich.com 277
Design Patterns by Tutorials Chapter 19: Mediator Pattern
// 2
private class ColleagueWrapper {
var strongColleague: AnyObject?
weak var weakColleague: AnyObject?
// 3
var colleague: ColleagueType? {
return
(weakColleague ?? strongColleague) as? ColleagueType
}
// 4
init(weakColleague: ColleagueType) {
self.strongColleague = nil
self.weakColleague = weakColleague as AnyObject
}
init(strongColleague: ColleagueType) {
self.strongColleague = strongColleague as AnyObject
self.weakColleague = nil
}
}
}
1. First, you define Mediator as a generic class that accepts any ColleagueType as
the generic type. You also declare Mediator as open to enable classes in other
modules to subclass it.
2. Next, you define ColleagueWrapper as an inner class, and you declare two stored
properties on it: strongColleague and weakColleague. In some use cases, you’ll
want Mediator to retain colleagues, but in others, you won’t want this. Hence,
you declare both weak and strong properties to support both scenarios.
raywenderlich.com 278
Design Patterns by Tutorials Chapter 19: Mediator Pattern
Next, add the following code after the closing curly brace for ColleagueWrapper:
// 2
public var colleagues: [ColleagueType] {
var colleagues: [ColleagueType] = []
colleagueWrappers = colleagueWrappers.filter {
guard let colleague = $0.colleague else { return false }
colleagues.append(colleague)
return true
}
return colleagues
}
2. Next, you add a computed property for colleagues. This uses filter to find
colleagues from colleagueWrappers that have already been released and then
returns an array of definitely non-nil colleagues.
3. Finally, you declare init(), which will act as the public designated initializer
for Mediator.
You also need a means to add and remove colleagues. Add the following instance
methods after the previous code to do this:
raywenderlich.com 279
Design Patterns by Tutorials Chapter 19: Mediator Pattern
// 2
public func removeColleague(_ colleague: ColleagueType) {
guard let index = colleagues.firstIndex(where: {
($0 as AnyObject) === (colleague as AnyObject)
}) else { return }
colleagueWrappers.remove(at: index)
}
Lastly, you need a means to actually invoke all of the colleagues. Add the following
methods below removeColleague(_:):
Both of these methods iterate through colleagues, the computed property you
defined before that automatically filters out nil instances, and call the passed-in
closure on each colleague instance.
You now have a very useful base Mediator class, and you’re ready to put this to good
use!
raywenderlich.com 280
Design Patterns by Tutorials Chapter 19: Mediator Pattern
Open the Mediator page from the File hierarchy, and enter this after Code
example:
As you may have guessed from these protocols, you’ll create a mediator-colleague
example where colleagues will send message strings via the mediator.
However, these won’t be just any colleagues — that wouldn’t be any fun. Instead, the
colleagues will be the Three Musketeers: the legendary swordsmen Athos, Porthos
and Aramis calling out battle cries to one another!
Okay, okay... maybe the example is a little silly, but it actually works really well! And,
maybe, it will even help you remember the mediator pattern — “The mediator design
pattern is the three musketeers calling each other!”
Enter the following code next; ignore the resulting compiler error for now:
// MARK: - Colleague
// 1
public class Musketeer {
// 2
public var name: String
public weak var mediator: MediatorProtocol?
// 3
public init(mediator: MediatorProtocol, name: String) {
self.mediator = mediator
self.name = name
raywenderlich.com 281
Design Patterns by Tutorials Chapter 19: Mediator Pattern
mediator.addColleague(self)
}
// 4
public func sendMessage(_ message: String) {
print("\(name) sent: \(message)")
mediator?.sendMessage(message, by: self)
}
}
4. Within sendMessage, you print out the name and passed-in message to the
console and then call sendMessage(_:by:) on the mediator. Ideally, the
mediator should then forward this message onto all of the other colleagues.
Here, you make Musketeer conform to Colleague. To do so, you implement its
required method colleague(_:didSendMessage:), where you print the Musketeer’s
name and the received message.
You next need to implement the mediator. Add the following code next to do so:
// MARK: - Mediator
// 1
public class MusketeerMediator: Mediator<Colleague> {
}
extension MusketeerMediator: MediatorProtocol {
// 2
public func addColleague(_ colleague: Colleague) {
self.addColleague(colleague, strongReference: true)
raywenderlich.com 282
Design Patterns by Tutorials Chapter 19: Mediator Pattern
// 3
public func sendMessage(_ message: String,
by colleague: Colleague) {
invokeColleagues(by: colleague) {
$0.colleague(colleague, didSendMessage: message)
}
}
}
2. Within addColleague(_:), you call its super class’ method for adding a
colleague, addColleague(_:strongReference:).
This takes care of the required mediator classes, so you’re now ready to try them out!
Add the following code next:
// MARK: - Example
let mediator = MusketeerMediator()
let athos = Musketeer(mediator: mediator, name: "Athos")
let porthos = Musketeer(mediator: mediator, name: "Porthos")
let aramis = Musketeer(mediator: mediator, name: "Aramis")
With the above, you declare an instance of MusketeerMediator called mediator and
three instances of Musketeer, called athos, porthos and aramis.
raywenderlich.com 283
Design Patterns by Tutorials Chapter 19: Mediator Pattern
Note that the message senders do not receive the message. For example, the message
sent by Athos was received by Porthos and Aramis, yet Athos did not receive it. This
is exactly the behavior you’d expect to happen!
Using mediator directly, it’s also possible to send a message to all colleagues. Add
following code to the end of the playground to do so:
mediator.invokeColleagues() {
$0.colleague(nil, didSendMessage: "Charge!")
}
All of them get the message this time. Now let’s charge onwards with the project!
However, you need to be careful about turning the mediator into a “god” object — an
object that knows about every other object within a system.
If your mediator gets too big, consider breaking it up into multiple mediator–
colleague systems. Alternatively, consider other patterns to break up the mediator,
such as delegating some of its functionality.
raywenderlich.com 284
Design Patterns by Tutorials Chapter 19: Mediator Pattern
Tutorial project
In this chapter, you’ll add functionality to an app called YetiDate. This app will help
users plan a date that involves three different locations: a bar, restaurant and movie
theater. It uses CocoaPods to pull in YelpAPI, a helper library for searching Yelp for
said venues.
If you haven’t used CocoaPods before, that’s OK! Everything you need has been
included for you in the starter project, so you don't need to run pod install. The
only thing you need to remember is to open YetiDate.xcworkspace, instead of the
YetiDate.xcodeproj file.
Before you can run the app, you first need to register for a Yelp API key.
If you didn’t work through CoffeeQuest, follow these instructions to generate a Yelp
API key.
• https://www.yelp.com/developers/v3/manage_app
Create an account if you don’t have one, or sign in. Next, enter the following in the
Create App form (or if you’ve created an app before, use your existing API key):
raywenderlich.com 285
Design Patterns by Tutorials Chapter 19: Mediator Pattern
• I have read and accepted the Yelp API Terms: Check this
Press Create New App to continue, and you should see a success message:
Open APIKeys.swift from the File hierarchy, and paste your API key where
indicated.
Note: You can change the location of the simulator by clicking Debug ▸
Location and then selecting a different option.
raywenderlich.com 286
Design Patterns by Tutorials Chapter 19: Mediator Pattern
If you build and run the app, you’ll be prompted to grant permission to access your
user’s location. Afterwards, however, you’ll see a blank map, and nothing happens!
searchClient.update(userCoordinate: userLocation.coordinate)
raywenderlich.com 287
Design Patterns by Tutorials Chapter 19: Mediator Pattern
This is what kicks off the process for searching for nearby businesses. Open
SearchClient.swift, and you’ll see several methods have // TODO comments within
them.
import CoreLocation.CLLocation
import YelpAPI
// 1
public protocol SearchColleague: class {
// 2
var category: YelpCategory { get }
var selectedBusiness: YLPBusiness? { get }
// 3
func update(userCoordinate: CLLocationCoordinate2D)
// 4
raywenderlich.com 288
Design Patterns by Tutorials Chapter 19: Mediator Pattern
// 5
func reset()
}
2. Next, you define two properties: category will be the YelpCategory to search
for, and selectedBusiness will be the YLPBusiness that has been selected.
You should know that YelpAPI actually doesn’t define categories as an enum, but
rather, it defines them as strings. To ensure correct string values are used, I’ve
added YelpCategory to Yeti Date for you with valid strings for restaurants, bars
and movie theaters and corresponding icon images.
import YelpAPI
// 1
func searchColleague(
_ searchColleague: SearchColleague,
didSelect business: YLPBusiness)
// 2
func searchColleague(
_ searchColleague: SearchColleague,
didCreate viewModels: Set<BusinessMapViewModel>)
// 3
func searchColleague(
_ searchColleague: SearchColleague,
searchFailed error: Error?)
}
raywenderlich.com 289
Design Patterns by Tutorials Chapter 19: Mediator Pattern
import CoreLocation
import YelpAPI
// 1
public let category: YelpCategory
public private(set) var selectedBusiness: YLPBusiness?
// 2
private var colleagueCoordinate: CLLocationCoordinate2D?
private unowned let mediator: SearchColleagueMediating
private var userCoordinate: CLLocationCoordinate2D?
private let yelpClient: YLPClient
// 3
private static let defaultQueryLimit = UInt(20)
private static let defaultQuerySort = YLPSortType.bestMatched
private var queryLimit = defaultQueryLimit
private var querySort = defaultQuerySort
// 4
public init(category: YelpCategory,
mediator: SearchColleagueMediating) {
self.category = category
self.mediator = mediator
self.yelpClient = YLPClient(apiKey: YelpAPIKey)
}
}
raywenderlich.com 290
Design Patterns by Tutorials Chapter 19: Mediator Pattern
3. You declare private properties for limiting search results: queryLimit, which
has a default value given by defaultQueryLimit, and querySort, which has a
default value given by defaultQuerySort. You’ll see shortly how these are used.
4. You declare the designated initializer, which accepts category and mediator.
// MARK: - SearchColleague
// 1
extension YelpSearchColleague: SearchColleague {
// 2
public func fellowColleague(_ colleague: SearchColleague,
didSelect business: YLPBusiness) {
colleagueCoordinate = CLLocationCoordinate2D(
business.location.coordinate)
queryLimit /= 2
querySort = .distance
performSearch()
}
// 3
public func update(userCoordinate: CLLocationCoordinate2D) {
self.userCoordinate = userCoordinate
performSearch()
}
// 4
public func reset() {
colleagueCoordinate = nil
queryLimit = YelpSearchColleague.defaultQueryLimit
querySort = YelpSearchColleague.defaultQuerySort
selectedBusiness = nil
performSearch()
}
raywenderlich.com 291
Design Patterns by Tutorials Chapter 19: Mediator Pattern
This results in a focused search around the colleagueCoordinate: You limit the
results by reducing queryLimit and show the closest results by changing
querySort to distance.
// 1
guard selectedBusiness == nil,
let coordinate = colleagueCoordinate ??
userCoordinate else { return }
// 2
let yelpCoordinate = YLPCoordinate(
latitude: coordinate.latitude,
longitude: coordinate.longitude)
let query = YLPQuery(coordinate: yelpCoordinate)
query.categoryFilter = [category.rawValue]
query.limit = queryLimit
query.sort = querySort
yelpClient.search(with: query) {
[weak self] (search, error) in
guard let self = self else { return }
guard let search = search else {
// 3
self.mediator.searchColleague(self,
searchFailed: error)
return
}
// 4
var set: Set<BusinessMapViewModel> = []
for business in search.businesses {
guard let coordinate = business.location.coordinate
else { continue }
raywenderlich.com 292
Design Patterns by Tutorials Chapter 19: Mediator Pattern
// 5
DispatchQueue.main.async {
self.mediator.searchColleague(self, didCreate: set)
}
}
This seems like a lot of work, but it’s actually not too difficult to understand.
1. You first validate that selectedBusiness is nil and that there’s either a non-nil
colleagueCoordinate or a non-nil userCoordinate. If either of these isn’t
true, you return early.
3. If there’s not a search object, then the Yelp API failed. If so, you inform the
mediator and return early.
5. You dispatch to the main queue and notify the mediator that the view models
were created by the YelpSearchColleague.
Great! This takes care of the colleagues, and you can now finish the mediator
implementation.
Open SearchClient.swift and replace the class declaration with the following:
raywenderlich.com 293
Design Patterns by Tutorials Chapter 19: Mediator Pattern
// MARK: - SearchColleagueMediating
// 1
extension SearchClient: SearchColleagueMediating {
// 2
public func searchColleague(
_ searchColleague: SearchColleague,
didSelect business: YLPBusiness) {
delegate?.searchClient(self,
didSelect: business,
for: searchColleague.category)
notifyDelegateIfAllBusinessesSelected()
}
// 3
public func searchColleague(
_ searchColleague: SearchColleague,
didCreate viewModels: Set<BusinessMapViewModel>) {
delegate?.searchClient(self,
didCreate: viewModels,
for: searchColleague.category)
}
// 4
public func searchColleague(
_ searchColleague: SearchColleague,
searchFailed error: Error?) {
delegate?.searchClient(self,
failedFor: searchColleague.category,
raywenderlich.com 294
Design Patterns by Tutorials Chapter 19: Mediator Pattern
error: error)
}
}
Just a few more methods to go! Replace the contents of setupColleagues() with the
following:
invokeColleagues() { colleague in
colleague.update(userCoordinate: userCoordinate)
}
raywenderlich.com 295
Design Patterns by Tutorials Chapter 19: Mediator Pattern
In response to getting a new userCoordinate, you pass this along to each of the
SearchColleague instances.
invokeColleagues() { colleague in
colleague.reset()
}
Likewise, you simply pass the reset() call onto each of the SearchColleague
instances.
Build and run the app. The map should now show restaurants, bars and movie
theaters.
raywenderlich.com 296
Design Patterns by Tutorials Chapter 19: Mediator Pattern
Upon tapping the checkmark, the related YelpSearchColleague will get its
selectedBusiness set, communicate this to its mediator, trigger the other
colleagues to do a new search and ultimately generate new view models to show on
the map! Eventually once you’ve selected one of each business type, you’ll see a
screen showing your choices.
Key points
You learned about the mediator pattern in this chapter. Here are its key points:
• The mediator pattern encapsulates how objects communicate with one another. It
involves four types: colleagues, a colleague protocol, a mediator, and a mediator
protocol.
• The colleagues are the objects that communicate; the colleague protocol defines
methods and properties all colleagues must have; the mediator controls the
communication of the colleagues; and the mediator protocol defines required
methods and properties that the mediator must have.
raywenderlich.com 297
Design Patterns by Tutorials Chapter 19: Mediator Pattern
• In lieu of talking directly, colleagues hold onto and communicate through the
mediator. The colleague protocol and mediator protocol helps prevent tight
coupling between all objects involved.
• YelpSearchClient isn’t very efficient with searches. You can improve this by
using caching and only performing searches when absolutely required.
• Why stop at just restaurants, bars and movie theaters? You could let users pick
whichever categories they’re interested in grouping together.
Each of these are possible using the existing patterns that you’ve learned in this
book. Feel free to continue building out Yeti Date as much as you like.
When you're ready, continue onto the next chapter to learn about the composite
design pattern.
raywenderlich.com 298
20 Chapter 20: Composite
Pattern
By Jay Strawn
The composite pattern is a structural pattern that groups a set of objects into a tree
structure so they may be manipulated as though they were one object. It uses three
types:
1. The component protocol ensures all constructs in the tree can be treated the
same way.
2. A leaf is a component of the tree that does not have child elements.
Both composites and leaf nodes derive from the component protocol. You can even
have several different leaf classes held in a composite object.
raywenderlich.com 299
Design Patterns by Tutorials Chapter 20: Composite Pattern
For example, an Array is a composite. The component is the Array itself. The
composite is a private container used by Array to contain leaf objects. Each leaf is a
concrete type such as Int, String or whatever you add to the Array.
You can solve this problem with the composite pattern by treating branches and
nodes the same by making them conform to a protocol. This adds a layer of
abstraction to your models and ultimately reduces their complexity.
Playground example
Open AdvancedDesignPatterns.xcworkspace in the Starter directory, and then
open the Composite page.
For this playground example, you’ll make an app that stores different elements in a
tree pattern.
A file hierarchy is an everyday example of the composite pattern. Think about files
and folders. All .mp3 and .jpeg files, as well as folders, share a lot of functions:
“open”, “move to trash,” “get info,” “rename,” etc. You can move and store groups of
different files, even if they aren’t all the same type, because they all conform to a
component protocol.
raywenderlich.com 300
Design Patterns by Tutorials Chapter 20: Composite Pattern
To make your own file hierarchy in the playground, add the following after Code
Example:
import Foundation
protocol File {
var name: String { get set }
func open()
}
You’ve just created a component protocol, which all the leaf objects and composites
will conform to. Next, you’re going to add a couple of leaf objects. Add the following
to the end of the playground:
func open() {
print("Opening \(name) by \(author) in iBooks...\n")
}
}
func open() {
print("Playing \(name) by \(artist) in iTunes...\n")
}
}
You’ve added two leaf objects that conform to the component protocol. They all have
a name property and an open function, but each open() varies based on the object’s
class.
raywenderlich.com 301
Design Patterns by Tutorials Chapter 20: Composite Pattern
init(name: String) {
self.name = name
}
func open() {
print("Displaying the following files in \(name)...")
for file in files {
print(file.name)
}
print("\n")
}
}
Your Folder object is a composite, and it has an array that can hold any object that
conforms to the File protocol. This means that, not only can a Folder hold Music
and eBook objects, it can also hold other Folder objects.
Feel free to play around with creating objects and placing them in folders within the
playground. Here’s one example showcasing a few leaf objects and composites:
documents.addFile(file: musicFolder)
documents.addFile(file: justKids)
musicFolder.addFile(file: psychoKiller)
musicFolder.addFile(file: rebelRebel)
blisterInTheSun.open()
justKids.open()
raywenderlich.com 302
Design Patterns by Tutorials Chapter 20: Composite Pattern
documents.open()
musicFolder.open()
You’re able to treat all of these objects uniformly and call the same functions on
them. But, to quote the Talking Heads song mentioned above: "Qu’est-ce que c’est?
(What does this mean?)"
Using composite patterns becomes meaningful when you’re able to treat different
objects the same way, and reusing objects and writing unit tests becomes much less
complicated.
Imagine trying to create a container for your files without using a component
protocol! Storing different types of objects would get complicated very quickly.
Tutorial project
Throughout this section, you’ll add functionality to an app called Defeat Your ToDo
List.
As the user checks items off, a warrior at the top of the screen moves closer to
treasure at the end of a dungeon. The warrior reaches the end when the user
completes 100% of the tasks.
In this project, you’re going to add a feature in which a user can create a task that
holds smaller tasks within, like a checklist.
raywenderlich.com 303
Design Patterns by Tutorials Chapter 20: Composite Pattern
First, open Models.swift and add the following below import Foundation:
protocol ToDo {
var name: String { get set }
var isComplete: Bool { get set }
var subtasks: [ToDo] { get set }
}
Here, you’ve added a component protocol, called ToDo, to which all of your to-do
objects should conform. You’ve also added a composite object called
ToDoItemWithCheckList, which stores your checklist items in an array called
subtasks.
raywenderlich.com 304
Design Patterns by Tutorials Chapter 20: Composite Pattern
Now, in order to actually use the composite pattern, you need to make your default
to-do conform to the component protocol. Still in Models.swift, replace ToDoItem
with the following code:
init(name: String) {
self.name = name
isComplete = false
subtasks = []
}
}
You’ll notice that, in order to have your default ToDoItem conform to the ToDo
protocol, you have to give it a subtasks property. While initializing subtasks as an
empty array may seem like an unnecessary added complexity, you’ll see in the next
steps that having both classes include all possible properties makes it easier to reuse
the custom ToDoCell for the collection view in your view controller.
First, you want both arrays to accept items that conform to the component protocol
instead of simply ToDoItem. Replace the two properties with the following:
You have to do this because Swift can’t figure out whether the protocol, ToDo, is a
struct or a class.
raywenderlich.com 305
Design Patterns by Tutorials Chapter 20: Composite Pattern
Next, open ViewController.swift. Now, it’s time to get your collection view cells to
display both ToDoItem and ToDoItemWithCheckList.
if currentToDo is ToDoItemWithCheckList {
cell.subtasks = currentToDo.subtasks
}
This if statement populates the subtasks in ToDoCell. The other collection view on
the custom ToDoCell is already set up for you, so no changes need to be made there.
Next, for the collection view located in the view controller, you want to be able to
change the cell’s height based on how many subtasks are on the checklist of your to-
do item.
raywenderlich.com 306
Design Patterns by Tutorials Chapter 20: Composite Pattern
Now, each cell’s height will increase by 60 for each subtask in the composite to-do
item.
Now, it’s time to add the ability for the user to create a ToDoItemWithCheckList!
Add the following method to the end of the MARK: - Internal extension:
func createTaskWithChecklist() {
let controller = UIAlertController(
title: "Task Name",
message: "",
preferredStyle: .alert)
controller.addTextField { textField in
textField.placeholder = "Enter Task Title"
}
for _ in 1...4 {
controller.addTextField { textField in
textField.placeholder = "Add Subtask"
}
}
raywenderlich.com 307
Design Patterns by Tutorials Chapter 20: Composite Pattern
subtasks: subtasks)
self?.toDos.append(currentToDo)
self?.toDoListCollectionView.reloadData()
self?.setWarriorPosition()
}
controller.addAction(
UIAlertAction(title: "Task with Checklist", style: .default) {
[weak self] _ in
self?.createTaskWithChecklist()
})
All set! Now you can add as many to-do items as you like. Build and run the app. Try
out the new functionality, and go get that treasure!
Key points
You learned about the composite pattern in this chapter. Here are its key points:
• The composite pattern is a structural pattern that groups a set of objects into a
tree so that they may be manipulated as though they were one object.
• If your app’s class hierarchy forms a branching pattern, you can treat branches and
nodes as almost the same objects by conforming them to a component protocol.
The protocol adds a layer of abstraction to your models, which reduces their
complexity.
• This is a great pattern to help simplify apps that have multiple classes with similar
features. With it, you can reuse code more often and reduce complexity in your
classes.
raywenderlich.com 308
Design Patterns by Tutorials Chapter 20: Composite Pattern
With your Defeat Your ToDo List app now using a composite pattern, it’s really
convenient that you can reuse the same custom cell on both ToDoItem and
ToDoItemWithCheckList. Also, since a ToDoItemWithCheckList can hold another
ToDoItemWithCheckList, you could actually write this app to have an infinite
number of checklists within checklists! (We wouldn’t recommend that on such a tiny
screen, though!)
raywenderlich.com 309
21 Chapter 21: Command
Pattern
By Joshua Greene
Hence, this pattern allows you to model the concept of executing an action.
raywenderlich.com 310
Design Patterns by Tutorials Chapter 21: Command Pattern
Playground example
Open AdvancedDesignPatterns.xcworkspace in the Starter directory, and then
open the Command page.
For this playground example, you’ll create a simple guessing game: a Doorman will
open and close a Door a random number of times, and you’ll guess in advance
whether the door will be open or closed in the end.
import Foundation
// MARK: - Receiver
public class Door {
public var isOpen = false
}
Door is a simple model that will act as the receiver. It will be opened and closed by
setting its isOpen property.
// MARK: - Command
// 1
public class DoorCommand {
public let door: Door
public init(_ door: Door) {
self.door = door
}
public func execute() { }
}
// 2
public class OpenCommand: DoorCommand {
public override func execute() {
print("opening the door...")
door.isOpen = true
}
}
raywenderlich.com 311
Design Patterns by Tutorials Chapter 21: Command Pattern
// 3
public class CloseCommand: DoorCommand {
public override func execute() {
print("closing the door...")
door.isOpen = false
}
}
1. You first define a class called DoorCommand, which acts at the command. This
class is intended to be an abstract base class, meaning you won’t instantiate it
directly. Rather, you will instantiate and use its subclasses.
This class has one property, door, which you set within its initializer. It also has a
single method, execute(), which you override within its subclasses.
// MARK: - Invoker
// 1
public class Doorman {
// 2
public let commands: [DoorCommand]
public let door: Door
// 3
public init(door: Door) {
let commandCount = arc4random_uniform(10) + 1
self.commands = (0 ..< commandCount).map { index in
return index % 2 == 0 ?
OpenCommand(door) : CloseCommand(door)
}
self.door = door
}
// 4
public func execute() {
print("Doorman is...")
commands.forEach { $0.execute() }
}
}
raywenderlich.com 312
Design Patterns by Tutorials Chapter 21: Command Pattern
1. You define a class called Doorman, which will act as the invoker.
4. You lastly define execute(), wherein you call execute() on each of the
commands.
Great! You’re ready to try out these classes. Enter the following at the end of the
playground:
// MARK: - Example
public let isOpen = true
print("You predict the door will be " +
"\(isOpen ? "open" : "closed").")
print("")
You make a prediction for whether the Door will ultimately be open or closed, as
determined by isOpen. You should see this printed to the console:
You create a door and doorman, and then call doorman.execute(). You should see
something like this printed to the console. The number of opening and closing
statements will depend on whatever random number is chosen!
Doorman is...
opening the door...
closing the door...
opening the door...
raywenderlich.com 313
Design Patterns by Tutorials Chapter 21: Command Pattern
To complete the game, you should also print out whether your guess was right or
wrong.
if door.isOpen == isOpen {
print("You were right! :]")
} else {
print("You were wrong :[")
}
print("The door is \(door.isOpen ? "open" : "closed").")
To repeat the game, press the “Stop Playground” button, and then press the “Play”
button that appears.
Tutorial project
You’ll build a game app called RayWenToe in this chapter. This is a variation on
TicTacToe. Here are the rules:
1. Like TicTacToe, players place Xs and Os on a 3x3 gameboard. The first player is X,
and the second player is O.
2. Unlike TicTacToe, each player secretly makes five selections at the beginning of
the game, which may not be changed. Players then alternate placing Xs and Os
on the gameboard in their preselected order.
raywenderlich.com 314
Design Patterns by Tutorials Chapter 21: Command Pattern
3. If a player places his mark on a spot that’s already taken, his mark overwrites the
existing mark.
4. A player may select the same spot multiple times, and he may even select the
same spot for all of his selections.
5. After all of the players’ selections have been played, a winner is decided.
6. Like TicTacToe, if only one player has three marks in a row — vertically,
horizontally or diagonally — that player is the winner.
7. If both players have three marks in a row, or neither player has, the first player
(X) is the winner.
8. Thereby, it’s a reasonable strategy for the first player to try to get three Xs in a
row or to prevent his opponent from getting three Os in a row.
9. The only way for the second player (O) to win is to get three Os in a row without
his opponent having three Xs in a row as well.
Can you guess which pattern you’ll use? The command pattern, of course!
Build and run, and you’ll be presented with a Select Gameplay Mode screen:
raywenderlich.com 315
Design Patterns by Tutorials Chapter 21: Command Pattern
If you tap on a spot, however, nothing happens. You need to implement this logic.
RayWenToe uses the state pattern — see Chapter 15, “State Pattern,” if you’re not
familiar with it — to support both one-player and two-player modes. Specifically, it
uses three states:
Open PlayerInputState.swift, and you’ll see there are a few methods containing
TODO: - comments. Likewise, if you open ComputerInputState.swift and
PlayGameState.swift, you’ll see a few other methods with similar comments.
raywenderlich.com 316
Design Patterns by Tutorials Chapter 21: Command Pattern
// 1
public struct MoveCommand {
// 2
public var gameboard: Gameboard
// 3
public var gameboardView: GameboardView
// 4
public var player: Player
// 5
public var position: GameboardPosition
}
1. You first defined a new struct called MoveCommand. Ultimately, this will place a
player’s move onto the gameboard and the gameboardView.
3. The GameboardView is a view for the RayWenToe board. It already contains logic
to draw the board and to draw a MarkView, representing either an X or an O, at a
given position. It also has the logic to notify its delegate in response to
touches, which has been set to GameplayViewController.
4. The Player represents the user that performed this move. It contains a
markViewPrototype, which uses the prototype pattern — see Chapter 14,
“Prototype Pattern,” if you’re not familiar with it — to allow a new MarkView to
be created by copying it.
raywenderlich.com 317
Design Patterns by Tutorials Chapter 21: Command Pattern
In order to be useful, you also need to declare a means to execute this command.
Add the following method next, right before the closing curly brace:
// 2
gameboardView.placeMarkView(
player.markViewPrototype.copy(), at: position,
animated: true, completion: completion)
}
1. You first set the player at the position on the gameboard. This doesn’t affect
how the view looks but, rather, it’s used to determine the game’s winner at the
end.
2. You then create a copy of the player’s markViewPrototype and set this at the
given position on the gameboardView. This method has already been
implemented for you, including animation and calling the completion closure
when its finished. If you’re curious how it works, see GameboardView.swift for
its implementation.
Since gameboard and gameboardView are acted upon by this command, they are both
receivers.
With this done, you’re now ready to put the command into use! Open
GameManager.swift and add the following, right after the gameboard property:
You’ll use this to hold onto the MoveCommand objects for a given Player.
Next, open GameState.swift and add the following, right after the gameplayView
property:
Here, you declare a computed property for movesForPlayer, which sets and returns
gameManager.movesForPlayer.
raywenderlich.com 318
Design Patterns by Tutorials Chapter 21: Command Pattern
This handles storing the command objects! You next need to actually create them.
// 1
public override func addMove(at position: GameboardPosition) {
// 2
let moveCount = movesForPlayer[player]!.count
guard moveCount < turnsPerPlayer else { return }
// 3
displayMarkView(at: position, turnNumber: moveCount + 1)
// 4
enqueueMoveCommand(at: position)
updateMoveCountLabel()
}
raywenderlich.com 319
Design Patterns by Tutorials Chapter 21: Command Pattern
movesForPlayer[player]!.append(newMove)
You here create a new MoveCommand and append this to the existing array at
movesForPlayer[player].
Build and run, select One Player Mode and tap a spot on the gameboard. You should
now see that an X appears! You can even tap on the same spot multiple times, and
this is handled correctly, too.
raywenderlich.com 320
Design Patterns by Tutorials Chapter 21: Command Pattern
You first verify the player has made all of her selections. If not, you return early.
Otherwise, you call gameManager.transitionToNextState(). Said method simply
moves to the next GameState: in one-player mode, this transitions to
ComputerInputState, and, in two-player mode, this goes to another
PlayerInputState for the other player.
// 1
var moves = movesForPlayer[player]!
guard let position = moves.popLast()?.position else { return }
// 2
movesForPlayer[player] = moves
updateMoveCountLabel()
// 3
let markView = gameboardView.markViewForPosition[position]!
_ = markView.turnNumbers.popLast()
// 4
guard markView.turnNumbers.count == 0 else { return }
gameboardView.removeMarkView(at: position, animated: false)
1. First, you get the moves for the given player from movesForPlayer, and you call
popLast() to remove and return the last object. If there aren’t any commands to
pop, this will return nil, and you return early. If there is a command that’s
popped, you get its position.
2. Next, you update movesForPlayer[player] with the new array of moves and call
updateMoveCountLabel() to show the new number of turns remaining.
raywenderlich.com 321
Design Patterns by Tutorials Chapter 21: Command Pattern
3. Next, you get the markView from the gameboardView and call
turnNumbers.popLast() on it. MarkView uses turnNumbers in order to display
the order that it was selected. This is an array since the player can select the
same spot more than once.
Build and run, select One Player Mode and tap a spot to add a move. Then, press
Undo, and your move will be removed.
As its name implies, this method combines the MoveCommand objects for Player1 and
Player2 into a single array. You’ll use this to alternate performing each moves for
each player.
// 1
guard index < moves.count else {
displayWinner()
return
}
// 2
let move = moves[index]
move.execute(completion: { [weak self] in
raywenderlich.com 322
Design Patterns by Tutorials Chapter 21: Command Pattern
1. You check that the passed-in index is less than moves.count. If it isn’t, then all
of the moves have been played, and you call displayWinner() to calculate and
display the winner.
2. You get the move for the given index and then execute it. Within the
completion closure, you recursively call performMove(at: with:) again,
incrementing the index by 1. In this manner, you will execute each of the moves
in order.
You also need to call these methods. Replace the TODO comment within begin() with
the following:
Awesome! You’re ready to try out the game. Build and run, but this time select Two
Player Mode.
Select gameboard spots for the first player and press Ready. Then, select spots for
the second player and press Play. You’ll then see each of the MoveCommands executed
in order and animated onscreen.
raywenderlich.com 323
Design Patterns by Tutorials Chapter 21: Command Pattern
If you press New Game, however, you’ll notice there’s an issue - the “moves left”
label shows as 0! This is because you don't currently reset movesForPlayer
whenever a new game is started. Fortunately, this is easy to fix.
Open GameManager.swift and replace the TODO comment within newGame() with
the following:
You can now play as many games as you'd like in Two Player Mode!
In case you don't have a friend around, you also need to complete One Player Mode.
To do so, you'll need to complete ComputerInputState.swift. Instead of accepting
spot selections from a user as PlayerInputState does, ComputerInputState will
generate these automatically.
movesForPlayer[player] = positions.map {
MoveCommand(gameboard: gameboard,
gameboardView: gameboardView,
player: player,
position: $0)
}
gameManager.transitionToNextState()
The logic to generate positions to play on has already been implemented for you,
via generateRandomWinningCombination(). Here, you map those positions to
create an array of MoveCommand objects, which you set on movesForPlayer. You then
immediately called gameManager.transitionToNextState(), which will ultimately
transition to PlayGameState and begin the game.
Build and run, and select One Player Mode. Pick your spots, press Play, and watch
the game play out!
Key points
You learned about the command pattern in this chapter. Here are its key points:
raywenderlich.com 324
Design Patterns by Tutorials Chapter 21: Command Pattern
• This pattern works best for actions that need to be stored and executed later. If you
always intend to execute actions immediately, consider calling the methods
directly on the receiver instead.
• You can use a larger board size, instead of the vanilla size of 3x3. Both
GameboardView and Gameboard have been written generically to support arbitrary
board sizes of 3x3 or larger, so you can easily change this and see how it affects the
game.
• Instead of just showing a text label for who won, you can create a new GameState
to draw a line connecting the winning views.
• You can add a three-person variation and a new mark entirely, instead of just X and
O.
Each of these is possible using the existing patterns you've already learned from this
book. Feel free to continue experimenting with RayWenToe as much as you like.
When you’re ready, continue onto the next chapter to learn about the chain-of-
responsibility pattern.
raywenderlich.com 325
22 Chapter 22: Chain-of-
Responsibility Pattern
By Joshua Greene
raywenderlich.com 326
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
1. The client accepts and passes events to an instance of a handler protocol. Events
may be simple, property-only structs or complex objects, such as intricate user
actions.
2. The handler protocol defines required properties and methods that concrete
handlers must implement. This may be substituted for an abstract, base class
instead allowing for stored properties on it. Even then, it’s still not meant to be
instantiated directly. Rather, it only defines requirements that concrete handlers
must fulfill.
3. The first concrete handler implements the handler protocol, and it’s stored
directly by the client. Upon receiving an event, it first attempts to handle it. If it’s
not able to do so, it passes the event on to its next handler.
Thereby, the client can treat all of the concrete handlers as if they were a single
instance. Under the hood, each concrete handler determines whether or not to
handle an event passed to it or pass it on to the next handler. This happens without
the client needing to know anything about the process!
If there aren’t any concrete handlers capable of handling the event, the last handler
simply returns nil, does nothing or throws an error depending on your
requirements.
Concrete handlers may be different classes entirely or they may be the same class
type but different instances and configurations.
For example, you can use this pattern to implement a VendingMachine that accepts
coins:
• The VendingMachine itself would be the client and would accept coin input
events.
• The concrete handlers would be coin validators. They would determine whether an
unknown coin was valid based on certain criteria, such as a coin’s weight and
diameter, and use this to create a known coin type, such as a Penny.
raywenderlich.com 327
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
Playground example
Open AdvancedDesignPatterns.xcworkspace in the Starter directory, and then
open the ChainOfResponsibility page.
You’ll consider each coin’s diameter and weight to validate said coins. Here are the
official specifications per the United States Mint:
Ok, that’s all you need to know, so it’s time to make some money! Or rather, accept
some money — you’re creating a vending machine, after all.
Before creating the chain-of-responsibility specific classes, you first need to declare a
few models. Add the following right after Code Example:
// MARK: - Models
// 1
public class Coin {
// 2
public class var standardDiameter: Double {
return 0
}
public class var standardWeight: Double {
return 0
}
// 3
public var centValue: Int { return 0 }
public final var dollarValue: Double {
raywenderlich.com 328
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
// 4
public final let diameter: Double
public final let weight: Double
// 5
public required init(diameter: Double, weight: Double) {
self.diameter = diameter
self.weight = weight
}
// 6
public convenience init() {
let diameter = type(of: self).standardDiameter
let weight = type(of: self).standardWeight
self.init(diameter: diameter, weight: weight)
}
}
1. You first create a new class for Coin, which you’ll use as the superclass for all coin
types.
4. You create diameter and weight as stored properties. As coins age, they get
dinged and worn down. Consequently, their diameters and weights tend to
decrease slightly over time. You’ll compare a coin’s diameter and weight against
the standards later when you create the coin validators.
5. You create a designated initializer that accepts a specific coin’s diameter and
weight. It’s important that this is a required initializer: You’ll use this to create
subclasses by calling it on a Coin.Type instance - i.e. a type of Coin.
6. You lastly create a convenience initializer. This creates a standard coin using
type(of: self) to get the standardDiameter and standardWeight. This way,
you won’t have to override this initializer for each specific coin subclass.
raywenderlich.com 329
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
To inspect coins, you’ll print them to the console. You make Coin conform to
CustomStringConvertible to give it a nice description that includes the coin’s type,
diameter, dollarValue and weight.
You next need to add concrete coin types. Add this code to do so:
raywenderlich.com 330
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
return 24.26
}
public override class var standardWeight: Double {
return 5.670
}
public override var centValue: Int { return 25 }
}
With the previous code, you create subclasses of Coin for Penny, Nickel, Dime and
Quarter using the coin specifications provided earlier.
Great! You’re now ready to add the chain-of-responsibility classes. Add the following
to the end of the playground:
// MARK: - HandlerProtocol
public protocol CoinHandlerProtocol {
var next: CoinHandlerProtocol? { get }
func handleCoinValidation(_ unknownCoin: Coin) -> Coin?
}
Here, you declare the handler protocol, which has requirements for
handleCoinValidation(_:) and a next property.
// 2
public var next: CoinHandlerProtocol?
public let coinType: Coin.Type
public let diameterRange: ClosedRange<Double>
public let weightRange: ClosedRange<Double>
// 3
public init(coinType: Coin.Type,
diameterVariation: Double = 0.05,
weightVariation: Double = 0.05) {
self.coinType = coinType
raywenderlich.com 331
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
}
}
• coinType will be the specific Coin this instance will create. Consequently, you
won’t need to create specific coin validators for Penny, Nickel, Dime and Quarter.
• diameterRange and weightRange will be the valid range for this specific coin.
// 1
public func handleCoinValidation(_ unknownCoin: Coin) ->
Coin? {
guard let coin = createCoin(from: unknownCoin) else {
return next?.handleCoinValidation(unknownCoin)
}
return coin
}
// 2
private func createCoin(from unknownCoin: Coin) -> Coin? {
print("Attempt to create \(coinType)")
guard diameterRange.contains(unknownCoin.diameter) else {
print("Invalid diameter")
return nil
}
guard weightRange.contains(unknownCoin.weight) else {
print("Invalid weight")
return nil
}
let coin = coinType.init(diameter: unknownCoin.diameter,
weight: unknownCoin.weight)
print("Created \(coin)")
return coin
}
}
raywenderlich.com 332
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
If it doesn’t, you print an error message and return nil. If it does, you call
coinType.init(diameter:weight:) passing the values from unknownCoin to
create a new instance of the coinType. Pretty cool how you can use a required
initializer like that, right?
You’ve got just one more class to go! Add the following to the end of the playground:
// MARK: - Client
// 1
public class VendingMachine {
// 2
public let coinHandler: CoinHandler
public var coins: [Coin] = []
// 3
public init(coinHandler: CoinHandler) {
self.coinHandler = coinHandler
}
}
1. You create a new class for VendingMachine, which will act as the client.
2. This has just two properties: coinHandler and coins. VendingMachine doesn’t
need to know that its coinHandler is actually a chain of handlers, but instead it
simply treats this as a single object. You’ll use coins to hold onto all of the valid,
accepted coins.
3. The initializer is also very simple: You simply accept a passed-in coinHandler
instance. VendingMachine doesn’t need to how a CoinHandler is set up, as it
simply uses it.
raywenderlich.com 333
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
You also need a method to actually accept coins. Add this next code right before the
closing class curly brace for VendingMachine:
// 1
guard let coin = coinHandler.handleCoinValidation(unknownCoin)
else {
print("Coin rejected: \(unknownCoin)")
return
}
// 2
print("Coin Accepted: \(coin)")
coins.append(coin)
// 3
let dollarValue = coins.reduce(0, { $0 + $1.dollarValue })
print("")
print("Coins Total Value: $\(dollarValue)")
// 4
let weight = coins.reduce(0, { $0 + $1.weight })
print("Coins Total Weight: \(weight) g")
print("")
}
2. If a valid Coin is created, you print a success message and append it to coins.
3. You then get the dollarValue for all of the coins and print this.
4. You lastly get the weight for all of the coins and print this, too.
You've created a vending machine — But you still need to try it out!
// MARK: - Example
// 1
let pennyHandler = CoinHandler(coinType: Penny.self)
let nickleHandler = CoinHandler(coinType: Nickel.self)
let dimeHandler = CoinHandler(coinType: Dime.self)
let quarterHandler = CoinHandler(coinType: Quarter.self)
raywenderlich.com 334
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
// 2
pennyHandler.next = nickleHandler
nickleHandler.next = dimeHandler
dimeHandler.next = quarterHandler
// 3
let vendingMachine = VendingMachine(coinHandler: pennyHandler)
1. Before you can instantiate a VendingMachine, you must first set up the
coinHandler objects for it. You do so by creating instances of CoinHandler for
pennyHandler, nickleHandler, dimeHandler and quarterHandler.
2. You then hook up the next properties for the handlers. In this case,
pennyHandler will be the first handler, followed by nickleHandler,
dimeHandler and lastly quarterHandler in the chain. Since there aren’t any
other handlers after quarterHandler, you leave its next set to nil.
You can now insert coins in the vendingMachine! Add the following to insert a
standard Penny:
Awesome — the penny was handled correctly. However, this one was easy: It was a
standard penny, after all!
Add the following code next to create an unknown Coin matching the criteria for a
Quarter:
raywenderlich.com 335
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
Great — the quarter was also handled correctly! Notice the print statements for
penny, nickel and dime, too? This is expected behavior: The unknown coin was
passed from CoinHandler to CoinHandler until, finally, the last one was able to
create a Quarter from it.
raywenderlich.com 336
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
You also need to consider what happens if an event can’t be handled. Will you return
nil, throw an error or do something else? You should identify this upfront, so you
can plan your system appropriately.
You should also consider whether or not an event needs to be processed by more
than one handler. As a variation on this pattern, you can forward the same event to
all handlers, instead of stopping at the first one that can handle it, and then return
an array of response objects.
Tutorial project
You’ll build an app called RWSecret in this chapter. This app allows users to decrypt
secret messages by attempting several known passwords provided by the user.
It’s OK if you’re not familiar with the iOS keychain or AES decryption — these
libraries do the heavy lifting for you! Your task will be to set up a handler chain to
perform decryption.
Open Finder and navigate to where you downloaded the resources for this chapter.
Then, open Starter\RWSecret\RWSecret.xcworkspace (not the .xcodeproj file) in
Xcode.
This app uses CocoaPods to pull in the open-source libraries. Everything has already
been included for you, so you don’t need to do pod install. You simply need to use
the .xcworkspace instead of the .xcodeproj file.
raywenderlich.com 337
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
Decryption failed!
While the view has already been set up to display secret messages, the app doesn’t
know how to decrypt them! Before you can add this functionality, you first need to
know a bit about how the app works.
Open SecretMessage.swift, and you’ll see this is a simple model with two
properties, encrypted and decrypted:
• encrypted holds onto to the encrypted form of the message. This is set via
init(encrypted:), so it will always have a value.
• decrypted is set whenever the message is decrypted. This is initially set to nil, as
SecretMessage doesn’t know how to perform decryption.
raywenderlich.com 338
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
secretMessage.decrypted =
passwordClient.decrypt(secretMessage.encrypted)
passwordClient acts as the client for handling decryption requests, but seemingly,
this method must always be returning nil.
import Foundation
import RNCryptor
DecryptionHandler will act as a concrete handler. This has two properties: next
per the DecryptionHandlerProtocol requirement, and password to hold onto the
decryption password to use.
raywenderlich.com 339
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
You’re making great progress! You next need to add a reference to the
DecryptionHandlerProtocol on the client. Open PasswordClient.swift and add
the following property, right after the others:
// 1
guard passwords.count > 0 else {
decryptionHandler = nil
return
}
// 2
var current = DecryptionHandler(password: passwords.first!)
decryptionHandler = current
// 3
for i in 1 ..< passwords.count {
let next = DecryptionHandler(password: passwords[i])
current.next = next
current = next
}
raywenderlich.com 340
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
1. You first ensure that passwords isn’t empty. Otherwise, you set
decryptionHandler to nil.
2. You create a DecryptionHandler for the first password, and you set this to both
current and decryptionHandler.
You lastly need to implement decrypt(_:). Replace the contents of it with the
following:
Since decrypt(_:) takes a String, you first attempt to convert this into base-64
encoded data and then pass this to the decryptionHandler for decryption. If this is
successful, you return the resulting decrypted value. Otherwise, you return nil.
Great job — that takes care of the chain-of-responsibility implementation! Build and
run. Tap to decrypt on the first cell. And then... you still see a Decryption failed!
in the console!? What gives?
Remember how RWSecret uses the keychain to hold onto passwords? Yep, you first
need to add the correct passwords. Tap on the Passwords button in the top right
corner. Then, type password into the text field and press add.
raywenderlich.com 341
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
Tap < Decrypt to return to the decryption screen and then Tap to Decrypt each cell
to reveal the secret messages!
raywenderlich.com 342
Design Patterns by Tutorials Chapter 22: Chain-of-Responsibility Pattern
Key points
You learned about the chain-of-responsibility pattern in this chapter. Here are its key
points:
• The client accepts events and passes them onto its handler protocol instance; the
handler protocol defines required methods and properties each concrete handler
much implement; and each concrete handler can accept an event and in turn
either handle it or pass it onto the next handler.
• This pattern thereby defines a group of related handlers, which vary based on the
type of event each can handle. If you need to handle new types of events, you
simply create a new concrete handler.
• You can add the ability to input and encrypt secret messages, instead of just
decrypting them.
• You can add the capability to send secret messages to other users.
Each of these is possible using the existing patterns that you’ve already learned from
this book. Feel free to continue experimenting with RWSecret as much as you like.
When you're ready, continue onto the next chapter to learn about the coordinator
design pattern.
raywenderlich.com 343
23 Chapter 23: Coordinator
Pattern
By Joshua Greene
The coordinator pattern is a structural design pattern for organizing flow logic
between view controllers. It involves the following components:
1. The coordinator is a protocol that defines the methods and properties all
concrete coordinators must implement. Specifically, it defines relationship
properties, children and router. It also defines presentation methods, present
and dismiss.
raywenderlich.com 344
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
3. The router is a protocol that defines methods all concrete routers must
implement. Specifically, it defines present and dismiss methods for showing
and dismissing view controllers.
4. The concrete router knows how to present view controllers, but it doesn’t know
exactly what is being presented or which view controller will be presented next.
Instead, the coordinator tells the router which view controller to present.
This pattern can be adopted for only part of an app, or it can be used as an
“architectural pattern” to define the structure of an entire app.
You’ll see both of these at work in this chapter: In the Playground example, you’ll
call a coordinator from an existing view controller, and in the Tutorial Project,
you’ll adopt this pattern across the entire app.
Consequently, view controllers are much more reusable: If you want to create a new
flow within your app, you simply create a new coordinator!
raywenderlich.com 345
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
Playground example
Open AdvancedDesignPatterns.xcworkspace in the starter directory, and then
open the Coordinator page.
For this playground example, you’ll create a step-by-step instruction flow. You could
use this for any instructions, such as app set up, first-time-help tutorials or any other
step-by-step flow.
To keep the example simple and focused on the design pattern, you’ll create a “How
to Code” flow that will show a set of view controllers with text only.
Hold down Option and left-click the arrow next to the Coordinator page to expand
all of its subfolders. You’ll see several folders have already been added for you:
Controllers contains all of the concrete view controllers. These are simple, vanilla
view controllers and have already been implemented for you.
raywenderlich.com 346
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
import UIKit
extension Router {
// 3
public func present(_ viewController: UIViewController,
animated: Bool) {
present(viewController,
animated: animated,
onDismissed: nil)
}
}
You're declaring a protocol called Router here. Here’s what this protocol defines:
1. You first define two present methods. The only difference is one takes an
onDismissed closure, and the other doesn’t. If provided, concrete routers will
execute the onDismissed whenever a view controller is dismissed, for example
via a “pop“ action in the case of a concrete router that uses a
UINavigationController.
2. You also declare dismiss(animated:). This will dismiss the entire router.
Depending on the concrete router, this may result in popping to a root view
controller, calling dismiss on a parentViewController or whatever action is
necessary per the concrete router’s implementation.
raywenderlich.com 347
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
nor the tutorial project require it. If you actually do need this in your own project,
feel free to declare one!
import UIKit
// 1
public class NavigationRouter: NSObject {
// 2
private let navigationController: UINavigationController
private let routerRootController: UIViewController?
private var onDismissForViewController:
[UIViewController: (() -> Void)] = [:]
// 3
public init(navigationController: UINavigationController) {
self.navigationController = navigationController
self.routerRootController =
navigationController.viewControllers.first
super.init()
}
}
3. You lastly create an initializer that takes a navigationController, and you set
the navigationController and routerRootController from it.
raywenderlich.com 348
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
You'll notice that this doesn't implement the Router protocol yet. So let's do that!
Add the following extension to the end of the file:
// MARK: - Router
extension NavigationRouter: Router {
// 1
public func present(_ viewController: UIViewController,
animated: Bool,
onDismissed: (() -> Void)?) {
onDismissForViewController[viewController] = onDismissed
navigationController.pushViewController(viewController,
animated: animated)
}
// 2
public func dismiss(animated: Bool) {
guard let routerRootController = routerRootController else {
navigationController.popToRootViewController(
animated: animated)
return
}
performOnDismissed(for: routerRootController)
navigationController.popToViewController(
routerRootController,
animated: animated)
}
// 3
private func performOnDismissed(for
viewController: UIViewController) {
raywenderlich.com 349
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
// MARK: - UINavigationControllerDelegate
extension NavigationRouter: UINavigationControllerDelegate {
Of course, you also need to actually set NavigationRouter as the delegate for the
navigationController.
navigationController.delegate = self
raywenderlich.com 350
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
// 1
var children: [Coordinator] { get set }
var router: Router { get }
// 2
func present(animated: Bool, onDismissed: (() -> Void)?)
func dismiss(animated: Bool)
func presentChild(_ child: Coordinator,
animated: Bool,
onDismissed: (() -> Void)?)
}
1. You declare relationship properties for children and router. You’ll use these
properties to provide default implementations within an extension on
Coordinator next.
2. You also declare required methods for present, dismiss and presentChild.
You can provide reasonable default implementations for both dismiss and
presentChild. Add the following extension to the end of the file:
extension Coordinator {
// 1
public func dismiss(animated: Bool) {
router.dismiss(animated: true)
}
// 2
public func presentChild(_ child: Coordinator,
animated: Bool,
onDismissed: (() -> Void)? = nil) {
children.append(child)
child.present(
animated: animated,
onDismissed: { [weak self, weak child] in
guard let self = self,
let child = child else {
return
}
self.removeChild(child)
raywenderlich.com 351
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
onDismissed?()
})
}
1. To dismiss a coordinator, you simply call dismiss on its router. This works
because whoever presented the coordinator is responsible for passing an
onDismiss closure to do any required teardown, which will be called by the
router automatically.
Remember how you wrote all that logic within NavigationRouter for handling
popping and dismissing? This is why you did that!
2. Within presentChild, you simply append the given child to children, and then
call child.present. You also take care of removing the child by calling
removeChild(_:) within the child’s onDismissed action, and lastly, you call the
provided onDismissed passed into the method itself.
Just like the Router didn’t declare a dismiss method for individual view controllers,
this Coordinator doesn’t declare a dismiss method for child coordinators. The
reasoning is the same: the examples in this chapter don’t require it! Of course, feel
free to add them, if necessary, to your application.
import UIKit
raywenderlich.com 352
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
// 2
private lazy var stepViewControllers = [
StepViewController.instantiate(
delegate: self,
buttonColor: UIColor(red: 0.96, green: 0, blue: 0.11,
alpha: 1),
text: "When I wake up, well, I'm sure I'm gonna be\n\n" +
"I'm gonna be the one writin' code for you",
title: "I wake up"),
StepViewController.instantiate(
delegate: self,
buttonColor: UIColor(red: 0.93, green: 0.51, blue: 0.07,
alpha: 1),
text: "When I go out, well, I'm sure I'm gonna be\n\n" +
"I'm gonna be the one thinkin' bout code for you",
title: "I go out"),
StepViewController.instantiate(
delegate: self,
buttonColor: UIColor(red: 0.23, green: 0.72, blue: 0.11,
alpha: 1),
text: "Cause' I would code five hundred lines\n\n" +
"And I would code five hundred more",
title: "500 lines"),
StepViewController.instantiate(
delegate: self,
buttonColor: UIColor(red: 0.18, green: 0.29, blue: 0.80,
alpha: 1),
text: "To be the one that wrote a thousand lines\n\n" +
"To get this code shipped out the door!",
title: "Ship it!")
]
// 3
private lazy var startOverViewController =
StartOverViewController.instantiate(delegate: self)
// MARK: - Coordinator
// 5
public func present(animated: Bool,
onDismissed: (() -> Void)?) {
let viewController = stepViewControllers.first!
router.present(viewController,
animated: animated,
onDismissed: onDismissed)
raywenderlich.com 353
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
}
}
1. First, you declare properties for children and router, which are required to
conform to Coordinator and Router respectively.
You set the view controllers’ texts to parody song lyrics of “I’m Gonna Be (500
miles)” by the Proclaimers. Google it if you don’t know it. Be sure to sing these
lyrics aloud to this tune, especially if others are nearby — they’ll love it..! Well,
depending on your singing skill, maybe it’s best if you sing alone!
3. Next, you declare a property for startOverViewController. This will be the last
view controller displayed and will simply show a button to “start over.”
4. Next, you create a designated initializer that accepts and sets the router.
// MARK: - StepViewControllerDelegate
extension HowToCodeCoordinator: StepViewControllerDelegate {
raywenderlich.com 354
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
}
}
// MARK: - StartOverViewControllerDelegate
extension HowToCodeCoordinator:
StartOverViewControllerDelegate {
Open the Coordinator page, and add the following right below Code Example:
import PlaygroundSupport
import UIKit
// 1
let homeViewController = HomeViewController.instantiate()
let navigationController =
UINavigationController(rootViewController: homeViewController)
// 2
let router = NavigationRouter(navigationController:
navigationController)
let coordinator = HowToCodeCoordinator(router: router)
raywenderlich.com 355
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
// 3
homeViewController.onButtonPressed = { [weak coordinator] in
coordinator?.present(animated: true, onDismissed: nil)
}
// 4
PlaygroundPage.current.liveView = navigationController
2. Next, you create the router using the navigationController, and in turn,
create the coordinator using the router.
Run the playground, and you should see the Live preview showing this in action. If
you don't, select Editor and ensure Live View is checked.
raywenderlich.com 356
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
Tap on How to Code to start the flow. Tap each of the buttons until you get to Start
Over. Once you tap this, the coordinator will be dismissed, and you’ll see How to
Code again.
For very simple apps, the Coordinator pattern may seem like overkill. You’ll be
required to create many additional classes upfront; namely, the concrete coordinator
and routers.
For long-term or complex apps, the coordinator pattern can help you provide needed
structure and increase view controllers’ reusability.
Tutorial project
You’ll build an app called RayPets in this chapter. This is a “pet” project by Ray: an
exclusive pets-only clinic for savvy iOS users.
Open Finder and navigate to where you downloaded the resources for this chapter.
Then, open starter ▸ RayPets ▸ RayPets.xcodeproj in Xcode.
raywenderlich.com 357
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
Before investigating why, take a look at the file hierarchy. In particular, you’ll find
there’s a Screens group, which contains view controllers and views that have been
implemented already.
These are your typical view controllers found in MVC. However, per the coordinator
pattern, they don’t know about view controller transitions. Instead, each informs its
delegate whenever a transition is required.
However, this part of the app hasn’t been implemented yet. To do so, you need to
implement a new concrete coordinator and router.
In the file hierarchy, you’ll also see there’s already a group for Coordinators. Within
this, you’ll find Coordinator.swift has already been copied from the playground
example.
You’ll also see a Routers group. This contains Router.swift that has likewise been
copied from the playground example.
Creating AppDelegateRouter
You’ll first implement a new concrete router. Within the Routers group, create a new
file called AppDelegateRouter.swift, and replace its contents with the following:
import UIKit
raywenderlich.com 358
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
// MARK: - Router
public func present(_ viewController: UIViewController,
animated: Bool,
onDismissed: (()->Void)?) {
window.rootViewController = viewController
window.makeKeyAndVisible()
}
This router is intended to hold onto the window from the AppDelegate.
This router will be held onto by the AppDelegate directly and isn't meant to be
dismissible. Thereby, you simply ignore calls to dismiss(animated:).
Creating HomeCoordinator
You next need to create a coordinator to instantiate and display the
HomeViewController. Within the Coordinators group, create a new file named
HomeCoordinator.swift. Replace its contents with the following, ignoring the
compiler error for now:
import UIKit
raywenderlich.com 359
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
This coordinator is pretty simple. You create properties for children and router,
which are required by the Coordinator protocol, and a simple initializer that sets
the router.
// MARK: - HomeViewControllerDelegate
extension HomeCoordinator: HomeViewControllerDelegate {
Using HomeCoordinator
You also need to actually use HomeCoordinator. To do so, open AppDelegate.Swift
and replace its contents with the following:
import UIKit
@UIApplicationMain
public class AppDelegate: UIResponder, UIApplicationDelegate {
raywenderlich.com 360
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
1. You first create lazy properties for coordinator, router, and window.
Build and run, and you’ll see the application displays the HomeViewController, just
at did before. However, you’re now set up to implement the coordinator pattern
across the entire app!
Creating PetAppointmentBuilderCoordinator
Open Models ▸ PetAppointment.swift, and you’ll see a model and related builder
has already been defined: PetAppointment and PetAppointmentBuilder.
import UIKit
raywenderlich.com 361
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
Sounds familiar, right? This is very similar to HomeCoordinator, and it’s a recurring
pattern you’ll see using coordinators: you instantiate a view controller, pass it to the
router to present it and receive feedback via delegate callbacks.
// MARK: - SelectVisitTypeViewControllerDelegate
extension PetAppointmentBuilderCoordinator:
SelectVisitTypeViewControllerDelegate {
// 1
builder.visitType = visitType
// 2
switch visitType {
case .well:
// 3
presentNoAppointmentViewController()
case .sick:
// 4
presentSelectPainLevelCoordinator()
}
raywenderlich.com 362
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
// MARK: - SelectPainLevelViewControllerDelegate
extension PetAppointmentBuilderCoordinator:
SelectPainLevelViewControllerDelegate {
// 1
builder.painLevel = painLevel
raywenderlich.com 363
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
// 2
switch painLevel {
// 3
case .none, .little:
presentFakingItViewController()
// 4
case .moderate, .severe, .worstPossible:
presentNoAppointmentViewController()
}
}
// MARK: - FakingItViewControllerDelegate
extension PetAppointmentBuilderCoordinator:
FakingItViewControllerDelegate {
raywenderlich.com 364
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
}
}
Wait a minute — so no matter what the user does, they ultimately winds up seeing
NoAppointmentRequiredViewController. What’s the deal with that?
I talked with Ray about this, and uh, he says it’s a marketing tactic to get customers
to come into the office... either that or, someone didn’t write a backend for this
example app. So it appears there’s nowhere to actually submit this data. What are you
going to do now?
Not to fear! Just like in real life when the backend isn’t ready, you fake it! Hence, the
app ultimately shows the NoAppointmentRequiredViewController, regardless of
the prior selection.
// MARK: - NoAppointmentRequiredViewControllerDelegate
extension PetAppointmentBuilderCoordinator:
NoAppointmentRequiredViewControllerDelegate {
Great! You now just need a concrete router to use with this coordinator.
raywenderlich.com 365
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
Creating ModalNavigationRouter
You may be wondering, “Couldn’t I just use NavigationRouter from the playground
example?”
Instead, you’ll create a new router that creates a new UINavigationController and
presents it using an existing parentViewController to support this use case.
import UIKit
// 1
public class ModalNavigationRouter: NSObject {
raywenderlich.com 366
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
// MARK: - Router
extension ModalNavigationRouter: Router {
// 1
public func present(_ viewController: UIViewController,
animated: Bool,
onDismissed: (() -> Void)?) {
onDismissForViewController[viewController] = onDismissed
if navigationController.viewControllers.count == 0 {
presentModally(viewController, animated: animated)
} else {
navigationController.pushViewController(
viewController, animated: animated)
}
}
// 3
navigationController.setViewControllers(
[viewController], animated: false)
parentViewController.present(navigationController,
animated: animated,
completion: nil)
}
// 4
public func dismiss(animated: Bool) {
performOnDismissed(for:
navigationController.viewControllers.first!)
raywenderlich.com 367
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
parentViewController.dismiss(animated: animated,
completion: nil)
}
// 5
private func performOnDismissed(for
viewController: UIViewController) {
guard let onDismiss =
onDismissForViewController[viewController] else { return }
onDismiss()
onDismissForViewController[viewController] = nil
}
}
raywenderlich.com 368
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
// MARK: - UINavigationControllerDelegate
extension ModalNavigationRouter:
UINavigationControllerDelegate {
This is just like the implementation from NavigationRouter in the playground: You
check if the from view controller has been popped, and if so, call
performOnDismissed to execute its on-dismiss closure.
navigationController.delegate = self
Remember the TO-DO within the HomeCoordinator? Yep, this is where you’ll trigger
the PetAppointmentBuilderCoordinator flow.
Open HomeCoordinator.swift and replace the TO-DO comment with the following:
let router =
ModalNavigationRouter(parentViewController: viewController)
let coordinator =
raywenderlich.com 369
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
PetAppointmentBuilderCoordinator(router: router)
presentChild(coordinator, animated: true)
You here create a new ModalNavigationRouter, which you then in turn pass use to
create a new PetAppointmentBuilderCoordinator and pass this to
presentChild(_:animated:) to kick off this flow.
Build and run, and then tap Schedule Visit to see the schedule-a-visit flow in
action!
Key points
You learned about the coordinator pattern in this chapter. Here are its key points:
• The coordinator pattern organizes flow logic between view controllers. It involves
a coordinator protocol, concrete coordinator, router protocol, concrete router and
view controllers.
• The coordinator defines methods and properties all concrete coordinators must
implement.
• The concrete coordinators know how to create concrete view controllers and
their order.
• The concrete view controllers are typical view controllers, but they don’t know
about other view controllers.
raywenderlich.com 370
Design Patterns by Tutorials Chapter 23: Coordinator Pattern
• This pattern can be adopted for only part of an app or used across an entire
application.
• http://khanlou.com/2015/01/the-coordinator/.
There are also several other structural and architectural patterns similar to
Coordinator. One example is VIPER, which further separates objects by
responsibility. You can learn more about it in this writeup on objc.io:
• https://www.objc.io/issues/13-architecture/viper/.
raywenderlich.com 371
C Conclusion
Let’s revisit that quote from the beginning of the book, taken from Design Patterns:
Elements of Reusable, Object-Oriented Software:
Hopefully you’ve seen in this book that good software design isn’t hard — it just
takes a little forethought and planning. To call such an important task hard scares
away novices, who must think that software design and design patterns in general
must be something reserved for the gurus of the discipline.
We think that nothing could be farther from the truth; good software design
practices can start early on in anyone’s career, and even if you’ve been a developer
for decades, there’s always something new to take away from a book like this. We
hope you’ve enjoyed reading it!
If you have any questions or comments as you work through this book, please stop by
our forums at http://forums.raywenderlich.com and look for the particular forum
category for this book.
Thank you again for purchasing this book. Your continued support is what makes the
tutorials, books, videos, conferences and other things we do at raywenderlich.com
possible, and we truly appreciate it!
Wishing you all the best in your continued adventures with design patterns,
raywenderlich.com 372