Hi, I’m @celia, an iOS Engineer working at Merpay. In this article, I want to introduce how the Merpay iOS team practices MVVM architecture with viewModel Inputs/Outputs, to create an isolated, highly testable codebase.
Let me begin by explaining how we chose to use it in the first place. When native iOS development started in early 2018 at Merpay, we wanted to create a modular, well architected product that could be seamlessly integrated into the Mercari app, or provided to third parties as a service if needed. We wanted the codebase to be independent and lightweight, with minimum dependency requirement on open source frameworks. We also want it to be easy to write tests for, because we think of code and tests as one. This led us to try out viewModel Inputs/Outputs, an approach inspired by the company Kickstarter, and after working with it for a year and a half, our team is happy with it.
What It Is
The MVC architecture has been the default practice for iOS development from the beginning. As a project gets bigger, the view controller’s code gets more complex, and harder to maintain and test - a phenomenon well known as Massive View Controller. With MVVM, we add a new layer called viewModel between the model and the view controller, and move the presentation logic and data operations from the view controller into the viewModel. It reduces the complexity of the view controllers and makes the viewModel a single source of truth for the user interface state.
It’s common practice to combine MVVM with a binding mechanism. Many choose functional reactive programming frameworks such as ReactiveSwift or RxSwift, but at Merpay, we minimize third-party framework dependencies as much as possible. We create our viewModel with Inputs/Outputs protocols. Why protocols you ask? Well, the protocols force the code author to reach the viewModel through Inputs and Outputs each time, thus keeping a consistent style across the entire codebase. Also they're similar to Java Interface which means we can use them to mock classes easier. Essentially, it's a lightweight way to map input and output signals to mimic binding behavior, so we can have functional programming without using binding frameworks.