MVVM Design Pattern with Combine framework on iOS

Learn how to build an iOS application that follows MVVM Design Pattern and uses Combine framework

Maksym Shcheglov
Dec 31, 2019 · 5 min read

The UI architectural design patterns and best practices used to organise iOS code into logical components evolved over the last years. Most of the times developers prefer to avoid the Model View Controller (MVC) pattern in favour of cleaner, modular and more testable patterns. You might be already familiar with MVP, MVVM, VIPER, MVI, etc. Like any tool, all of them have pros and cons and should be used on a case by case basis.

In this article, I’ll show how to build an iOS application that follows MVVM Design Pattern and uses Combine framework under the hood.

What is MVVM?

Model-View-ViewModel (MVVM) is a UI architectural design pattern that decouples UI code from the business and presentation logic of an application. As it comes from the name, MVVM divides an application into three components to help separate the code: the model, the view, and the view model. Let’s discuss the purpose of each of those.

  • The View defines the layout, appearance and structure of the UI. The view informs the ViewModel about user interactions and observables state changes exposed by the viewModel.


Let’s dive into details and have a look at how can we implement an application that follows this pattern. We’ll create an iOS application that uses TMDb API to search a movie and show the details.

As with any design pattern, there are many ways to implement MVVM in Swift. In this article, I’ll follow the SOLID design principles and keep the focus on having clean, maintainable and testable code.


As it was mentioned above, the model layer consists of the model objects and use cases that encapsulate the data and behavior of the application. The use cases are typically utilized in conjunction with services that contain data access and caching. Taking it all into account, we can declare the MoviesUseCaseType protocol:

As you can see, the protocol functions are quite straight-forward. All of them return a type-erasing publisher, that can deliver a sequence of values over time. We’re now ready to implement the MoviesUseCase class:

The MoviesUseCase class consumes network and image loader service via initializer. Those are responsible for fetching data via network and image loading and caching. The searchMovies function could be implemented as following using Combine framework:

load creates a publisher that delivers the results of performing URL session data tasks. It returns down the pipeline Result<Movies, NetworkError> object.
➋ The map operator is used to transform the result object.
➌ Performs the work on the background queue.
➍ Switches to receive the result on the main queue.
eraseToAnyPublisher does type erasure on the chain of operators so the searchMovies(with:) function returns an object of type AnyPublisher<Result<[Movie], Error>, Never>.


With the above-mentioned code in place, we’re now ready to declare viewModel for the search screen. You might consider several options at this point. It should be a nice idea to expose @Published properties in the viewModel and observe changes from the view. A better solution would be defining a ViewModel, that transforms the input to the output:

Where MoviesSearchViewModelInput is a struct that defines UI events to be used by the viewModel:

And MoviesSearchViewModelOuput defines the view’s state via the type-erasing publisher:

It should be pointed out that you could have more complex output type in a real project. It can be declared as a struct then.

Next, we have to declare the MoviesSearchViewModel class. It is initialized with MoviesUseCaseType and MoviesSearchNavigator objects, that define movies search business rules and screens navigation respectively.

We’re now ready to implement the transform function. This is the most important and probably complex part of our project:

➊ Cancels current subscriptions.
➋ Adds a subscriber to show the details screen when a user taps on a movie from the list.
➌ Debounces search events and removes duplicates to create the searchInput object.
➍ The creation of the movies publisher, that starts search on user input and emits MoviesSearchState objects eventually.
➎ Defines idle state publisher, that emits value immediately(default state) and when the search string is empty.
➏ Merges idle and movies state publishers. Calls eraseToAnyPublisher that does type erasure on the chain of operators so the transform(input:) function returns an object of type AnyPublisher<MoviesSearchState, Never>.


Using the above setup we can implement the MoviesSearchViewController. It consumes a MoviesSearchViewModelType instance via initializer and binds one on viewDidLoad:

Next, we need a way to declare UI events. This could be achieved with PassthroughSubject type, that provides a convenient way to adapt existing imperative code to the Combine model:

We can use these events to declare the bind function which is called from viewDidLoad. It establishes a binding with the viewModel, subscribes on the output(state) changes and renders one when changed:

Just like that, we’ve created the movies search screen that follows MVVM software design pattern and is built with Combine framework.


The Model-View-ViewModel pattern helps to neatly separate the application logic and UI. It results in having single-purpose components that are easier to test, maintain, and evolve. MVVM works greatly in conjunction with functional reactive frameworks like Combine, that encourage you to write clean, readable code.

You can find the project’s source code on Github. Feel free to play around and reach me out on Twitter if you have any questions, suggestions or feedback.

Thanks for reading!

Flawless iOS

🍏 Community around iOS development, mobile design, and marketing

Maksym Shcheglov

Written by

iOS Developer with more than a decade of experience · Author of · Follow me on

Flawless iOS

🍏 Community around iOS development, mobile design, and marketing

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade