Clean Swift Architecture Terminology

This is part 1 of this terminology series:

What should I name this class, struct, and protocol?

What is the difference between worker, service, and manager?

How should I name this function, method, and parameters?

These questions frequently come up in my mentorship program, especially when you just get started. People coming from other architectures may even get confused or overwhelmed with a bunch of new terms. You may be able to guess 80% of what these mean correctly. But having a shared vocabulary is important to communicate ideas to other developers effectively. You don’t want to waste 30 minutes to just get on the same page on the terminology.

So, let’s clarify the terms used in the Clean Swift Architecture.

Scene is actually taken straight from Apple’s storyboard naming convention. A storyboard tells a story. The story is told by connecting multiple scenes together by segues. In a storyboard, when you drag a view controller to the canvas, that view controller becomes a scene.

For illustrative purpose, let’s assume we have a scene named Account. To create this scene, you name your custom view controller AccountViewController.

In MVC, you can use scene and view controller interchangeably, because you put everything in the view controller. A scene contains only one component – the view controller.

In Clean Swift, there is more to a scene. At a minimum, you have 3 components:

  1. View controller contains display logic.
  2. Interactor contains business logic.
  3. Presenter contains presentation logic.

The flow of control starts from the view controller, to the interactor, to the presenter, and back to the view controller. And this forms the VIP cycle. You name these components accordingly as such:

  1. AccountViewController
  2. AccountInteractor
  3. AccountPresenter

The Dependency Inversion Principle is used to isolate the VIP components, so that they are individually testable. The boundaries between these components are identified using protocols, and named after their functions:

  1. The AccountBusinessLogic protocol defines the functions of the interactor which conforms to the protocol.
  2. The AccountPresentationLogic protocol defines the functions of the presenter which conforms to the protocol.
  3. The AccountDisplayLogic protocol defines the functions of the view controller which conforms to the protocol.

The flow of control goes as follows:

  1. First, the view controller invokes the interactor by calling a method in the AccountBusinessLogic protocol.
  2. Next, the interactor invokes the presenter by calling a method in the AccountPresentationLogic protocol.
  3. Lastly, the presenter invokes the view controller by calling a method in the AccountDisplayLogic protocol.

But how does a component invokes another component in practice?

If an app is any useful, it must have features, or use cases. A feature is comprised of a list of requirements. For example, let’s say your app has a feature to fetch a user’s profile. But that’s a high-level concept. Under the hood, you may break up this feature into the following requirements:

  • Check login status to an API service.
  • Fetch the profile over the network.
  • Format the returned JSON into some struct.
  • Display the profile details in a table view controller.

For the sake of this discussion, let’s name this feature FetchProfile.

To send messages through the VIP cycle, you don’t send them directly over the wire. Instead, we encapsulate the data in payloads, and send them across the boundaries. These payloads are modeled as structs in Swift:

  1. The view controller sends a request to the interactor.
  2. The interactor sends a response to the presenter.
  3. The presenter sends a view model to the view controller.

Since these payloads are specific to the feature of a particular scene, we can use nested structs to better isolate them avoid name clashes. The common idiom is Scene.Feature.Payload.

For the FetchProfile feature, we have the following payload structs:

  1. Account.FetchProfile.Request
  2. Account.FetchProfile.Response
  3. Account.FetchProfile.ViewModel

Account is the scene. FetchProfile is the feature. Request, Response, and ViewModel are the payloads.

If you have a complex user interface, you’ll create custom UIView subclasses to both encapsulate the details and make them reusable. Similarly, when you have complex business or presentation logic, you create workers to encapsulate the details and make them reusable.

By default, my Xcode templates generate a single worker for the interactor named AccountWorker to contain complex business logic. You can also create workers for your presenters too.

Get the Clean Swift Xcode Templates

Subscribe below to get my Xcode templates and learn how to apply the VIP cycle to your projects, extract business and presentation logic into interactor and presenter, navigate to different scenes using multiple storyboards, and write fast, maintainable tests with confidence to make changes.

I promise I'll never send you spam. You can unsubscribe at any time.

When a worker is reused in multiple scenes, it is promoted to a shared worker, because it is shared by other interactors from other scenes.

Worker is just a general term used to mean doing work. You can call it a manager, helper, or service. It’s a little loose here. It’s more important that your team understands what it is than what its name is. Nonetheless, service object is often used to encapsulate the details of interacting with a 3rd party service, or API.

An app with only one scene, or view controller, isn’t very interesting. Most likely, you’ll create several scenes in the storyboard (or programmatically by instantiating custom UIViewController subclasses). You then establish an application flow by connecting the scenes using segues.

For example, from the Account scene, the user may tap on the gear icon in the navigation bar to go to the Settings scene. The router component of the source scene, named AccountRouter, is responsible for routing from the Account scene to the Settings scene.

