Update note: This tutorial was updated for iOS 8 and Swift by Vincent Ngo. Original post by Tutorial team member Eli Ganem.
iOS Design Patterns – you’ve probably heard the term, but do you know what it means? While most developers probably agree that design patterns are very important, there aren’t many articles on the subject and we developers sometimes don’t pay too much attention to design patterns while writing code.
Design patterns are reusable solutions to common problems in software design. They’re templates designed to help you write code that’s easy to understand and reuse. They also help you create loosely coupled code so that you can change or replace components in your code without too much of a hassle.
If you’re new to design patterns, then I have good news for you! First, you’re already using tons of iOS design patterns thanks to the way Cocoa is built and the best practices you’re encouraged to use. Second, this tutorial will bring you up to speed on all the major (and not so major) iOS design patterns that are commonly used in Cocoa.
In this two-part tutorial, you will create a Music Library app that will display your albums and their relevant information.
In the process of developing this app, you’ll become acquainted with the most common Cocoa design patterns:
- Creational: Singleton.
- Structural: MVC, Decorator, Adapter, Facade.
- Behavioral: Observer, and, Memento
Don’t be misled into thinking that this is an article about theory; you’ll get to use most of these design patterns in your music app. Your app will look like this by the end of the tutorial:
Let’s get started!
Getting Started
Download the starter project, extract the contents of the ZIP file, and open BlueLibrarySwift.xcodeproj in Xcode.
There are three things to note in the project:
- The
ViewController
has twoIBOutlet
connecting the table view and toolbar in storyboard. - The storyboard has 3 components which are setup with constraints for your convenience. The top component is where the album covers will be displayed. Below the album covers will be a table view which list information related to an album cover. Lastly the tool bar has two buttons, one to undo an action and another to delete an album that you select. The storyboard is shown below:
- A starter HTTP Client class (
HTTPClient
) with an empty implementation for you to fill in later.
Note: Did you know that as soon as you create a new Xcode project your code is already filled with design patterns? Model-View-Controller, Delegate, Protocol, Singleton – You get them all for free! :]
Before you dive into the first design pattern, you must create two classes to hold and display the album data.
Navigate to “File\New\File…” (or simply press Command+N). Select iOS > Cocoa Touch Class and then click Next. Set the class name to Album
and the subclass to NSObject
. Lastly choose Swift as the language. Click Next and then Create.
Open Album.swift and add the following properties to the class definition:
var title : String! var artist : String! var genre : String! var coverUrl : String! var year : String! |
Here you create five properties. The Album class will keep track of the title, artist, genre, album cover, and the year of the album.
Next add the following object initializer after the properties:
init(title: String, artist: String, genre: String, coverUrl: String, year: String) { super.init() self.title = title self.artist = artist self.genre = genre self.coverUrl = coverUrl self.year = year } |
This code creates an initializer for the Album class. When you create a new album, you’ll pass in the album name, the artist, the genre, the album cover URL, and the year.
Next add the following method:
func description() -> String { return "title: \(title)" + "artist: \(artist)" + "genre: \(genre)" + "coverUrl: \(coverUrl)" + "year: \(year)" } |
The method description()
returns a string representation of the album’s attributes.
Again, navigate to File\New\File…. Select Cocoa Touch Class and then click Next. Set the class name to AlbumView, but this time set the subclass to UIView. Make sure the language is set to Swift and then click Next and then Create.
Open AlbumView.swift and add the following properties inside the class definition:
private let coverImage: UIImageView! private let indicator: UIActivityIndicatorView! |
The coverImage
represents the album cover image. The second property is an indicator that spins to indicate activity while the cover is being downloaded.
The properties are marked as private because no class outside AlbumView
needs to know of the existence of these properties; they are used only in the implementation of the class’s internal functionality. This convention is extremely important if you’re creating a library or framework for other developers to use to keep private state information private.
Next, add the initializers to the class:
required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } init(frame: CGRect, albumCover: String) { super.init(frame: frame) backgroundColor = UIColor.blackColor() coverImage = UIImageView(frame: CGRectMake(5, 5, frame.size.width - 10, frame.size.height - 10)) addSubview(coverImage) indicator = UIActivityIndicatorView() indicator.center = center indicator.activityIndicatorViewStyle = .WhiteLarge indicator.startAnimating() addSubview(indicator) } |
The NSCoder
initializer is required because UIView
conforms to NSCoding
. You have no reason to encode or decode instances of your AlbumView
in this app, so you can just leave this be, calling it’s super init.
In “real” initializer that you’ll use in the rest of the app, you set some nice defaults for the album view. You set the background to black, create the image view with a small margin of 5 pixels and create and add the activity indicator.
Finally, add the following method:
func highlightAlbum(#didHighlightView: Bool) { if didHighlightView == true { backgroundColor = UIColor.whiteColor() } else { backgroundColor = UIColor.blackColor() } } |
This will toggle the album’s background color to white if it’s highlighted, and black when it’s not.
Build your project (Command+B) just to make sure everything is in order. All good? Then get ready for your first design pattern! :]
MVC – The King of Design Patterns
Model-View-Controller (MVC) is one of the building blocks of Cocoa and is undoubtedly the most-used design pattern of all. It classifies objects according to their general role in your application and encourages clean separation of code based on role.
The three roles are:
- Model: The object that holds your application data and defines how to manipulate it. For example, in your application the Model is your
Album
class. - View: The objects that are in charge of the visual representation of the Model and the controls the user can interact with; basically, all the
UIView
-derived objects. In your application the View is represented by yourAlbumView
class. - Controller: The controller is the mediator that coordinates all the work. It accesses the data from the model and displays it with the views, listens to events and manipulates the data as necessary. Can you guess which class is your controller? That’s right:
ViewController
.
A good implementation of this design pattern in your application means that each object falls into one of these groups.
The communication between View to Model through Controller can be best described with the following diagram:
The Model notifies the Controller of any data changes, and in turn, the Controller updates the data in the Views. The View can then notify the Controller of actions the user performed and the Controller will either update the Model if necessary or retrieve any requested data.
You might be wondering why you can’t just ditch the Controller, and implement the View and Model in the same class, as that seems a lot easier.
It all comes down to code separation and reusability. Ideally, the View should be completely separated from the Model. If the View doesn’t rely on a specific implementation of the Model, then it can be reused with a different model to present some other data.
For example, if in the future you’d also like to add movies or books to your library, you could still use the same AlbumView
to display your movie and book objects. Furthermore, if you want to create a new project that has something to do with albums, you could simply reuse your Album
class, because it’s not dependent on any view. That’s the strength of MVC!
How to Use the MVC Pattern
First, you need to ensure that each class in your project is either a Controller, a Model or a View; don’t combine the functionality of two roles in one class. You’ve already done a good job so far by creating an Album
class and an AlbumView
class.
Second, in order to ensure that you conform to this method of work you should create three project groups to hold your code, one for each category.
Navigate to File\New\Group (or press on Command+Option+N) and name the group Model. Repeat the same process to create View and Controller groups.
Now drag Album.swift to the Model group. Drag AlbumView.swift to the View group, and finally drag ViewController.swift to the Controller group.
At this point the project structure should look like this:
Your project already looks a lot better without all those files floating around. Obviously you can have other groups and classes, but the core of the application is contained in these three categories.
Now that your components are organized, you need to get the album data from somewhere. You’ll create an API class to use throughout your code to manage the data — which presents an opportunity to discuss your next design pattern — the Singleton.
The Singleton Pattern
The Singleton design pattern ensures that only one instance exists for a given class and that there’s a global access point to that instance. It usually uses lazy loading to create the single instance when it’s needed the first time.
Note: Apple uses this approach a lot. For example: NSUserDefaults.standardUserDefaults()
, UIApplication.sharedApplication()
, UIScreen.mainScreen()
, NSFileManager.defaultManager()
all return a Singleton object.
You’re likely wondering why you care if there’s more than one instance of a class floating around. Code and memory is cheap, right?
There are some cases in which it makes sense to have exactly one instance of a class. For example, there’s only one instance of your application and one main screen for the device, so you only want one instance of each. Or, take a global configuration handler class: it’s easier to implement a thread-safe access to a single shared resource, such as a configuration file, than to have many class modifying the configuration file possibly at the same time.
How to Use the Singleton Pattern
Take a look at the diagram below:
The above image shows a Logger class with a single property (which is the single instance), and two methods: sharedInstance
and init
.
The first time a client asks for the sharedInstance
, the instance
property isn’t yet initialized, so you create a new instance of the class and return a reference to it.
The next time you call sharedInstance
, instance
is immediately returned without any initialization. This logic ensures that only one instance exists at one time.
You’ll implement this pattern by creating a singleton class to manage all the album data.
You’ll notice there’s a group called API in the project; this is where you’ll put all the classes that will provide services to your app. Create a new file inside this group by right-clicking the group and selecting New File. Select iOS > Cocoa Touch Class and then click Next. Set the class name to LibraryAPI, and the subclass to NSObject
. Lastly choose Swift as the language. Click Next and then Create.
Now go to LibraryAPI.swift and insert this code inside the class definition.
//1 class var sharedInstance: LibraryAPI { //2 struct Singleton { //3 static let instance = LibraryAPI() } //4 return Singleton.instance } |
There’s a lot going on in this short method:
- Create a class variable as a computed type property. The class variable, like class methods in Objective-C, is something you can call without having to instantiate the class
LibraryAPI
For more information about type properties please refer to Apple’s Swift documentation on
properties - Nested within the class variable is a struct called
Singleton
. Singleton
wraps a static constant variable named instance. Declaring a property asstatic
means this property only exists once. Also note that static properties in Swift are implicitly lazy, which means thatInstance
is not created until it’s needed. Also note that since this is a constant property, once this instance is created, it’s not going to create it a second time. This is the essence of the Singleton design pattern. The initializer is never called again once it has been instantiated.- Returns the computed type property.
Note: To learn more about different ways to create a singleton in Swift refer to this: Github page
You now have a Singleton object as the entry point to manage the albums. Take it a step further and create a class to handle the persistence of your library data.
Now within the group API create a new file. Select iOS > Cocoa Touch class and then click Next. Set the class name to PersistencyManager
, and make it a subclass of NSObject
.
Open PersistencyManager.swift Add the following code inside the curly braces
private var albums = [Album]() |
Here you declare a private property to hold album data. The array is mutable, so you can easily add and delete albums.
Now add the following initializer to the class:
override init() { //Dummy list of albums let album1 = Album(title: "Best of Bowie", artist: "David Bowie", genre: "Pop", coverUrl: "http://www.coversproject.com/static/thumbs/album/album_david%20bowie_best%20of%20bowie.png", year: "1992") let album2 = Album(title: "It's My Life", artist: "No Doubt", genre: "Pop", coverUrl: "http://www.coversproject.com/static/thumbs/album/album_no%20doubt_its%20my%20life%20%20bathwater.png", year: "2003") let album3 = Album(title: "Nothing Like The Sun", artist: "Sting", genre: "Pop", coverUrl: "http://www.coversproject.com/static/thumbs/album/album_sting_nothing%20like%20the%20sun.png", year: "1999") let album4 = Album(title: "Staring at the Sun", artist: "U2", genre: "Pop", coverUrl: "http://www.coversproject.com/static/thumbs/album/album_u2_staring%20at%20the%20sun.png", year: "2000") let album5 = Album(title: "American Pie", artist: "Madonna", genre: "Pop", coverUrl: "http://www.coversproject.com/static/thumbs/album/album_madonna_american%20pie.png", year: "2000") albums = [album1, album2, album3, album4, album5] } |
In the initializer, you’re populating the array with five sample albums. If the above albums aren’t to your liking, feel free to replace them with the music you enjoy. :]
Now add the following functions to the class:
func getAlbums() -> [Album] { return albums } func addAlbum(album: Album, index: Int) { if (albums.count >= index) { albums.insert(album, atIndex: index) } else { albums.append(album) } } func deleteAlbumAtIndex(index: Int) { albums.removeAtIndex(index) } |
These methods allow you to get, add, and delete albums.
Build your project just to make sure everything still compiles correctly.
At this point, you might wonder where the PersistencyManager
class comes in since it’s not a Singleton. You’ll see the relationship between LibraryAPI
and PersistencyManager
in the next section where you’ll look at the Facade design pattern.
The Facade Design Pattern
The Facade design pattern provides a single interface to a complex subsystem. Instead of exposing the user to a set of classes and their APIs, you only expose one simple unified API.
The following image explains this concept:
The user of the API is completely unaware of the complexity that lies beneath. This pattern is ideal when working with a large number of classes, particularly when they are complicated to use or difficult to understand.
The Facade pattern decouples the code that uses the system from the interface and implementation of the classes you’re hiding; it also reduces dependencies of outside code on the inner workings of your subsystem. This is also useful if the classes under the facade are likely to change, as the facade class can retain the same API while things change behind the scenes.
For example, if the day comes when you want to replace your backend service, you won’t have to change the code that uses your API as it wont’ change.
How to Use the Facade Pattern
Currently you have PersistencyManager
to save the album data locally and HTTPClient
to handle the remote communication. The other classes in your project should not be aware of this logic, as they will be hiding behind the facade of LibraryAPI
.
To implement this pattern, only LibraryAPI
should hold instances of PersistencyManager
and HTTPClient
. Then, LibraryAPI
will expose a simple API to access those services.
The design looks like the following:
LibraryAPI
will be exposed to other code, but will hide the HTTPClient
and PersistencyManager
complexity from the rest of the app.
Open LibraryAPI.swift and add the following constant properties to the class:
private let persistencyManager: PersistencyManager private let httpClient: HTTPClient private let isOnline: Bool |
isOnline
determines if the server should be updated with any changes made to the albums list, such as added or deleted albums.
You now need to initialize these variables via init
. Add the initializer to the class next:
override init() { persistencyManager = PersistencyManager() httpClient = HTTPClient() isOnline = false super.init() } |
The HTTP client doesn’t actually work with a real server and is only here to demonstrate the usage of the facade pattern, so isOnline
will always be false
.
Next, add the following three methods to LibraryAPI.swift:
func getAlbums() -> [Album] { return persistencyManager.getAlbums() } func addAlbum(album: Album, index: Int) { persistencyManager.addAlbum(album, index: index) if isOnline { httpClient.postRequest("/api/addAlbum", body: album.description()) } } func deleteAlbum(index: Int) { persistencyManager.deleteAlbumAtIndex(index) if isOnline { httpClient.postRequest("/api/deleteAlbum", body: "\(index)") } } |
Take a look at addAlbum(_:index:)
. The class first updates the data locally, and then if there’s an internet connection, it updates the remote server. This is the real strength of the Facade; when some class outside of your system adds a new album, it doesn’t know — and doesn’t need to know — of the complexity that lies underneath.
Build and run your app. You’ll see two empty views, and a toolbar. The top view will be used to display your album covers, and the bottom view will be used to display a table of information related to that album.
You’ll need something to display the album data on screen — which is a perfect use for your next design pattern: the Decorator.
The Decorator Design Pattern
The Decorator pattern dynamically adds behaviors and responsibilities to an object without modifying its code. It’s an alternative to subclassing where you modify a class’s behavior by wrapping it with another object.
In Swift there are two very common implementations of this pattern: Extensions and Delegation.
Extensions
Adding extensions is an extremely powerful mechanism that allows you to add new functionality to existing classes, structures or enumeration types without having to subclass. What’s also really awesome is you can extend code you don’t have access to, and enhance their functionality. That means you can add your own methods to Cocoa classes such as UIView and UIImage!
For example, new methods that are added at compile time can be executed like normal methods of the extended class. It’s slightly different from the classic definition of a decorator, because a extension doesn’t hold an instance of the class it extends.
How to Use Extensions
Imagine a situation in which you have an Album
object that you want to present inside a table view:
Where will the album titles come from? Album
is a Model object, so it doesn’t care how you present the data. You’ll need some external code to add this functionality to the Album
class, but without modifying the class directly.
You’ll create a extension that will extend the Album
class; it will define a new method that returns a data structure which can be used easily with UITableView
.
The data structure will look like the following:
To add a Extensions to Album
, navigate to File\New\File… and select the iOS > Source > Swift File template, and then click Next. Enter AlbumExtensions
and click Create.
Go to AlbumExtensions.swift and add the following extension:
extension Album { func ae_tableRepresentation() -> (titles:[String], values:[String]) { return (["Artist", "Album", "Genre", "Year"], [artist, title, genre, year]) } } |
Notice there’s a ae_
at the beginning of the method name, as an abbreviation of the name of the extension: AlbumExtension
. Conventions like this will help prevent collisions with methods (including possible private methods you might not know about) in the original implementation.
Note: Classes can of course override a superclass’s method, but with extensions you can’t. Methods or properties in an extension cannot have the same name as methods or properties in the original class.
Consider for a moment how powerful this pattern can be:
- You’re using properties directly from
Album
. - You have added to the
Album
class but you haven’t subclassed it. If you need to sub-classAlbum
, you can still do that too. - This simple addition lets you return a
UITableView
-ish representation of anAlbum
, without modifyingAlbum
‘s code.
Delegation
The other Decorator design pattern, Delegation, is a mechanism in which one object acts on behalf of, or in coordination with, another object. For example, when you use a UITableView
, one of the methods you must implement is tableView(_:numberOfRowsInSection:)
.
You can’t expect the UITableView
to know how many rows you want to have in each section, as this is application-specific. Therefore, the task of calculating the amount of rows in each section is passed on to the UITableView
delegate. This allows the UITableView
class to be independent of the data it displays.
Here’s a pseudo-explanation of what’s going on when you create a new UITableView
:
The UITableView
object does its job of displaying a table view. However, eventually it will need some information that it doesn’t have. Then, it turns to its delegates and sends a message asking for additional information. In Objective-C’s implementation of the delegate pattern, a class can declare optional and required methods through a protocol. You’ll cover protocols a bit later in this tutorial.
It might seem easier to just subclass an object and override the necessary methods, but consider that you can only subclass based on a single class. If you want an object to be the delegate of two or more other objects, you won’t be able to achieve this by subclassing.
Note: This is an important pattern. Apple uses this approach in most of the UIKit classes: UITableView
, UITextView
, UITextField
, UIWebView
, UIAlert
, UIActionSheet
, UICollectionView
, UIPickerView
, UIGestureRecognizer
, UIScrollView
. The list goes on and on.
How to Use the Delegate Pattern
Open up ViewController.swift and add these private properties to the class:
private var allAlbums = [Album]() private var currentAlbumData : (titles:[String], values:[String])? private var currentAlbumIndex = 0 |
Next, replace viewDidLoad
with this code:
override func viewDidLoad() { super.viewDidLoad() //1 self.navigationController?.navigationBar.translucent = false currentAlbumIndex = 0 //2 allAlbums = LibraryAPI.sharedInstance.getAlbums() // 3 // the uitableview that presents the album data dataTable.delegate = self dataTable.dataSource = self dataTable.backgroundView = nil view.addSubview(dataTable!) } |
Here’s a breakdown of the above code:
- Turn off translucency on the navigation bar.
- Get a list of all the albums via the API. Remember, the plan is to use the facade of
LibraryAPI
rather thanPersistencyManager
directly! - This is where you setup the
UITableView
. You declare that the view controller is theUITableView
delegate/data source; therefore, all the information required byUITableView
will be provided by the view controller. Note that you can actually set the delegate and datasource in a storyboard, if your table view is created there.
Now, add the following method to ViewController.swift:
func showDataForAlbum(albumIndex: Int) { // defensive code: make sure the requested index is lower than the amount of albums if (albumIndex < allAlbums.count && albumIndex > -1) { //fetch the album let album = allAlbums[albumIndex] // save the albums data to present it later in the tableview currentAlbumData = album.ae_tableRepresentation() } else { currentAlbumData = nil } // we have the data we need, let's refresh our tableview dataTable!.reloadData() } |
showDataForAlbum()
fetches the required album data from the array of albums. When you want to present the new data, you just need to call reloadData
. This causes UITableView
to ask its delegate such things as how many sections should appear in the table view, how many rows in each section, and how each cell should look.
Add the following line to the end of viewDidLoad
self.showDataForAlbum(currentAlbumIndex) |
This loads the current album at app launch. And since currentAlbumIndex
was previously set to 0, this shows the first album in the collection.
Now it’s time to implement the data source protocol! You can add the list of protocols implemented by the class right on the class declaration line. Or, to keep things tidy you can add them as extensions, which you’re already familiar with.
Add the following extensions to the bottom of the file. Make sure you add these lines after the closing brace of the class definition!
extension ViewController: UITableViewDataSource { } extension ViewController: UITableViewDelegate { } |
This is how you make your delegate conform to a protocol — think of it as a promise made by the delegate to fulfill the method’s contract. Here, you indicate that ViewController
will conform to the UITableViewDataSource
and UITableViewDelegate
protocols. This way UITableView
can be absolutely certain that the required methods are implemented by its delegate.
Add the following code to the UITableViewDataSource
extension:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if let albumData = currentAlbumData { return albumData.titles.count } else { return 0 } } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { var cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell if let albumData = currentAlbumData { cell.textLabel?.text = albumData.titles[indexPath.row] if let detailTextLabel = cell.detailTextLabel { detailTextLabel.text = albumData.values[indexPath.row] } } return cell } |
tableView(_:numberOfRowsInSection:)
returns the number of rows to display in the table view, which matches the number of titles in the data structure.
tableView(_:cellForRowAtIndexPath:)
creates and returns a cell with the title and its value.
UITableViewDataSource
extension. For humans reading the code though, this kind of organization really helps with readability.
Build and run your project. Your app should start and present you with the following screen:
Table view data source success!
Where to go from here?
Things are looking pretty good so far! You have the MVC pattern in place, and you’ve also seen the singleton, facade, and decorator patterns in action. You can see how these are used within Cocoa by Apple, and also how to apply the patterns to your own code.
Here are the project files up to this point if you want to have a look or compare.
There’s a lot more in store: there are still the adapter, observer, and memento patterns to cover in part two of this tutorial. And if that’s not enough, we have a follow-up tutorial coming up covering even more design patterns as you work on refactoring a simple iOS game.
If you have questions or just want to talk about your favorite design patterns, join in on the forum discussion below!
Introducing iOS Design Patterns in Swift – Part 1/2 is a post from: Ray Wenderlich
The post Introducing iOS Design Patterns in Swift – Part 1/2 appeared first on Ray Wenderlich.