Chapters

Hide chapters

Design Patterns by Tutorials

Third Edition · iOS 13 · Swift 5 · Xcode 11

22. Chain-of-Responsibility Pattern
Written by Joshua Greene

The chain-of-responsibility pattern is a behavioral design pattern that allows an event to be processed by one of many handlers. It involves three types:

  1. The client accepts and passes events to an instance of a handler protocol. Events may be simple, property-only structs or complex objects, such as intricate user actions.

  2. The handler protocol defines required properties and methods that concrete handlers must implement. This may be substituted for an abstract, base class instead allowing for stored properties on it. Even then, it’s still not meant to be instantiated directly. Rather, it only defines requirements that concrete handlers must fulfill.

  3. The first concrete handler implements the handler protocol, and it’s stored directly by the client. Upon receiving an event, it first attempts to handle it. If it’s not able to do so, it passes the event on to its next handler.

Thereby, the client can treat all of the concrete handlers as if they were a single instance. Under the hood, each concrete handler determines whether or not to handle an event passed to it or pass it on to the next handler. This happens without the client needing to know anything about the process!

If there aren’t any concrete handlers capable of handling the event, the last handler simply returns nil, does nothing or throws an error depending on your requirements.

When should you use it?

Use this pattern whenever you have a group of related objects that handle similar events but vary based on event type, attributes or anything else related to the event.

Concrete handlers may be different classes entirely or they may be the same class type but different instances and configurations.

For example, you can use this pattern to implement a VendingMachine that accepts coins:

  • The VendingMachine itself would be the client and would accept coin input events.
  • The handler protocol would require a handleCoinValidation(_:) method and a next property.
  • The concrete handlers would be coin validators. They would determine whether an unknown coin was valid based on certain criteria, such as a coin’s weight and diameter, and use this to create a known coin type, such as a Penny.

Playground example

Open AdvancedDesignPatterns.xcworkspace in the Starter directory, and then open the ChainOfResponsibility page.

For this playground example, you’ll implement the VendingMachine mentioned above. For simplicity, it will only accept U.S. pennies, nickels, dimes and quarters. So don’t try feeding it Canadian coins!

You’ll consider each coin’s diameter and weight to validate said coins. Here are the official specifications per the United States Mint:

Ok, that’s all you need to know, so it’s time to make some money! Or rather, accept some money — you’re creating a vending machine, after all.

Before creating the chain-of-responsibility specific classes, you first need to declare a few models. Add the following right after Code Example:

// MARK: - Models
// 1
public class Coin {

  // 2
  public class var standardDiameter: Double {
    return 0
  }
  public class var standardWeight: Double {
    return 0
  }

  // 3
  public var centValue: Int { return 0 }
  public final var dollarValue: Double {
    return Double(centValue) / 100
  }

  // 4
  public final let diameter: Double
  public final let weight: Double

  // 5
  public required init(diameter: Double, weight: Double) {
    self.diameter = diameter
    self.weight = weight
  }

  // 6
  public convenience init() {
    let diameter = type(of: self).standardDiameter
    let weight = type(of: self).standardWeight
    self.init(diameter: diameter, weight: weight)
  }
}

Let’s go over this step by step:

  1. You first create a new class for Coin, which you’ll use as the superclass for all coin types.

  2. You then declare standardDiameter and standardWeight as class properties. You’ll override these within each specific coin subclass, and you’ll use them later when you create the coin validators.

  3. You declare centValue and dollarValue as computed properties. You’ll override centValue to return the correct value for each specific coin. Since there’s always 100 cents to a dollar, you make dollarValue a final property.

  4. You create diameter and weight as stored properties. As coins age, they get dinged and worn down. Consequently, their diameters and weights tend to decrease slightly over time. You’ll compare a coin’s diameter and weight against the standards later when you create the coin validators.

  5. You create a designated initializer that accepts a specific coin’s diameter and weight. It’s important that this is a required initializer: You’ll use this to create subclasses by calling it on a Coin.Type instance - i.e. a type of Coin.

  6. You lastly create a convenience initializer. This creates a standard coin using type(of: self) to get the standardDiameter and standardWeight. This way, you won’t have to override this initializer for each specific coin subclass.

Next, add the following:

