Implementing delegation 

Imagine you're building an interactive cookbook. Cookbooks are often confusing as all of the steps may be on the same page, without images, and you believe that a full page with videos and so on will provide a better experience. 

We can break down the cookbook into multiple recipes—each recipe has an ingredient list and multiple steps to accomplish in order, only one step after the other. We don't really need to focus right now on the ingredient list, but more on how we can use delegation in order to easily abstract away the realization of a recipe:

class Step: Equatable {
let instructions: String = ""
weak var delegate: StepDelegate?
}

Step is merely a simple abstraction over a recipe step:

class Recipe {
let steps: [Step]
init(steps: [Step]) {
self.steps = steps
}
    func step(after: Step) -> Step? { /* implement me */ }
func step(before: Step) -> Step? { /* implement me */ }
}

Recipe is just defined for now as an ordered collection of steps, as well as a simple helper method for getting the next and previous step. As an exercise, you can implement them, and then generalize to Array or Collection types through the power of extensions:

protocol StepDelegate: NSObjectProtocol {
func didComplete(step: Step)
}

extension Step {
func complete() {
delegate?.didComplete(step: self)
}
}

The StepDelegate protocol is defining a single method that will be invoked when the step is marked as completed:

class RecipeCookingManager: NSObject {
weak var delegate: RecipeCookingDelegate?
private let recipe: Recipe

init(recipe: Recipe) {
self.recipe = recipe
}

func start() {
guard let step = recipe.steps.first else { fatalError() }
run(step: step)
}

private func run(step: Step) {
step.delegate = self
// Make the step available to the UI layer, present it etc...
// When the user finishes the step, ensure to call step.complete()

}
}

In the previous code, it is very important to mark the delegate weak. Failing to do so would create a strong relationship between the object that originates the events and the delegate, which, in turn, would likely prevent the safe and timely deallocation of both objects.

RecipeCookingManager is responsible for coordinating the user experience involved in cooking a recipe. Its most important responsibility is to be able to move forward through the recipe:

protocol RecipeCookingManagerDelegate: NSObjectProtocol {
func manager(_ manager: RecipeCookingManager, didCook recipe: Recipe)
func manager(_ manager: RecipeCookingManager, didCancel recipe: Recipe)
}

RecipeCookingManagerDelegate is responsible for communicating back to the creator, application, or manager when the recipe is complete or the cooking is cancelled and the user wants to exit the current step:

extension RecipeCookingManager: StepDelegate {
func didComplete(step: Step) {
step.delegate = nil // no need for the delegate anymore
if let nextStep = recipe.step(after: step) {
run(step: nextStep)
} else {
delegate?.manager(self, didComplete:recipe)
}
}
}

In the previous extension, we are putting the logic that will move forward to the next step or complete the recipe when there is no more steps to accomplish in the recipe.

Let's now have a look at how we can use this RecipeCookingManager. In this case, we can write AutomaticCooker that would help us run tests quickly and ensure we can show any kind of recipe on screen properly:

class AutomatedCooker: NSObject, RecipeCookingManagerDelegate {

func cook(recipe: Recipe) {
let manager = RecipeCookingManager(recipe: recipe)
manager.delegate = self // self is a RecipeCookingManagerDelegate
manager.start() // start cooking the first step should be in progress!

/* complete all steps */
recipe.steps.forEach { $0.complete() }

// the did cook method should have been be called!

}

func manager(_ manager: RecipeCookingManager, didCook recipe: Recipe) {
// We're done!
}

func manager(_ manager: RecipeCookingManager, didCancel recipe: Recipe) {
// The user cancelled
}
}

Now, you should be more comfortable with the delegation design pattern, how to implement it effectively, and how to use it in your programs.