• Design Patterns on iOS using Swift — Part 1/2
  • Lorenzo Boaro
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: iWeslie
  • Proofreader: Swants, Chunk49

IOS Design Patterns using Swift (Part 1)

In this two-part tutorial, you’ll learn about common design patterns for building iOS applications and how to apply them in your own applications.

What’s New: This tutorial has been updated by the translator for iOS 12, Xcode 10, and Swift 4.2. The original post was posted by tutorial team member Eli Ganem.

IOS Design Patterns – You’ve probably heard the term, but do you know what it means? Although most developers probably agree that design patterns are very important, there aren’t many articles written on the topic, and we developers sometimes don’t pay much attention to them when we write code.

Design patterns are reusable solutions to common problems in software design. Their templates are designed to help you write code that is easy to understand and reuse. They can also help you create low-coupling code so that you can change or replace components in your code without much hassle.

If you’re not familiar with design patterns, I have good news for you! First, because of the way Cocoa is architected and the best practices it encourages you to use, you’ve already used a lot of iOS design patterns. Second, this tutorial will quickly help you understand all the important (and not so important) iOS design patterns commonly used in Cocoa.

In this two-part tutorial, you will create a music application that displays your album and its related information.

As you develop this application, you will become familiar with the most common Cocoa design patterns:

  • Create type: singleton.
  • Structural: MVC, decoration, adapter, and appearance.
  • Behaviorist: Observer and memo.

Don’t mistake this for a theoretical article, you’ll be using most of these design patterns in music applications. At the end of this tutorial, your application will look like this:

Let’s get started!

An introduction to

Download the getting started project, unzip the contents of the ZIP file, and open RWBlueLibrary. Xcodeproj in Xcode.

Please note the following in the project:

  1. In the storyboard,ViewControllerThere are threeIBOutletIt’s connected to the TableView, and it has undo and delete buttons.
  2. The Storyboard has three components, and we set constraints for convenience. The top component is used to display album covers. Below the album cover is a TableView that lists information related to the album cover. Finally, the toolbar has two buttons, one to undo the action and one to delete the album you selected. The Storyboard looks like this:

  1. There is an initial HTTP client class that is not implemented (HTTPClient) for you to fill in later.

Note: Did you know that as soon as you create a new Xcode project, you’re already full of design patterns? Model-view-controller, proxy, protocol, singleton – these design patterns are readily available!

MVC – The king of design patterns

Model-view-controller (MVC), one of Cocoa’s building blocks, is easily the most commonly used of all design patterns. It classifies in-application objects according to their common roles and advocates decoupling code based on roles.

The three roles are:

  • ModelA Model is an object in your application that holds and defines how data is manipulated. For example, in your application, the model isAlbumThe structure, you can be inAlbum.swiftFind it in. Most applications will have multiple types as part of their model.
  • ViewThe: View is the object that displays the model’s data and manages controls that can interact with the user, basically all of themUIViewDerived object. In your application, the view isAlbumViewYou can use theAlbumView.swiftFind it in.
  • ControllerThe controller is the intermediary that coordinates all the work. It accesses the data in the model and displays it with the view, listens for events and manipulates the data as needed. Can you guess which class is your controller? Yes, it isViewController.

For your App to formally use the MVC design pattern, it means that every object in your App can be divided into one of these three roles.

The communication between the View and the Model is best described by the Controller, as shown in the following figure:

The model notifies the controller of any data changes, which in turn updates the data in the view. The view can then notify the controller of the actions performed by the user, and the controller will update the model or retrieve any requested data if necessary.

You might wonder why you can’t just throw away the controller and implement the view and model in the same class, because that would seem so much easier.

It all boils down to code separation and reusability. Ideally, the view should be completely separate from the model. If the view is not dependent on a particular implementation of the model, it can be reused using a different model to present some additional data.

For example, if you want to add movies or books to the library in the future, you can still use the same AlbumView to display movie and book objects. In addition, if you want to create a new project related to an Album, you can simply reuse your Album structure because it doesn’t depend on any view. That’s the power of MVC!