extension Coin: CustomStringConvertible {
  public var description: String {
    return String(format:
    "%@ {diameter: %0.3f, dollarValue: $%0.2f, weight: %0.3f}",
    "\(type(of: self))", diameter, dollarValue, weight)
  }
}

To inspect coins, you’ll print them to the console. You make Coin conform to CustomStringConvertible to give it a nice description that includes the coin’s type, diameter, dollarValue and weight.

You next need to add concrete coin types. Add this code to do so:

public class Penny: Coin {

  public override class var standardDiameter: Double {
    return 19.05
  }
  public override class var standardWeight: Double {
    return 2.5
  }
  public override var centValue: Int { return 1 }
}

public class Nickel: Coin {

  public override class var standardDiameter: Double {
    return 21.21
  }
  public override class var standardWeight: Double {
    return 5.0
  }
  public override  var centValue: Int { return 5 }
}

public class Dime: Coin {
  public override class var standardDiameter: Double {
    return 17.91
  }
  public override class var standardWeight: Double {
    return 2.268
  }
  public override  var centValue: Int { return 10 }
}

public class Quarter: Coin {

  public override class var standardDiameter: Double {
    return 24.26
  }
  public override class var standardWeight: Double {
    return 5.670
  }
  public override  var centValue: Int { return 25 }
}

With the previous code, you create subclasses of Coin for Penny, Nickel, Dime and Quarter using the coin specifications provided earlier.

Great! You’re now ready to add the chain-of-responsibility classes. Add the following to the end of the playground:

// MARK: - HandlerProtocol
public protocol CoinHandlerProtocol {
  var next: CoinHandlerProtocol? { get }
  func handleCoinValidation(_ unknownCoin: Coin) -> Coin?
}

Here, you declare the handler protocol, which has requirements for handleCoinValidation(_:) and a next property.

Add this code next:

// MARK: - Concrete Handler
// 1
public class CoinHandler {

  // 2
  public var next: CoinHandlerProtocol?
  public let coinType: Coin.Type
  public let diameterRange: ClosedRange<Double>
  public let weightRange: ClosedRange<Double>

  // 3
  public init(coinType: Coin.Type,
              diameterVariation: Double = 0.05,
              weightVariation: Double = 0.05) {
    self.coinType = coinType

    let standardDiameter = coinType.standardDiameter
    self.diameterRange =
      (1-diameterVariation)*standardDiameter ...
      (1+diameterVariation)*standardDiameter

    let standardWeight = coinType.standardWeight
    self.weightRange =
      (1-weightVariation)*standardWeight ...
      (1+weightVariation)*standardWeight
  }
}

Here’s what you’ve done:

  1. You declare CoinHandler, which will be the concrete handler.

  2. You declare several properties:

  • next will hold onto the next CoinHandler.
  • coinType will be the specific Coin this instance will create. Consequently, you won’t need to create specific coin validators for Penny, Nickel, Dime and Quarter.
  • diameterRange and weightRange will be the valid range for this specific coin.
  1. You lastly create an designated initializer, init(coinType: diameterVariation:weightVariation). Within this, you set self.coinType to coinType, and you use standardDiameter and standardWeight to create self.diameterRange and self.weightRange.

You also need to make CoinHandler conform to CoinHandlerProtocol:

extension CoinHandler: CoinHandlerProtocol {

  // 1
  public func handleCoinValidation(_ unknownCoin: Coin) ->
    Coin? {
    guard let coin = createCoin(from: unknownCoin) else {
      return next?.handleCoinValidation(unknownCoin)
    }
    return coin
  }
  // 2
  private func createCoin(from unknownCoin: Coin) -> Coin? {
    print("Attempt to create \(coinType)")
    guard diameterRange.contains(unknownCoin.diameter) else {
      print("Invalid diameter")
      return nil
    }
    guard weightRange.contains(unknownCoin.weight) else {
      print("Invalid weight")
      return nil
    }
    let coin = coinType.init(diameter: unknownCoin.diameter,
                             weight: unknownCoin.weight)
    print("Created \(coin)")
    return coin
  }
}

Let’s go over these two methods:

  1. Within handleCoinValidation(_:), you first attempt to create a Coin via createCoin(from:) that is defined after this method. If you can’t create a Coin, you give the next handler a chance to attempt to create one.

  2. Within createCoin(from:), you validate that the passed-in unknownCoin actually meets the requirements to create the specific coin given by coinType. Namely, the unknownCoin must have a diameter that falls within the diameterRange and weightRange.

    If it doesn’t, you print an error message and return nil. If it does, you call coinType.init(diameter:weight:) passing the values from unknownCoin to create a new instance of the coinType. Pretty cool how you can use a required initializer like that, right?

