Chapters

Hide chapters

Advanced iOS App Architecture

Fourth Edition · iOS 15 · Swift 5.5 · Xcode 13.2

Before You Begin

Section 0: 4 chapters
Show chapters Hide chapters

Section I

Section 1: 9 chapters
Show chapters Hide chapters

8. Architecture: Elements, Part 2
Written by René Cacheaux

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

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

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

Unlock now

In Chapter 7, you learned about Elements and how to design user interface and interaction responder elements. In this chapter, you’ll take a deep dive into two more elements: observer and use case.

Note: The example Koober Xcode project for this chapter is the same as Chapter 7’s Xcode project. To see this chapter’s material in Koober, open the Xcode project that is located in Chapter 7’s project directory.

Observer

Observers are objects view controllers use to receive external events. You can think of these events as input signals to view controllers. Observers know how to:

  • Subscribe to events
  • Process events
  • Deliver processed events to a view controller

For instance, say you’re building a view controller that needs to respond to a NotificationCenter notification. An observer would know how to subscribe to the notification, how to pull out the relevant information from the user info dictionary and would know what view controller method to call. The view controller would then perform some work in response to the processed notification. You might be thinking, but wait, adding and removing observers from NotificationCenter is really easy. Why not leave this code in view controllers? Hang tight, you’ll read about the benefits soon.

Note: Observers allow you to decouple view controllers from event technologies such as NotificationCenter, target-action, etc. Combine also allows you to decouple view controllers from event technologies. As you read this section you might be wondering why not just use Combine? Using Combine adds boilerplate code to your view controllers making them a bit harder to read. You can use the Observer pattern alongside Combine to both decouple view controllers from event technologies and to make view controllers light and easy to read. Using Combine directly inside view controllers is also a valid approach. This decision comes down to reading preference.

Mechanics

This section explains how observers are created, used and de-allocated. If this section is a bit fuzzy, don’t worry. You’ll see code examples of all these concepts further down.

Instantiating

In the simplest usage, you write an observer class for every view controller that needs to observe external events. Observers are initialized with references to the systems that emit events. This is so an observer can subscribe to events when a view controller wants to start observing.

Providing

Observers are created outside view controllers; i.e., observers are provided to their respective view controller. Observers are provided to view controllers either via a view controller’s initializer or by setting a view controller property. At this point, a view controller has a reference to its observer.

Using

Once view controllers are ready to start observing, view controllers can call an observer’s startObserving() method. During this method, observers subscribe to all the events that a view controller needs to observe. At this point, observers are live. They are accepting, processing and delivering events to their view controller.

Tearing down

In the simplest usage, observers live as long as their respective view controllers. Observer and view controller lifetimes should match. To guarantee the lifetime, make sure that a view controller is the only object holding onto an observer. Also, observers need to hold a weak reference to their event responder; i.e., view controller, to avoid retain cycles.

Types

Observer protocol

All observers implement the Observer protocol.

Observer event responder protocols

When events occur, observers need to be able to call methods on their view controller to let their view controller know an event occurred and to pass any related data. In order to do this, observers hold a weak reference to their view controller.

Observer classes

Observer classes conform to the Observer protocol. As mentioned before, they hold a weak reference to their EventResponder, which is usually a view controller. Observer classes know how to subscribe to events, process events and call methods on an EventResponder. You implement one observer class for each view controller that needs to observe external events. Here’s an example skeleton implementation:

Example

In this section, you’ll walk through a complete example so you can see how all the different types and objects work together. The example is from Koober’s sign-in screen.

Variations and advanced usage

There’s a lot more to observers than meets the eyes. In this section, you’ll explore more ways to implement observers.

Building multiple observers per view controller

Building one observer class per view controller is simple and straightforward. However, in certain situations, you might prefer to break a single observer into multiple observer classes.

Building reusable observers

What if you find yourself writing the same observer over and over again? Many of the systems that generate events in Cocoa Touch are general in nature.

Building multicast observers

In the case that you have a large number of view controllers on-screen, which are all listening to the same events from the same reusable observer class, your app could have a large number of observer instances all listening to the exact same notifications or events.

Composing multiple observers

Say you’re working on a view controller and you’ve designed four different observers. You plan on calling startObserving and stopObserving on all four observers at the same time. Creating four observer properties in the view controller and calling these methods can be inconvenient.

Initializing observer with event responder

One thing you might have noticed is the event responder property on all of the observers is mutable and not private. If, in your code, you’re following the dependency container factory method patterns shown in the examples, this isn’t a huge problem because view controllers don’t have access to observer’s event responder properties. However, you don’t have to use the dependency container pattern in order to use this Observer pattern.

When to use it

The Observer element is perfect for situations where view controllers need to update their view hierarchy in response to external events; i.e., events not emitted by the view controller’s own view hierarchy. If you’re taking a unidirectional approach, all of your view controllers probably need an observer to listen for view state changes.

Why use this element?

Observers help keep your view controllers small and light. They remove a lot of technology-specific boilerplate from your view controllers. This ends up making your view controllers much easier to read and reason about. Using observers, any developer can read a view controller without having to know specifics of NotificationCenter, Combine, ReSwift store subscriptions, etc. Anyone reading a view controller can clearly and obviously see what all external events come into the view controller by inspecting the event responder methods.

Origin

The observer element isn’t a new idea. It’s one of the patterns explained in the famous 1994 Gang of Four book, Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides.

Use case

Use cases are command pattern objects that know how to do a task needed by a user. Use cases know:

Mechanics

In this section you’ll learn, at a high level, how to create, inject, use and de-allocate use case objects. This section is pure theory. If it’s a bit fuzzy, don’t worry, you’ll walk through many different code examples further ahead. The theory will help you hit the ground running when reading through the code examples.