How to use the MVC design pattern

First, you need to make sure that every class in your project is a Controller, Model, or View, and don’t combine the functions of two roles in one class.

Second, to make sure you’re working this way, you should create three folders to hold your code, one for each character.

Click **File\New\Group (or Command + Option + N) ** and rename the Group to Model. Repeat the same process to create the View and Controller groups.

Now drag and drop Album. Swift into the Model group. Drag albumView.swift to the View group and viewController.swift to the Controller group.

The project structure should look like this:

Your project would look a lot better without all those folders. Obviously, you can have other groups and classes, but the heart of the application will be contained within these three categories.

Now that your components are organized, you need to get the album data from somewhere. You’ll create an API class that you’ll use throughout your code to manage data, which provides an opportunity to discuss the next design pattern — singletons.

The singleton pattern

The singleton design pattern ensures that only one instance of a given class will exist, and that instance will have a global access point. It typically uses lazy loading to create a single instance the first time it is needed.

Note: Apple uses this method a lot. For example: Userdefaults. standard, UIApplication.shared, uiscreen. main, and Filemanager.default all return a singleton object.

You may wonder why you care about having more than one instance of a class. Isn’t code and memory cheap?

In some cases, only a single instance of a class makes sense. For example, your application has only one instance and the device has only one home screen, so you only need one instance. Furthermore, with the global configuration handler class, it is easier to achieve thread-safe access to a single shared resource, such as a configuration file, rather than having many classes that might modify the configuration file at the same time.

What should you look out for?

Notes: This pattern has a history of being abused (or misused) by beginners and experienced developers, so we took a brief excerpt from Joshua Greene’s Design Patterns by Tutorials, which explains the DOS and don ‘ts of using it.

The singleton pattern can easily be abused.

If you come across a situation where you want to use singletons, first consider whether there are other ways to accomplish your task.

For example, if you are just trying to pass information from one view controller to another, singletons are not appropriate. But you can consider passing the model through initializers or properties.

If you are sure that you really need a singleton, then it makes more sense to consider extending the singleton.

Does having multiple instances cause problems? Will custom instances be useful? Your answer will determine whether you are better off using the real singleton or its extension.

The most common reason for problems with singletons is testing. If you store state in global objects like singletons, the test order can be important, and emulating them can be annoying. Both of these reasons can make testing a pain.

Finally, watch out for “code smell,” which indicates that your use case is not suitable for singletons at all. For example, if you often need many custom instances, then your use case may be better served as a regular object.

How to use the singleton pattern

To ensure that your singleton has only one instance, you must make it impossible for anyone else to create an instance. Swift allows you to do this by marking the initialization method private, and then you can add static properties to the shared instance that are initialized in the class.

You will implement this pattern by creating a singleton to manage all album data.

You will notice that there is a group called API in the project, which is where you will put all the classes that will serve your application. Right-click the group and select New File to create a New File in the group and select iOS > Swift File. Set the file name to Libraryapi.swift, and click Create.

Now open Libraryapi.swift and insert code:

final class LibraryAPI {
  / / 1
  static let shared = LibraryAPI(a)/ / 2
  private init() {}}Copy the code

Here’s the breakdown:

  1. Among themsharedDeclared constants make the singleton accessible to other objectsLibraryAPI.
  2. Private initialization methods prevent creation from outsideLibraryAPIA new instance of.

You now have a singleton object as a portal to manage albums. Next, create a class to persist the database data.

Now create a new file in the API group. Choose iOS > Swift File. Set the class name to PersistencyManager.swift, and click Create.

Open persistencyManager.swift and add the following code:

final class PersistencyManager {}Copy the code

Add the following code to the parentheses:

private var albums = [Album] ()Copy the code

Here, you declare a private property to hold the album data. The array will be mutable, so you can easily add and remove albums.

Now add the following initialization methods to the class:

