Building a clean MVVM architecture in iOS Applications using Swift language.

Jalpesh Goti
5 min readDec 1, 2023
MVVM Architecture Prepared By — Jalpesh Goti 

Introduction

As an iOS developer, one of the key challenges is to build applications that are easy to maintain, test, and extend. This is where architectural patterns come into play, and one such pattern that has gained significant popularity in recent years is the MVVM (Model-View-ViewModel) Clean Architecture. In this article, we will explore how MVVM Clean Architecture can help us build robust and scalable iOS applications.

What is MVVM Clean Architecture?

At its core, MVVM Clean Architecture is a software design pattern that separates the concerns of data, presentation logic, and user interface. It promotes loose coupling, testability, and maintainability by dividing the application into independent layers.

 The Layers of MVVM Clean Architecture

  1. Presentation Layer(Controller): This layer contains the user interface components, including the view controllers, views, and data bindings. It is responsible for receiving user input and displaying the data from the ViewModel.
  2. ViewModel Layer: The ViewModel serves as the bridge between the presentation layer and the data layer. It retrieves and transforms data from the data layer, performs business logic, and exposes observable properties that the view can bind to.
  3. Data Layer: The data layer is responsible for providing the necessary data to the ViewModel. It can retrieve data from local databases, web services, or other external sources.

 Benefits of MVVM Clean Architecture

Separation of concerns:

  • By separating the different aspects of the application, each layer can be developed, tested, and maintained independently. This promotes modular and reusable code.

Testability:

  • MVVM Clean Architecture makes it easier to write unit tests for individual components. The ViewModel layer, in particular, can be thoroughly tested with mock data without any dependency on the user interface.

Scalability:

  • As the project grows, the clear separation of concerns allows for easier extension and modification of different layers. New features can be added or existing features can be modified without impacting other parts of the application.

Code reusability:

  • With MVVM Clean Architecture, the ViewModel layer can be shared between different views, allowing for code reusability across the application.

 Implementing MVVM Clean Architecture in iOS 📲

Now that we understand the basic concepts of MVVM Clean Architecture, let’s dive into how we can implement it in an iOS application.

1. Define the Models

The first step is to define the models that represent the data within your application. These models will be used by the ViewModel layer to perform transformations and expose the data to the presentation layer.

struct UserModel {
let id: Int
let name: String
let email: String
}

2. Create the ViewModel

The ViewModel is the heart of MVVM Clean Architecture. It interacts with the data layer, performs business logic, and exposes properties that the view can bind to. When creating the ViewModel, ensure that it doesn’t have any direct dependency on the views or view controllers.

protocol UserListViewModelDelegate: AnyObject {
func fetchUsersDelegate()
func showError(message: String)
}
class UserListViewModel {
weak var delegate: UserListViewModelDelegate?

private var users: [UserModel] = []

func fetchUsers() {
// Assume a UserService for fetching users from an API
UserService.fetchUsers { [weak self] result in
switch result {
case .success(let fetchedUsers):
self?.users = fetchedUsers
self?.delegate?.fetchUsersDelegate()
case .failure(let error):
self?.delegate?.showError(message: error.localizedDescription)
}
}
}

func numberOfUsers() -> Int {
return users.count
}

func user(at index: Int) -> User {
return users[index]
}
}

3. Design the Views and View Controllers

Design your views and view controllers in a way that they are lightweight and don’t contain complex business logic. They should solely focus on displaying the data provided by the ViewModel and sending user inputs back to it.

class UserListViewController: UIViewController, UserListViewModelDelegate {
@IBOutlet private weak var tableView: UITableView!

var viewModel: UserListViewModel!

override func viewDidLoad() {
super.viewDidLoad()
// Register Tableview Cell here..
viewModel.delegate = self
viewModel.fetchUsers()
}

func usersFetched() {
tableView.reloadData()
}

func showError(message: String) {
// Display error to the user
}
}


extension UserListViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.numberOfUsers()
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserTblCell", for: indexPath) as! UserTblCell
let user = viewModel.user(at: indexPath.row)
cell.configure(with: user)
return cell
}
}

4. Implement Data Binding

Use a data binding mechanism (such as KVO, ReactiveSwift, or Combine) to establish a bi-directional relationship between the ViewModel and the views. This allows for automatic updating of the UI whenever the ViewModel’s properties change.

5. Integrate with the Data Layer

Implement the data layer to fetch and persist data from various sources. This can be done using network requests, local databases, or other data storage mechanisms. The ViewModel should interact with the data layer through interfaces or protocols, promoting loose coupling.

enum NetworkError: Error {
case invalidURL
case requestFailed
// Other possible errors
}

class UserService {
static func fetchUsers(completion: @escaping (Result<[UserModel], NetworkError>) -> Void) {
guard let service_ulr = URL(string: "https://YOUR_API_HERE") else {
completion(.failure(.invalidURL))
return
}

URLSession.shared.dataTask(with: service_ulr) { data, response, error in
if let error = error {
completion(.failure(.requestFailed))
return
}

guard let new_data = data else {
completion(.failure(.requestFailed))
return
}

do {
let users = try JSONDecoder().decode([UserModel].self, from: new_data)
completion(.success(users))
} catch {
completion(.failure(.requestFailed))
}
}.resume()
}
}

6. Unit Testing

Write unit tests for the ViewModel layer to ensure its correctness and reliability. Mock the data layer dependencies to isolate the ViewModel’s logic and verify its behavior under different scenarios.

Conclusion

In this article, we explored how MVVM Clean Architecture can help us build robust and scalable iOS applications. By separating concerns and promoting loose coupling, this architectural pattern enhances testability, maintainability, and reusability of code. When implemented correctly, MVVM Clean Architecture can empower iOS developers to create applications that are easy to maintain, extend, and test, leading to a better user experience.

So, the next time you start a new iOS project, consider adopting MVVM Clean Architecture and harness the benefits it offers. Your future self, as well as your fellow developers, will thank you for it.

🤝 If you would like to hire me as an iOS application developer or get in touch with a profile about.me. 👍🏻

--

--