My take on iOS app architecture


iOS apps have a single entry point. That single entry point can create and hold a reference to whatever we need to be global, and then inject it everywhere.

Object Oriented Design, or Architecture if you will, is a daunting topic. I believe there is never a right or wrong way to architect a software project, because the number of tradeoffs to consider is huge, so what works for a project might not be the optimal solution for the next one.

That being said, I do believe there are a basic set of practices that help build highly cohesive and decoupled code: single responsibility principle, modularity, a set of domain model objects, and dependency injection.

To illustrate my point, let’s discuss at a very simple app, that reads and writes data from and to a backend. I guess that, conceptually, covers almost 100% of the apps that are developed these days. Let’s say it is a todo list.

Domain model objects

The first step would be looking at the domain (the problem we are trying to solve) and come up with a set of abstractions that model the entities that are part of it.

In our example, we want to display todo’s, create todo’s, present a list of todo’s, and maybe share todo’s… I guess by now we all can see a model object emerging: Todo

struct Todo {
    let id: String
    let title: String
    let due: Date

Data manipulation.

We need to send Todos to a backend. And we need to read Todos from a backend as well. That probably implies dealing with network requests, and also with a good deal of JSON (or protocol buffers).

We don’t want to scatter those responsibilities across the codebase, even if it is just because network operations have to be done in a background thread, to avoid blocking the UI, so it makes sense to deal with all that async calls in just one place.

But, we have a difficult solution here: that one thing dealing with networking will have to be used from multiple places

A common solution to this problem is making the Thing That Handles Networking a singleton. (Note: Since I wrote the first draft of this post, three weeks ago, Soroush Khanlou wrote this excellent post about refactoring out those singletons. Go read it now!)

Singletons, per se, are neither good nor bad. Sometimes, something is a singleton and it makes sense to model it as such.

But, in my experience, it is better to move away from them, for one reason in particular: a singleton is a global variable, and as such it is a dependency that is very hard to get rid of.

Dependency Inversion

But what if we invert that dependency? And we have a huge advantage here: the iOS app architecture.

An iOS app has a single entry point (the AppDelegate, an implementation of the AppDelegate protocol).

That ensures that instances variables of the AppDelegate are going to be unique as well. Well, not completely, but let’s say that we can guarantee that an instance of a regular class is created once and only once.

You might say, and you would be right, that there is nothing preventing a developer from creating as many instances of that class as she wants. I believe the solution to that is, as Uncle Bob said, well, just create one.

Back to Data Manipulation

Another look at the domain will tell us that we want to:
– obtain a list of Todos
– obtain a Todo
– create a new Todo
– edit an existing Todo
– delete a Todo

This nothing more than a list of high level operations. We don’t really care, or need to care, at this stage about how those operations are actually implemented, so we can just declare them as a protocol.

typealias SingleTodo = (Todo?) -> Void
typealias MultipleTodo = ([Todo]?) -> Void

protocol TodoService {
    func all(completion: MultipleTodo)
    func todo(id: String, completion: SingleTodo)
    func save(todo: Todo)
    func delete(todo: Todo)

Notice how this protocol abstracts two different responsibilities: reading and writing. This is a hint that, eventually, we might need to split the protocol in two (again, this post provides a great discussion of why it might be a good idea to do so).

However, I am not going to do it right now, in part because it is one of the points I want to make: modularity, good separation of concerns, empowers us to further break things down whenever we things it is the right moment to do so.

Now all we have to do is provide an implementation of that protocol to our UI. And the easiest way to do that is, again, relying on the fact that we have a single entry point (the App Delegate), and applying the Dependency Inversion principle. Or, if you will, inject all the things!

So a typical AppDelegate looks like this:

class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Init UI here
        return true

I am not very fond of storyboards, so I usually have a class whose responsibility is bootstrapping the UI, creating the root view controller, the navigation logic (if necessary) and preparing both for collaboration.

I create that bootstrapping logic in the App Delegate, and I would also create an instance of my service, and inject it into the bootstrapping logic:

final class DefaultTodoService: TodoService {
    // Implementation of the TodoService protocol

final class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    let service = DefaultTodoService()
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        Bootstrap(data:service).start(window: window)
        return true

And then my data service can be injected into any view controller:

final class TodoList: UIViewController {
    private let data: TodoService
    init(nibName nibNameOrNil: String?,
         bundle nibBundleOrNil: Bundle?,
         data: TodoService) { = data
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)

Is this approach future-proof?

Nothing is completely future-proof, but I think this approach is flexible enough, and to me, it remains open enough for modification and extension.

  • There is good separation of concerns. There is one thing that deals with data (fetching and saving), there is one thing that creates UI, and there might or might not be one thing that handles navigation.
  • There is good modularity. Any of those things in the previous bullet point can be refactored and split into smaller pieces, if necessary. There is a clearly defined interface in between them, so if, say, I noticed that the implementation of my TodoService is starting to mic business logic and pure data transport, I can break it down in two.
  • There is good testability. Since I am injecting the TodoService into each view controller that needs to collaborate with it, I can inject a mock in my tests.

I have found that by pursuing a dependency graph in the form of a tree, where the root node is the App Delegate, I have more room for adapting to changing requirements, and I can factor and test my code better.

Leave a Reply

Your email address will not be published. Required fields are marked *