Every apps are developed following one or some kinds of architectures or design patterns. Choosing an architecture for an app depends on a lot of factors: team size, project size, type of the apps, release cycle etc… And the chosen architecture should be continuously reevaluated when the app grows.
MVC has been the standard architecture for iOS apps for a long time. But due to the “massive view controllers” problem, community has come up with many alternative architectures such as MVP, MVVM, Viper, RIBs, Redux etc … with more or less the same goals, separate business logic and app logic into different components to aid SOLID principles.
I have been working intensively with MVVM in the last four years at my current company. With a team of 5-7 developers, MVVM served us quite well. We often have a setup where two developers work together on the same feature. One of them focus on the view controller (UI) part, the other on the service, view model part.
But MVVM comes up with some issues too:
- We sometimes have some massive view models where the business logics behind the features are complex.
- We have our navigation logics inside our view controllers which clutter the view controllers code (it can be solved by using Coordinators pattern).
- We have bad code coverage since we can’t test all app logics due to strong couplings between components where we have to mock a lot of things to be able to test a simple component.
- No clear dependency enforcing.
Since our team as well as the complexity of our app is growing fast at this moment. I have taken a look for an alternative architecture for our app.
The goals are:
- modular architecture where big team can work independently in the same code base.
- Testability and scalability support from architecture level.
- Component-based to enforce dependency Injection and aid portability.
VIPER and RIBs come into my consideration. Since RIBs seems to be an improved version of VIPER, I decided to create sample project using RIBs to evaluate it.
RIBs is a variation of VIPER that is introduced by UBER in 2017. From their blogs, the motivations for the creating RIBs are:
- Working at scale: At Uber, scale is both a top constraint and most valuable resource. Three million drivers use our app and we can’t fail them. At the same time, as an engineering organization we have grown to a point where we have hundreds of engineers available to work together building the app. Scale is key to all of our decision-making, from the planning all the way to the rollout.
RIBs to the rescue: By utilizing RIBs and the plugin infrastructure for Carbon, we gained the benefits of the architure in key ways:
- Modularity: Each feature is developed in a way that does not depend on another unless it is Core RIBs. This architecture makes it easier for engineering teams to develop feature RIBs in parallel, without worrying how one affects another.
- Extensibility: The tree-like architecture can be extended vertically by adding children and horizontally by adding siblings or segregating business logic in different workers.
- Reliability: The core vs non-core concept implemented using the plugin framework allows us to disable non-core parts of the code, so we can move fast but minimize breaking things.
- Building together: We could not have done this without the help provided by our driver-partners, and the key insights they provided to us during our user research and beta testing phases.
RIBs are usually composed of the following elements, with every element implemented in its own class:
The sample app will fetch images from pixabay.com using a search term entered by users. The app has a simple navigation step from the result list screen to the detail screen.
The code is available in github: https://github.com/antranapp/PixabayRIBs
Some benefits from RIBs that I can observe:
- Each RIB is a separated component which has a clear dependency definition and can be developed and tested separately.
- Composition of components aids parallelism of feature development.
- Straightforward migration path from MVVM since the logic of ViewModel can be splited into Interactor and Presentor.
But there are some downside too:
- RIBs framework depends on RxSwift for message passings which can be a blocker for projects where a different reactive framework is used.
- Many boilerplates code for simple components (which can be solved with the provided code templates)
I can see that RIBs architecture can bring great benefits to big apps and big teams where components can be developed and tested by different developers in parallel. It is also suitable when your teams create multiple apps using the same components with similar business logics. For smaller apps and smaller teams, it might be overkilled and will slow you down at the beginning.
- RIBs’s repository: https://github.com/uber/RIBs
- Engineering the Architecture Behind Uber’s New Rider App: https://eng.uber.com/new-rider-app/
- Architecting Uber’s New Driver App in RIBs https://eng.uber.com/driver-app-ribs-architecture/
- RIBs - Uber’s new mobile architecture that scales to hundreds of engineers: https://www.youtube.com/watch?v=FfwZSk6VRVY