Instantiating

Use cases are created every time your app needs to perform a user task. For instance, a Twitter app would create a new LikeTweetUseCase instance every time a user taps on a tweet’s Like button. Use cases are usually created and started by view controllers in response to a user’s interaction with the UI. However, you can create and start a use cases in response to any system event as well; i.e., use cases aren’t just for responding to UI events.

Providing

Because use cases are created on-demand, whenever you need to perform a user task, they cannot be injected into other objects. Say you’re building a view controller for a settings screen. The view controller needs to be able to create a new use case every time a user toggles a setting. So the view controller can’t be injected with a single use case instance, because the view controller might need to create more than one instance.

Using

Use cases are super easy to use. Once you’ve created a use case, you just need to start it. It’s similar to how you create and resume URLSessionDataTasks.

Tearing down

This part is a bit more complicated. Ideally, use cases are created when needed and deallocated when completed. The easiest way to accomplish this is to have view controllers, or whatever objects are starting use cases, hold each use case instance in an optional stored property. When a use case finishes, the view controller can nil out the property.

Types

It’s time to transition from theory to code. To get started, this section covers the main types you’ll declare in order to build, create and use use case objects.

Use case protocol

Use cases are represented by a very simple protocol:

Swift Result enum

For the simplest usage, use cases report back their result. Result is a great way to report success or failure in a clean manner. Result is included starting with Swift 5:

Use case result type alias

Because Result is generic, specializing the enum in type annotations is inconvenient. Especially in closure types because the type signature becomes really long. typealiases, like the following one, help keep lines of code short:

Use case classes

This is a skeleton of an example use case implementation used to sign users in to Koober:

Use case factory type alias

In the sign-in example that you’ll walk through in the next section, the SignInViewController needs to be able to create a use case when the user taps the Sign in button. SignInViewController creates a use case using a use case factory closure. The closure’s type is too long to use inline. This use case factory typealias solves the closure type length problem:

Example

This section uses Koober’s sign-in functionality to demonstrate how use cases can be built and used. Koober’s SignInViewController needs a use case that’s capable of trying to sign a user into Koober with a username and password. In Koober, SignInUseCase implements the logic needed by SignInViewController. When a user taps the Sign In button on the sign-in screen, SignInViewController starts a SignInUseCase.

Variations and advanced usage

You’ve read most of what you need to incorporate use cases into your own Xcode projects. However, there are some subtle variations that you might prefer to use. This section walks through using protocols instead of closure types for use case factories, designing unidirectional use cases and designing cancelable use cases.

Using use case factory protocols instead of closures

One of the big drawbacks with the use case factory closure type is that the parameters aren’t labeled:

Providing use case completion closure on start

In the main example, the sign-in use case’s onComplete closure was provided to the use case during initialization of the use case. You might have thought that looked a bit strange.

Designing hybrid unidirectional-bidirectional use cases

In the main example, the SignInUseCase gives the SignInViewController the use case result via the onComplete closure. What if another object also needs to know the result? The SignInViewController could start communicating with other objects by passing the result around. However, this isn’t great because object data flow becomes very hard to follow.

Designing database backed unidirectional use cases

When building apps following unidirectional data-flow patterns, you can either store your app’s state in a database or in a Redux-like in-memory state store. This section demonstrates what use cases look like if you’re using a database to store your app state.

Designing Redux unidirectional use cases

Use cases also work really well in apps built using the Redux architecture pattern. Here’s another version of SignInUseCase that could be used inside Chapter 6’s example project:

Designing cancelable use cases

By adding some additional types, you can take what you’ve learn so far and add cancelation to any use case. The first type to look at is the Cancelable protocol:

When to use

Most of the time, use cases are used within view controllers or view models. Use cases typically run as a response to a user’s interaction with your app’s UI. However, sometimes you need to do some work in response to some system event, such as a location notification. You can use use cases for these situations as well.

Why use this element?

The use case pattern is one of the most versatile patterns I’ve used in iOS app development. Use cases fit into nearly all architecture patterns. And, they come with a lot of benefits.

Origin

I first came across code that looked like use cases when reading Agile Principles, Patterns, and Practices in C# by Robert C. Martin and Micah Martin. The use case pattern in Elements was inspired by the transaction pattern presented in the book’s Payroll case study.

Pros and cons of Elements

Pros of Elements

Cons of Elements

  1. Elements makes use of many different protocols. You might feel like you’re working with too many protocols. This is especially true in the dependency container code. If this is the case, the protocols are all optional. Feel free to exclusively use concrete versions. Just know that you might lose some unit testing benefits.
  2. Elements breaks logic down into fairly small pieces. You can end up with lots of classes. It can be difficult to navigate an Xcode project if the files aren’t organized well.
  3. While most of the Elements evolved from existing ideas and techniques, Elements as a whole is new and other developers might not be familiar with the patterns. As of this writing, this book is the only source of information about Elements.

Key points

  • Observers are objects that view controllers use to receive external events. You can think of these events as input signals to view controllers.
  • The Observer element is perfect for situations where view controllers need to update their view hierarchy in response to external events; i.e., events not emitted by the view controller’s own view hierarchy.
  • Observers help keep your view controllers small and light. They remove a lot of technology specific boilerplate from your view controllers.
  • Use cases are command pattern objects that know how to do a task needed by a user.
  • UseCases fit into nearly all architecture patterns — and they come with a lot of benefits.
  • Most of the time, use cases are used within view controllers or view models. Use cases typically run as a response to a user’s interaction with your app’s UI.
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.

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

Unlock now