Home iOS & Swift Books Design Patterns by Tutorials

6
Singleton Pattern Written by Joshua Greene

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

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.

import UIKit

// MARK: - Singleton
let app = UIApplication.shared
// let app2 = UIApplication()
public class MySingleton {
  // 1
  static let shared = MySingleton()
  // 2
  private init() { }
}
// 3
let mySingleton = MySingleton.shared
// 4
// let mySingleton2 = MySingleton()
// MARK: - Singleton Plus
let defaultFileManager = FileManager.default
let customFileManager = FileManager()
public class MySingletonPlus {
  // 1
  static let shared = MySingletonPlus()
  // 2
  public init() { }
}
// 3
let singletonPlus = MySingletonPlus.shared

// 4
let singletonPlus2 = MySingletonPlus()

What should you be careful about?

The singleton pattern is very easy to overuse.

Tutorial project

You’ll continue building Rabble Wabble from the previous chapter.

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!

import Foundation

public class AppSettings {
  // MARK: - Static Properties
  public static let shared = AppSettings()
  
  // MARK: - Object Lifecycle
  private init() { }
}
// 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)
    }
  }
}
// MARK: - Keys
private struct Keys {
  static let questionStrategy = "questionStrategy"
}
// 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
// MARK: - Instance Methods
public func questionStrategy(
  for questionGroup: QuestionGroup) -> QuestionStrategy {
  return questionStrategyType.questionStrategy(
    for: questionGroup)
}

Selecting the strategy

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

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)
  }
}
// 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
  }
}
// MARK: - UITableViewDelegate
extension AppSettingsViewController {
  public override func tableView(
    _ tableView: UITableView,
    didSelectRowAt indexPath: IndexPath) {

    let questionStrategyType = 
      QuestionStrategyType.allCases[indexPath.row]
    appSettings.questionStrategyType = questionStrategyType
    tableView.reloadData()
  }
}

private let appSettings = AppSettings.shared
viewController.questionStrategy =
  SequentialQuestionStrategy(
    questionGroup: selectedQuestionGroup)
viewController.questionStrategy =
      appSettings.questionStrategy(for: selectedQuestionGroup)

Key points

You learned about the singleton pattern in this chapter. Here are its key points:

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.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.