init() {
  //Dummy list of albums
  let album1 = Album(title: "Best of Bowie",
                     artist: "David Bowie",
                     genre: "Pop",
                     coverUrl: "https://s3.amazonaws.com/CoverProject/album/album_david_bowie_best_of_bowie.png",
                     year: "1992")
    
  let album2 = Album(title: "It's My Life",
                     artist: "No Doubt",
                     genre: "Pop",
                     coverUrl: "https://s3.amazonaws.com/CoverProject/album/album_no_doubt_its_my_life_bathwater.png",
                     year: "2003")
    
  let album3 = Album(title: "Nothing Like The Sun",
                     artist: "Sting",
                     genre: "Pop",
                     coverUrl: "https://s3.amazonaws.com/CoverProject/album/album_sting_nothing_like_the_sun.png",
                     year: "1999")
    
  let album4 = Album(title: "Staring at the Sun",
                     artist: "U2",
                     genre: "Pop",
                     coverUrl: "https://s3.amazonaws.com/CoverProject/album/album_u2_staring_at_the_sun.png",
                     year: "2000")
    
  let album5 = Album(title: "American Pie",
                     artist: "Madonna",
                     genre: "Pop",
                     coverUrl: "https://s3.amazonaws.com/CoverProject/album/album_madonna_american_pie.png",
                     year: "2000")
    
  albums = [album1, album2, album3, album4, album5]
}
Copy the code

In the initializer, you will populate the array with five sample albums. If the above albums are not to your liking, feel free to replace them with your favorite music.

Now add the following functions to the class:

func getAlbums(a)- > [Album] {
  return albums
}
  
func addAlbum(_ album: Album, at index: Int) {
  if albums.count >= index {
    albums.insert(album, at: index)
  } else {
    albums.append(album)
  }
}
  
func deleteAlbum(at index: Int) {
  albums.remove(at: index)
}
Copy the code

These methods allow you to get, add and delete albums.

Compile your project and make sure everything is compiled correctly.

At this point, you might want to know the location of the PersistencyManager class because it is 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 appearance model

The facade design pattern provides a single interface for complex subsystems. Instead of exposing users to a set of classes and their apis, you just expose a simple unified API.

The following diagram illustrates this concept:

Users of the API are completely unaware of its complexity. This pattern is ideal when a lot of complex or difficult to understand classes are used.

The facade decouples the code that uses the system interface from the implementation of your hidden classes. It also reduces the dependence of external code on the internal workings of the subsystem. This is still useful if the classes under the facade are likely to change, because the facade class can retain the same API when changes are made behind the scenes.

For example, if you want to replace a back-end service, you don’t have to change the code that uses the API, just the code in the facade class.

How do I use appearance patterns

Currently, you have PersistencyManager that stores album data locally and handles remote communication using HTTPClient. Other classes in the project should not involve this logic because they will be hidden behind the appearance of the LibraryAPI.

To implement this pattern, only LibraryAPI should contain instances of PersistencyManager and HTTPClient. Second, LibraryAPI exposes a simple API to access these services.

The design is as follows:

The LibraryAPI is exposed to other code, but hides the HTTPClient and PersistencyManager complexities of the rest of the application.

Open Libraryapi.swift and add the following constant properties to the class:

private let persistencyManager = PersistencyManager(a)private let httpClient = HTTPClient(a)private let isOnline = false
Copy the code

IsOnline decides whether the server should be updated with any changes made to the album list, such as adding or removing albums. The HTTP client is not actually working with the real server and is only used to demonstrate the use of appearance mode, so isOnline will always be false.

Next, add the following three methods to Libraryapi.swift:

func getAlbums(a)- > [Album] {
  return persistencyManager.getAlbums()    
}
  
func addAlbum(_ album: Album, at index: Int) {
  persistencyManager.addAlbum(album, at: index)
  if isOnline {
    httpClient.postRequest("/api/addAlbum", body: album.description)
  }  
}
  
func deleteAlbum(at index: Int) {
  persistencyManager.deleteAlbum(at: index)
  if isOnline {
    httpClient.postRequest("/api/deleteAlbum", body: "\(index)")}}Copy the code

