Data Flow

1. View(UI) calls method from ViewModel (Presenter).

2. ViewModel executes Use Case.

3. Use Case combines data from User and Repositories.

4. Each Repository returns data from a Remote Data (Network), Persistent DB Storage Source or In-memory Data (Remote or Cached).

5. Information flows back to the View(UI) where we display the list of items.

Dependency Direction

Presentation Layer -> Domain Layer <- Data Repositories Layer

Presentation Layer (MVVM) = ViewModels(Presenters) + Views(UI)

Domain Layer = Entities + Use Cases + Repositories Interfaces

Data Repositories Layer = Repositories Implementations + API(Network) + Persistence DB

Example Project: "Movies App"

Domain Layer

Inside the example project you can find Domain Layer. It contains Entities, SearchMoviesUseCase which searches movies and stores recent successful queries. Also, it contains Data Repositories Interfaces which are needed for Dependency Inversion.

Note: Another way to create Use Cases is to use UseCase protocol with start() function and all use cases implementations will conform to this protocol. One of use cases in the example project follows this approach: FetchRecentMovieQueriesUseCase. Use Cases are also called Interactors

Note: A UseCase can depend on other UseCases

Presentation Layer

Presentation Layer contains MoviesListViewModel with items that are observed from the MoviesListView. MoviesListViewModel does not import UIKit. Because keeping the ViewModel clean from any UI frameworks like UIKit, SwiftUI or WatchKit will allow for easy reuse and refactor. For example in future the Views refactor from UIKit to SwiftUI will be much easier, because the ViewModel will not need to change.

Note: We use interfaces MoviesListViewModelInput and MoviesListViewModelOutput to make MoviesListViewController testable, by mocking ViewModel easily(example). Also, we have MoviesListViewModelActions closures, which tells to MoviesSearchFlowCoordinator when to present another views. When action closure is called coordinator will present movie details screen. We use a struct to group actions because we can add later easily more actions if needed.

Presentation Layer also contains MoviesListViewController which is bound to data(items) of MoviesListViewModel.

UI cannot have access to business logic or application logic (Business Models and UseCases), only ViewModels can do it. This is the separation of concerns. We cannot pass business models directly to the View (UI). This why we are mapping Business Models into ViewModel inside ViewModel and pass them to the View.

We also add a search event call from the View to ViewModel to start searching movies:

Note: We observe items and reload view when they change. We use here a simple Observable, which is explained in MVVM section below.

We also assign function showMovieDetails(movie:) to Actions of MoviesListViewModel inside MoviesSearchFlowCoordinator, to present movie details screens from flow coordinator:

Note: We use Flow Coordinator for presentation logic, to reduce View Controllers’ size and responsibility. We have strong reference to Flow (with action closures, self functions) to keep Flow not deallocated while is needed.

With this approach, we easily can use different views with the same ViewModel without modifying it. We could just check if iOS 13.0 is available and then create a SwiftUI View instead of UIKit and bind it to the same ViewModel otherwise we create UIKit View. In the example project I also added SwiftUI example for MoviesQueriesSuggestionsList. At least Xcode 11 Beta is required.

Data Layer

Data Layer contains DefaultMoviesRepository. It conforms to interfaces defined inside Domain Layer (Dependency Inversion). We also add here the mapping of JSON data(Decodable conformance) and CoreData Entities to Domain Models.

Note: Data Transfer Objects DTO is used as intermediate object for mapping from JSON response into Domain. Also if we want to cache endpoint response we would store Data Transfer Objects in persistent storage by mapping them into Persistent objects(e.g. DTO -> NSManagedObject).

In general Data Repositories can be injected with API Data Service and with Persistent Data Storage. Data Repository works with these two dependencies to return data. The rule is to first ask persistent storage for cached data output (NSManagedObject are mapped into Domain via DTO object, and retrieved in cached data closure). Then to call API Data Service which will return the latest updated data. Then Persistent Storage is updated with this latest data (DTOs are mapped into Persistent Objects and saved). And then DTO is mapped into Domain and retrieved in updated data/completion closure. This way users will see the data instantaneously. Even if there is no internet connection, users still will see the latest data from Persistent Storage. example

The storage and API can be replaced by totally different implementations (from CoreData to Realm for example). While all the rest layers of the app will not be affected by this change, this is because DB is a detail.

It is a wrapper around network framework, it can be Alamofire (or another framework). It can be configured with network parameters (for example base URL). It also supports defining endpoints and contains data mapping methods (using Decodable).

Note: You can read more here: https://github.com/kudoleh/SENetworking

MVVM

The Model-View-ViewModel pattern (MVVM) provides a clean separation of concerns between the UI and Domain.

When used together with Clean Architecture it can help to separate concerns between Presentation and UI Layers.

Different view implementations can be used with the same ViewModel. For example, you can use CarsAroundListView and CarsAroundMapView and use CarsAroundViewModel for both. You can also implement one View UIKit and another View with SwiftUI. It is important to remember to not import UIKit, WatchKit and SwiftUI inside your ViewModel. This way it could be easily reused in other platforms if needed.