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
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:
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:
AccountBusinessLogicprotocol defines the functions of the interactor which conforms to the protocol.
AccountPresentationLogicprotocol defines the functions of the presenter which conforms to the protocol.
AccountDisplayLogicprotocol 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
- Next, the interactor invokes the presenter by calling a method in the
- Lastly, the presenter invokes the view controller by calling a method in the
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
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
FetchProfile feature, we have the following payload structs:
Account is the scene.
FetchProfile is the feature.
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
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:
(segue:) method is invoked which, in turn, calls
(source:destination:) and then
(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:
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
SettingsDataStore protocols, so that it can be sent by the
Account scene and received at the
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
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
UIControlin the storyboard such as a
UIButton, 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 call
present(_:animated:completion:)method, or the
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.