Chapters

Hide chapters

Design Patterns by Tutorials

Third Edition · iOS 13 · Swift 5 · Xcode 11

8. Observer Pattern
Written by Joshua Greene

Heads up... You’re accessing parts of this content for free, with some sections shown as knjapsvyb text.

Heads up... You’re accessing parts of this content for free, with some sections shown as kszasqpag text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

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.

This pattern involves three types:

  1. The subscriber is the “observer” object and receives updates.
  2. The publisher is the “observable” object and sends updates.
  3. The value is the underlying object that’s changed.

Note: this chapter provides a high-level introduction to @Published properties, but it doesn’t get into all of the details or powerful features offered in the Combine framework. If you’d like to learn more Combine, see our book Combine: Asynchronous Programming with Swift (http://bit.ly/swift-combine).

When should you use it?

Use the observer pattern whenever you want to receive changes made on another object.

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.

Heads up... You’re accessing parts of this content for free, with some sections shown as qxrurcwog text.

Heads up... You’re accessing parts of this content for free, with some sections shown as cwqyrbwyn text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
// 1
import Combine

// 2
public class User {
    
    // 3
    @Published var name: String
    
    // 4
    public init(name: String) {
        self.name = 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"

Heads up... You’re accessing parts of this content for free, with some sections shown as srhaqlnym text.

Heads up... You’re accessing parts of this content for free, with some sections shown as gbgapjdiz text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
User's name is Ray
User's name is Vicki
subscriber = nil
user.name = "Ray has left the building"

What should you be careful about?

Before you implement the observer pattern, define what you expect to change and under which conditions. If you can’t identify a reason for an object or property to change, you’re likely better off not declaring it as var or @Published, and instead, making it a let property.

Heads up... You’re accessing parts of this content for free, with some sections shown as jgbabfzur text.

Heads up... You’re accessing parts of this content for free, with some sections shown as qtkoctlow text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Tutorial project

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

import Combine
@Published public var runningPercentage: Double = 0
// 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)
}

Heads up... You’re accessing parts of this content for free, with some sections shown as rmridfdoj text.

Heads up... You’re accessing parts of this content for free, with some sections shown as dvdomgzeh text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
public var correctCount: Int = 0 {
  didSet { updateRunningPercentage() }
}
public var incorrectCount: Int = 0 {
  didSet { updateRunningPercentage() }
}
public func reset() {
  correctCount = 0
  incorrectCount = 0
}
public private(set) var score: Score

Heads up... You’re accessing parts of this content for free, with some sections shown as hwsuxqwig text.

Heads up... You’re accessing parts of this content for free, with some sections shown as nxrirjfiv text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
self.questionGroupCaretaker.selectedQuestionGroup.score =
  QuestionGroup.Score()
self.questionGroupCaretaker.selectedQuestionGroup.score.reset()
import Combine
public var percentageSubscriber: AnyCancellable?
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

Heads up... You’re accessing parts of this content for free, with some sections shown as ttzijcsek text.

Heads up... You’re accessing parts of this content for free, with some sections shown as wtvefmriq text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Key points

You learned about the observer 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.
© 2025 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as jnzojnpis text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now