Let’s take a look at addAlbum(_:at:). This class first updates the data locally, and then updates the remote server if there is a network connection. This is the core advantage of the appearance pattern, because when you write a class outside of an Album to add a new Album, it doesn’t know, nor does it need to know, the complexity behind the class.

Note: When designing the look and feel for classes in a subsystem, remember that unless you are building separate modules and using access control, you will not prevent clients from accessing these “hidden” classes directly. Don’t skimp on access control code, and don’t assume that all clients must use the same classes that the facade uses their methods.

Compile and run your application. You’ll see two empty views and a toolbar. The top View will be used to display your album cover, and the bottom View will be used to display a list of information related to that album.

You need something to display the album’s data on the screen, which is perfect practice for your next design pattern: decorators.

Decorative pattern

The decorator pattern dynamically adds behavior and responsibilities to objects without modifying the code in them. It is an alternative to subclassing, modifying the behavior of a class by wrapping it with another object.

In Swift, there are two very common implementations of this pattern: extension and proxy.

expand

Adding extensions is a very powerful mechanism that allows you to add new functionality to existing classes, structs, or enumerated types without subclassing. It’s great that you can extend code that you don’t have access to and enhance it. This means that you can add your own methods to Cocoa classes, such as UIView and UIImage.

The Swift extension differs slightly from the classic definition of the decorator pattern in that the extension does not contain an instance of the class it extends.

How to Use extensions

Imagine if you wanted to display an Album instance in a TableView:

Where does the album title come from? An Album is a model, so it doesn’t care how you present the data. You’ll need some external code to add this functionality to the Album structure.

You will create an extension to the Album structure, which will define a new method for returning data structures that can be easily used in UITableView.

Open album.swift and add the following code at the end of the file:

typealias AlbumData = (title: String, value: String)
Copy the code

This type defines a tuple that contains all the information needed for a table view to display a row of data. Now add the following extension to access this information:

extension Album {
  var tableRepresentation: [AlbumData] {
    return[("Artist", artist),
      ("Album", title),
      ("Genre", genre),
      ("Year", year)
    ]
  }
}
Copy the code

The AlbumData array will be easier to display in TableView.

Note: Classes can override methods of their parent classes, but not for extensions. A method or property in an extension cannot have the same name as a method or property in the original class.

Consider how powerful this pattern is:

  • You can go directly toAlbumUse attributes in.
  • You have added toAlbumStructure and you don’t have to modify it.
  • This simple operation will allow you to return a similar oneUITableViewAlbum.

The agent

Another implementation of the design pattern is proxy, which is a mechanism for one object to work on behalf of or in collaboration with another object. UITableView is greedy, it has two proxy-type properties, one called data source and one called proxy. They do slightly different things, for example, the TableView will ask its data source how many rows it should have in a particular section, but it will ask its agent what to do when the row is clicked.

You can’t expect UITableView to know how many rows you want in each section, because that’s application-specific. Therefore, the task of counting the number of rows in each section is passed to the data source. This allows the UITableView class to be independent of the data it displays.

Here’s a pseudo-explanation of what happens when you create a new UITableView:

Table: Here I am! All I want to do is display the cell. Hey, how many sections do I have? Data Source: One! Table: Ok, ok, easy! How many cells are in the first section? Data source: Four! Table: Thank you! Now, be patient, this can get a little repetitive. Can I get the cell in section 0, line 0? Data source: Yes, go ahead! Table: Now section 0th, row 1?

To be continued…

The UITableView object does the job of displaying the table view. But ultimately it needs some information it doesn’t have. It then turns to its broker and data source and sends a message asking for additional information.

It may seem easier to subclass an object and rewrite the necessary methods, but consider that you can only subclass based on a single class. If you want an object to be a proxy for two or more other objects, you cannot subclass it.

Note: This is an important pattern. Apple uses this method in most UIKit classes: UITableView, UITextView, UITextField, UIWebView, UICollectionView, UIPickerView, UIGestureRecognizer, UIScrollView. This list will continue to be updated.

How to use proxy mode