You’ve got just one more class to go! Add the following to the end of the playground:

// MARK: - Client
// 1
public class VendingMachine {

  // 2
  public let coinHandler: CoinHandler
  public var coins: [Coin] = []

  // 3
  public init(coinHandler: CoinHandler) {
    self.coinHandler = coinHandler
  }
}

Here’s what you’ve done:

  1. You create a new class for VendingMachine, which will act as the client.

  2. This has just two properties: coinHandler and coins. VendingMachine doesn’t need to know that its coinHandler is actually a chain of handlers, but instead it simply treats this as a single object. You’ll use coins to hold onto all of the valid, accepted coins.

  3. The initializer is also very simple: You simply accept a passed-in coinHandler instance. VendingMachine doesn’t need to how a CoinHandler is set up, as it simply uses it.

You also need a method to actually accept coins. Add this next code right before the closing class curly brace for VendingMachine:

public func insertCoin(_ unknownCoin: Coin) {

  // 1
  guard let coin = coinHandler.handleCoinValidation(unknownCoin)
    else {
    print("Coin rejected: \(unknownCoin)")
    return
  }
  
  // 2
  print("Coin Accepted: \(coin)")
  coins.append(coin)

  // 3
  let dollarValue = coins.reduce(0, { $0 + $1.dollarValue })
  print("")
  print("Coins Total Value: $\(dollarValue)")

  // 4
  let weight = coins.reduce(0, { $0 + $1.weight })
  print("Coins Total Weight: \(weight) g")
  print("")
}

Here’s what this does:

  1. You first attempt to create a Coin by passing an unknownCoin to coinHandler. If a valid coin isn’t created, you print out a message indicating that the coin was rejected.

  2. If a valid Coin is created, you print a success message and append it to coins.

  3. You then get the dollarValue for all of the coins and print this.

  4. You lastly get the weight for all of the coins and print this, too.

You’ve created a vending machine — But you still need to try it out!

Add this code to the end of the playground:

// MARK: - Example
// 1
let pennyHandler = CoinHandler(coinType: Penny.self)
let nickleHandler = CoinHandler(coinType: Nickel.self)
let dimeHandler = CoinHandler(coinType: Dime.self)
let quarterHandler = CoinHandler(coinType: Quarter.self)

// 2
pennyHandler.next = nickleHandler
nickleHandler.next = dimeHandler
dimeHandler.next = quarterHandler

// 3
let vendingMachine = VendingMachine(coinHandler: pennyHandler)

Let’s go over this:

  1. Before you can instantiate a VendingMachine, you must first set up the coinHandler objects for it. You do so by creating instances of CoinHandler for pennyHandler, nickleHandler, dimeHandler and quarterHandler.

  2. You then hook up the next properties for the handlers. In this case, pennyHandler will be the first handler, followed by nickleHandler, dimeHandler and lastly quarterHandler in the chain. Since there aren’t any other handlers after quarterHandler, you leave its next set to nil.

  3. You lastly create vendingMachine by passing pennyHandler as the coinHandler.

You can now insert coins in the vendingMachine! Add the following to insert a standard Penny:

let penny = Penny()
vendingMachine.insertCoin(penny)

You should see the following printed to the console:

Attempt to create Penny
Created Penny {diameter: 0.750,
  dollarValue: $0.01, weight: 2.500}
Accepted Coin: Penny {diameter: 0.750,
  dollarValue: $0.01, weight: 2.500}
  
Coins Total Value: $0.01
Coins Total Weight: 2.5 g

Awesome — the penny was handled correctly. However, this one was easy: It was a standard penny, after all!

Add the following code next to create an unknown Coin matching the criteria for a Quarter:

let quarter = Coin(diameter: Quarter.standardDiameter,
                   weight: Quarter.standardWeight)
vendingMachine.insertCoin(quarter)

You should then see this in the console:

Attempt to create Penny
Invalid diameter
Attempt to create Nickel
Invalid diameter
Attempt to create Dime
Invalid diameter
Attempt to create Quarter
Created Quarter {diameter: 0.955,
  dollarValue: $0.25, weight: 5.670}
