Chapters

Hide chapters

Design Patterns by Tutorials

Third Edition · iOS 13 · Swift 5 · Xcode 11

6. Singleton Pattern
Written 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.

When should you use it?

Use the singleton pattern when having more than one instance of a class would cause problems, or when it just wouldn’t be logical.

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 FileManager, which handles everything to do with filesystem access. There is a “default” instance which is a singleton, or you can create your own. You would usually create your own if you’re using it on a background thread.

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.

Click on the Singleton link to open that page.

Both singleton and singleton plus are common throughout Apple frameworks. For example, UIApplication is a true singleton.

Add the following right after Code example:

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:

public class MySingleton {
  // 1
  static let shared = MySingleton()
  // 2
  private init() { }
}
// 3
let mySingleton = MySingleton.shared
// 4
// let mySingleton2 = MySingleton()

Here’s what you did:

  1. You first declare a public static property called shared, which is the singleton instance.
  2. You mark init as private to prevent the creation of additional instances.
  3. You get the singleton instance by calling MySingleton.shared.
  4. You’ll get a compiler error if you try to create additional instances of MySingleton.

Next, add the following singleton plus example below your MySingleton example:

// MARK: - Singleton Plus
let defaultFileManager = FileManager.default
let customFileManager = FileManager()

FileManager provides a default instance, which is its singleton property.

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:

public class MySingletonPlus {
  // 1
  static let shared = MySingletonPlus()
  // 2
  public init() { }
}
// 3
let singletonPlus = MySingletonPlus.shared

// 4
let singletonPlus2 = MySingletonPlus()

This is very similar to a true singleton:

  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.
  3. You get the singleton instance by calling MySingletonPlus.shared.
  4. You can also create new instances, too.

What should you be careful about?

The singleton pattern is very easy to overuse.

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.

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.

Creating the AppSettings singleton

The first thing you need to do is to have somewhere to store app settings. You’re going to create a singleton for this!

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.

Replace the contents of AppSettings.swift with the following:

import Foundation

public class AppSettings {
  // MARK: - Static Properties
  public static let shared = AppSettings()
  
  // MARK: - Object Lifecycle
  private init() { }
}

Here, you create a new class called AppSettings, which is a singleton.

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
  
  // MARK: - Instance Methods    
  public func title() -> String {
    switch self {
    case .random:
      return "Random"
    case .sequential:
      return "Sequential"
    }
  }
  
  public func questionStrategy(
    for questionGroup: QuestionGroup) -> QuestionStrategy {
    switch self {
    case .random:
      return RandomQuestionStrategy(
        questionGroup: questionGroup)
    case .sequential:
      return SequentialQuestionStrategy(
        questionGroup: questionGroup)
    }
  }
}

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.

You’ll use questionStrategy(for:) to create a QuestionStrategy from the selected QuestionStrategyType.

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"
}

You’ll use strings as the keys to store settings in UserDefaults. Instead of hardcoding the string "questionStrategy" everywhere, you declare a new struct named Keys to give a named and typed way of referencing such strings.

Next, add the following after the shared property:

// MARK: - Instance Properties
public var questionStrategyType: QuestionStrategyType {
  get {
    let rawValue = userDefaults.integer(
      forKey: Keys.questionStrategy)
    return QuestionStrategyType(rawValue: rawValue)!
  } set {
    userDefaults.set(newValue.rawValue,
                     forKey: Keys.questionStrategy)
  }
}
private let userDefaults = UserDefaults.standard

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.

userDefaults is set to UserDefaults.standard, which is another singleton plus provided by Apple! You use this to store key-value pairs that persist across app launches.

Finally, add the following to AppSettings, after init:

// MARK: - Instance Methods
public func questionStrategy(
  for questionGroup: QuestionGroup) -> QuestionStrategy {
  return questionStrategyType.questionStrategy(
    for: questionGroup)
}

This is a convenience method to get the QuestionStrategy from the selected questionStrategyType.

Great job! This completes AppSettings.

Selecting the strategy

You next need to create a new view controller so the user can select their desired question strategy.

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.

Replace the contents of AppSettingsViewController.swift with the following:

import UIKit

// 1
public class AppSettingsViewController: UITableViewController {
  // 2
  // MARK: - Properties
  public let appSettings = AppSettings.shared
  private let cellIdentifier = "basicCell"

  // MARK: - View Life Cycle
  public override func viewDidLoad() {
    super.viewDidLoad()
    
    // 3
    tableView.tableFooterView = UIView()
    
    // 4
    tableView.register(UITableViewCell.self,
                       forCellReuseIdentifier: cellIdentifier)
  }
}

Here’s what you’re doing above:

  1. First, you declare AppSettingsTableViewController as a subclass of UITableViewController.
  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.
  4. You also register UITableViewCell.self for the cellReuseIdentifier of cellIdentifier. This ensures you’ll always get back a UITableViewCell instance whenever you call tableView.dequeueReusableCell(withIdentifier:for:).

Next, add the following code at the end of the file, after the closing curly brace of the class:

// MARK: - UITableViewDataSource
extension AppSettingsViewController {

  public override func tableView(
    _ tableView: UITableView,
    numberOfRowsInSection section: Int) -> Int {
    
      // 1
      return QuestionStrategyType.allCases.count
  }
  
  public override func tableView(
    _ tableView: UITableView,
    cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    let cell = tableView.dequeueReusableCell(
      withIdentifier: cellIdentifier, for: indexPath)

    // 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
  }
}

Here’s what you’re doing:

  1. First, you override tableView(_:numberOfRowsInSection:) to return QuestionStrategyType.allCases.count, which is the number of strategies you have.
  2. Next, you override tableView(_:cellForRowAt:) and again use QuestionStrategyType.allCases to get questionStrategyType for the given indexPath.row.
  3. Set the label to be the name of that strategy.
  4. Finally, if the appSettings.questionStrategyType is equal to the given questionStrategyType, it’s the currently selected strategy, which you denote with a check mark.

Next, add this last extension to the end of the file, after the last closing curly brace:

// MARK: - UITableViewDelegate
extension AppSettingsViewController {
  public override func tableView(
    _ tableView: UITableView,
    didSelectRowAt indexPath: IndexPath) {

    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.

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.

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.

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!

Open SelectQuestionGroupViewController.swift, and add the following property right after // MARK: - Properties:

private let appSettings = AppSettings.shared

Next, scroll down to prepare(for:) and replace:

viewController.questionStrategy =
  SequentialQuestionStrategy(
    questionGroup: selectedQuestionGroup)

…with the following:

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 pattern restricts a class to only one instance.

  • 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.

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