This is part 1 of this terminology series:
- Terminology Part 1: Clean Swift Architecture (this post)
- Terminology Part 2: Unit Testing and TDD
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:
- View controller contains display logic.
- Interactor contains business logic.
- 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:
AccountViewController
AccountInteractor
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:
- The
AccountBusinessLogic
protocol defines the functions of the interactor which conforms to the protocol. - The
AccountPresentationLogic
protocol defines the functions of the presenter which conforms to the protocol. - The
AccountDisplayLogic
protocol defines the functions of the view controller which conforms to the protocol.
The flow of control goes as follows:
- First, the view controller invokes the interactor by calling a method in the
AccountBusinessLogic
protocol. - Next, the interactor invokes the presenter by calling a method in the
AccountPresentationLogic
protocol. - 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:
- The view controller sends a request to the interactor.
- The interactor sends a response to the presenter.
- 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:
Account.FetchProfile.Request
Account.FetchProfile.Response
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.
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:
- Data passing, if there is any data to pass
- 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 routeTo
Destination(segue:)
method is invoked which, in turn, calls passDataTo
Destination(source:destination:)
and then navigateTo
Destination(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:
- Automatic Segue – If the segue is connected to a
UIControl
in the storyboard such as aUIButton
, it’ll be automatically triggered. You don’t need to do anything. - 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. - Programmatic – In the
navigateToSettings(source:destination:)
method, you can callUIViewController
‘sshow(_:sender:)
,showDetailViewController(_:sender:)
, orpresent(_:animated:completion:)
method, or theUINavigationController
‘spushViewController(_: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.
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?
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.
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?
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.
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?
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
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.
Hi bagusflyer,
The answer to your question lies in here: https://clean-swift.com/zero-configuration It’s not necessary to test the
setup()
method, because there’s no benefit to be gained.