Accepted Coin: Quarter {diameter: 0.955,
  dollarValue: $0.25, weight: 5.670}
  
Coins Total Value: $0.26
Coins Total Weight: 8.17 g

Great — the quarter was also handled correctly! Notice the print statements for penny, nickel and dime, too? This is expected behavior: The unknown coin was passed from CoinHandler to CoinHandler until, finally, the last one was able to create a Quarter from it.

Lastly, add the following to insert an invalid coin:

let invalidDime = Coin(diameter: Quarter.standardDiameter,
                       weight: Dime.standardWeight)
vendingMachine.insertCoin(invalidDime)

You should then see this printed to the console:

Attempt to create Penny
Invalid diameter
Attempt to create Nickel
Invalid diameter
Attempt to create Dime
Invalid diameter
Attempt to create Quarter
Invalid weight
Coin rejected: Coin {diameter: 0.955,
  dollarValue: $0.00, weight: 2.268}

Fantastic! VendingMachine rejected that invalid coin just as it should.

What should you be careful about?

The chain-of-responsibility pattern works best for handlers that can determine very quickly whether or not to handle an event. Be careful about creating one or more handlers that are slow to pass an event to the next handler.

You also need to consider what happens if an event can’t be handled. Will you return nil, throw an error or do something else? You should identify this upfront, so you can plan your system appropriately.

You should also consider whether or not an event needs to be processed by more than one handler. As a variation on this pattern, you can forward the same event to all handlers, instead of stopping at the first one that can handle it, and then return an array of response objects.

Tutorial project

You’ll build an app called RWSecret in this chapter. This app allows users to decrypt secret messages by attempting several known passwords provided by the user.