Open viewController.swift and add these private properties to the class:

private var currentAlbumIndex = 0
private var currentAlbumData: [AlbumData]?
private var allAlbums = [Album] ()Copy the code

Starting with Swift 4, variables marked private can share the same scope of access control between a type and any extension of the type described. If you want to see What’s New in Swift 4, check out What’s New in Swift 4.

You’re going to make the ViewController the data source for the TableView. Add this extension to the end of viewController.swift after the closing brace of the class definition:

extension ViewController: UITableViewDataSource {}Copy the code

The compiler will issue a warning because UITableViewDataSource has some required functions. Add the following code to the extension to make the warning disappear:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  guard let albumData = currentAlbumData else {
    return 0
  }
  return albumData.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCell(withIdentifier: "Cell".for: indexPath)
  if let albumData = currentAlbumData {
    letrow = indexPath.row cell.textLabel? .text = albumData[row].title cell.detailTextLabel?.text = albumData[row].value }return cell
}
Copy the code

TableView (_:numberOfRowsInSection:) Returns the number of rows to be displayed in the tableView, matching the number of projects in the album’s “Decorate” representation.

TableView (_:cellForRowAtIndexPath:) creates and returns a cell with a title and value.

Note: you can actually add methods to the main class declaration or extension, and the compiler doesn’t care that the data source methods actually exist in the UITableViewDataSource extension. For those who read the code, this organization really helps with readability.

Next, replace viewDidLoad() with the following:

override func viewDidLoad(a) {
  super.viewDidLoad()
 
  / / 1
  allAlbums = LibraryAPI.shared.getAlbums()

  / / 2
  tableView.dataSource = self		
}
Copy the code

Here is a breakdown of the above code:

  1. Get a list of all albums through the API. Remember, our plan is to use it directlyLibraryAPIInstead of using it directlyPersistencyManager!
  2. This is your setupUITableViewPlace. You declare the ViewController to beUITableViewData source, therefore,UITableViewAll the information needed will be provided by the ViewController. Notice that if you create a TableView in your storyboard, you can actually set up the proxy and data source there.

Now add the following methods to the ViewController:

private func showDataForAlbum(at index: Int) {
    
  // defensive code: make sure the requested index is lower than the amount of albums
  if index < allAlbums.count && index > -1 {
    // fetch the album
    let album = allAlbums[index]
    // save the albums data to present it later in the tableview
    currentAlbumData = album.tableRepresentation
  } else {
    currentAlbumData = nil
  }
  // we have the data we need, let's refresh our tableview
  tableView.reloadData()
}
Copy the code

ShowDataForAlbum (at:) retrieves the required album data from the album array. When you want to refresh data, you just call reloadData in the UITableView. This causes the TableView to call its data source methods again, such as reloading the number of sections to display in the TableView, the number of rows in each section, the appearance of each cell, and so on.

Add the following line to the end of viewDidLoad() :

showDataForAlbum(at: currentAlbumIndex)
Copy the code

This loads the current album when the app starts. Because the currentAlbumIndex is set to 0, the first album in the collection is displayed.

Compile and run your project, and your app should appear on the screen as follows:

TableView data source setup complete!

Write in the last

To not contaminate the code with hard-coded values (such as the string Cell), look at the ViewController and add the following after the opening brace of the class definition:

private enum Constants {
  static let CellIdentifier = "Cell"
}
Copy the code

Here, you will create an enumeration to act as a constant container.

Note: The advantage of using an enumeration without a case is that it is not instantiated by accident and serves only as a pure namespace.

Now just replace “Cell” with Constants.CellIdentifier.

What’s next?

Things seem to be going well so far! You’ve got the MVC pattern, you’ve got the singleton, the facade, and the decorator pattern. You can see how Apple uses them in Cocoa and how to apply patterns to your own code.

If you want to see or compare, see the final project.

There are many more in stock: The second part of this tutorial also includes adapters, observer and memo patterns. If that’s not enough, we’ll have a follow-up tutorial that covers more design patterns as you refactor a simple iOS game.

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.