The routing process has two steps:

  1. Data passing, if there is any data to pass
  2. Navigation, if not using a segue which is already performed by iOS

When a route starts, the routeToSettings(segue:) method of the AccountRouter is invoked. From there, it calls passDataToSettings(source:destination:) if there is data to pass. Next, it calls navigateToSettings(source:destination:) if there is not a segue. The convention is always as follows:

The SourceRouter‘s routeToDestination(segue:) method is invoked which, in turn, calls passDataToDestination(source:destination:) and then navigateToDestination(source:destination:). Note the use of the words source and destination.

Let’s talk a little more about the first step – how to pass data. Passing data from the source scene to the destination scene is accomplished by defining what data needs to be passed in the data stores of both scenes: AccountDataStore and SettingsDataStore.

For example, if your app requires the user to log in first before the Account scene is shown. The Account scene already has a user model which can be passed to the Settings scene. You’ll then declare this user in both the AccountDataStore and SettingsDataStore protocols, so that it can be sent by the Account scene and received at the Settings scene.

Most of the time, the data is derived from your app’s business logic, and thus the interactor acts as the data store for the scene. Therefore, the AccountInteractor conforms to the AccountDataStore protocol, and the SettingsInteractor conforms to the SettingsDataStore protocol. So both interactors need to define the user variable.

The second step is to actually present the destination scene on the screen. If you’re using a storyboard segue, this step is already taken care of by iOS. So you don’t need to do anything about the presentation. You only need to make sure the segue is triggered properly. On the other hand, you can also present the destination view controller programmatically, like the old times.

This means there are 3 navigation types:

  1. Automatic Segue – If the segue is connected to a UIControl in the storyboard such as a UIButton, it’ll be automatically triggered. You don’t need to do anything.
  2. Manual Segue – If the segue is connected to the view controller in the storyboard, you need to manually trigger it by calling the performSegue(withIdentifier:sender:) method when appropriate.
  3. Programmatic – In the navigateToSettings(source:destination:) method, you can call UIViewController‘s show(_:sender:), showDetailViewController(_:sender:), or present(_:animated:completion:) method, or the UINavigationController‘s pushViewController(_:animated:) method, and such.

That’s about it. As you saw, the meanings are kinda intuitive. You could guess it 80% before, and you can affirm it 100% now. To see it in action, check out The Clean Swift Handbook + Effective Unit Testing Bundle.

Get the Clean Swift Xcode Templates

Subscribe below to get my Xcode templates and learn how to apply the VIP cycle to your projects, extract business and presentation logic into interactor and presenter, navigate to different scenes using multiple storyboards, and write fast, maintainable tests with confidence to make changes.

I promise I'll never send you spam. You can unsubscribe at any time.

Raymond
I've been developing in iOS since the iPhone debuted, jumped on Swift when it was announced. Writing well-tested apps with a clean architecture has been my goal.

9 Comments

  1. I have some doubts on this:

    But response should be from worker, am I right? Why you create an empty response and pass back to presenter?

    1. Hi bagusflyer,

      Response is from the interactor to the presenter. The interactor can optionally delegate work to the worker. The result is returned to the interactor, and the VIP cycle continues.

      1. Thanks for your reply. So you mean I should use data returned by worker to populate the response? If this is the case, would returning response directly from worker a natural choice?

        1. Hi bagusflyer,

          The response object exists only between the interactor and presenter, because you want to encapsulate the data in that layer to keep things isolated. Since the worker is kinda dedicated to do work for the interactor, you don’t need to define a specific model layers between the interactor and worker. But if you later decide to promote the worker to a shared worker such that it can be used in multiple scenes, you can then define a formal interface with protocol.

  2. Hi Raymond,

    I have another question about the data validating. For example, I have login view controller. I need to validate the user id and password in real time. I’ll do this in ViewController when I’m using MVC. But I should move the validating logic to interactor, and interactor call present for the result. Am I right? Isn’t it a bit not so straightforward for a simple validating?

    1. Hi bagusflyer,

      Yes, you would want to move the validation logic off the view controller to the interactor. And yes, this would seem like more work than just doing it in the view controller. But isn’t that the reason you’re looking into an alternative architecture in the first place? 🙂

      Think about it this way. If you’re doing a prototype, I would use MVC too. Otherwise, your app is going to grow sooner or later, that means you’re going to have to refactor a massive view controller later when you least want to. Or, you can take up a little upfront effort now to do it right the first time.

      Take a look at this sample project on GitHub for how to wire up a super simple authentication using the VIP cycle: https://github.com/Clean-Swift/Authentication

      1. Make sense. Thank you so much.

        I noticed you create interactor, presenter etc in setup() function in ViewController. I’m wondering if there is a better way by using dependency injection? Because it may make the Unit Test for ViewController easier.

Leave a Comment

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