You’ll use two open-source libraries in this app: SwiftKeychainWrapper (http://bit.ly/SwiftKeychainWrapper) to store passwords within the iOS keychain, and RNCryptor (http://bit.ly/RNCryptor) to perform AES, or Advanced Encryption Standard, decryption.

It’s OK if you’re not familiar with the iOS keychain or AES decryption — these libraries do the heavy lifting for you! Your task will be to set up a handler chain to perform decryption.

Open Finder and navigate to where you downloaded the resources for this chapter. Then, open Starter\RWSecret\RWSecret.xcworkspace (not the .xcodeproj file) in Xcode.

This app uses CocoaPods to pull in the open-source libraries. Everything has already been included for you, so you don’t need to do pod install. You simply need to use the .xcworkspace instead of the .xcodeproj file.

Build and run. You’ll see the Decrypt screen:

If you Tap to decrypt, you’ll see this printed to the console:

Decryption failed!

What’s up with that?

While the view has already been set up to display secret messages, the app doesn’t know how to decrypt them! Before you can add this functionality, you first need to know a bit about how the app works.

Open SecretMessage.swift, and you’ll see this is a simple model with two properties, encrypted and decrypted:

  • encrypted holds onto to the encrypted form of the message. This is set via init(encrypted:), so it will always have a value.

  • decrypted is set whenever the message is decrypted. This is initially set to nil, as SecretMessage doesn’t know how to perform decryption.

Next, open DecryptViewController.swift. This is the view controller that’s shown whenever the app is launched. It uses a tableView to display SecretMessages. Scroll down to tableView(_:didSelectRowAt:) to see what happens when a cell is tapped.

Specifically, look for this line:

secretMessage.decrypted = passwordClient.decrypt(secretMessage.encrypted)

passwordClient acts as the client for handling decryption requests, but seemingly, this method must always be returning nil.

Open PasswordClient.swift, scroll down to decrypt(_:), and you’ll find there’s a TODO comment there. Ah ha! This is what you need to implement. Specifically, you need to set up a chain of decryption handlers to perform decryption.

To do so, create a new file called DecryptionHandlerProtocol.swift within the PasswordClient group and replace its contents with the following:

import Foundation

public protocol DecryptionHandlerProtocol {
  var next: DecryptionHandlerProtocol? { get }
  func decrypt(data encryptedData: Data) -> String?
}

DecryptionHandlerProtocol will act as the handler protocol. It has two requirements: next to hold onto the next decryption handler, and decrypt(data:) to perform decryption.

Create another new file called DecryptionHandler.swift within the PasswordClient group and replace its contents with the following:

import RNCryptor

public class DecryptionHandler {

  // MARK: - Instance Properties
  public var next: DecryptionHandlerProtocol?
  public let password: String

  public init(password: String) {
    self.password = password
  }
}

DecryptionHandler will act as a concrete handler. This has two properties: next per the DecryptionHandlerProtocol requirement, and password to hold onto the decryption password to use.

You also need to make DecryptionHandler conform to DecryptionHandler Protocol. Add the following right after the previous code:

extension DecryptionHandler: DecryptionHandlerProtocol {

  public func decrypt(data encryptedData: Data) -> String? {
    guard let data = try? RNCryptor.decrypt(
      data: encryptedData,
      withPassword: password),
      let text = String(data: data, encoding: .utf8) else {
        return next?.decrypt(data: encryptedData)
    }
    return text
  }
}

This method accepts encryptedData and calls RNCryptor.decrypt(data:withPassword:) to attempt the decryption. If it’s successful, you return the resulting text. Otherwise, it passes the provided encryptedData on to the next handler to attempt decryption.

You’re making great progress! You next need to add a reference to the DecryptionHandlerProtocol on the client. Open PasswordClient.swift and add the following property, right after the others:

private var decryptionHandler: DecryptionHandlerProtocol?

Next, scroll down to setupDecryptionHandler(). This method is called in two places: in didSet for passwords, which is called whenever a new password is added or removed, and in init() after passwords have been loaded from the keychain. Replace the TODO comment within this method with the following:

// 1
guard passwords.count > 0 else {
  decryptionHandler = nil
  return
}

// 2
var current = DecryptionHandler(password: passwords.first!)
decryptionHandler = current

// 3
for i in 1 ..< passwords.count {
  let next = DecryptionHandler(password: passwords[i])
  current.next = next
  current = next
}

Here’s how this works step by step:

  1. You first ensure that passwords isn’t empty. Otherwise, you set decryptionHandler to nil.

  2. You create a DecryptionHandler for the first password, and you set this to both current and decryptionHandler.

  3. You lastly iterate through the remaining passwords. You create a DecryptionHandler for each, which you set as current.next and then update current to next as well. In this manner, you ultimately set up a chain of DecryptionHandler objects.

You lastly need to implement decrypt(_:). Replace the contents of it with the following:

guard let data = Data(base64Encoded: base64EncodedString),
  let value = decryptionHandler?.decrypt(data: data) else {
    return nil
}
return value

Since decrypt(_:) takes a String, you first attempt to convert this into base-64 encoded data and then pass this to the decryptionHandler for decryption. If this is successful, you return the resulting decrypted value. Otherwise, you return nil.

Great job — that takes care of the chain-of-responsibility implementation! Build and run. Tap to decrypt on the first cell. And then… you still see a Decryption failed! in the console!? What gives?

Remember how RWSecret uses the keychain to hold onto passwords? Yep, you first need to add the correct passwords. Tap on the Passwords button in the top right corner. Then, type password into the text field and press add.

Likewise, add passwords for ray and raywender.

Tap < Decrypt to return to the decryption screen and then Tap to Decrypt each cell to reveal the secret messages!

Key points

You learned about the chain-of-responsibility pattern in this chapter. Here are its key points:

  • The chain-of-responsibility pattern allows an event to be processed by one of many handlers. It involves three types: a client, handler protocol, and concrete handlers.

  • The client accepts events and passes them onto its handler protocol instance; the handler protocol defines required methods and properties each concrete handler much implement; and each concrete handler can accept an event and in turn either handle it or pass it onto the next handler.

  • This pattern thereby defines a group of related handlers, which vary based on the type of event each can handle. If you need to handle new types of events, you simply create a new concrete handler.

Where to go from here?

Using the chain-of-responsibility pattern, you created a secret message app that decrypts messages using passwords provided by the user. There’s still a lot of functionality that you can add to RWSecret:

  • You can add the ability to input and encrypt secret messages, instead of just decrypting them.

  • You can add the capability to send secret messages to other users.

  • You can support several types of decryption instead of only AES.

Each of these is possible using the existing patterns that you’ve already learned from this book. Feel free to continue experimenting with RWSecret as much as you like.

When you’re ready, continue onto the next chapter to learn about the coordinator design pattern.

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.