Between controlling the world, reshaping it or building a better one

Costin Calugaru
21 Buttons Engineering
7 min readOct 4, 2018

This year has been my first time attending the wonderful iOS & macOS conference, NSSpain. Among the many great presentations there, which are already uploaded here, the ones that most tickled my interest were concerning architecture, scalability and testability.

At 21Buttons, we are working on a hybrid Objective-C/Swift app with the aim of having it all migrated to Swift in the near future. For the Swift part, we are using a custom flavoured VIPER architecture, protocol oriented, using dependency injection and functional reactive programming, in order to be able to scale and maintain the huge code base on our hands. Unfortunately, all these lovely concepts bring along lots of boilerplate code and every little change involves modifying the protocols, the implementations, the tests, the mocks and so on. So every once in a while, looking at the dozens of files covering just one screen of the app, we ask ourselves if maybe there’s a simpler way of doing this. And of course there is, in fact there’s dozens.

Today I’m going to focus on three approaches, as different as they come, all of them presented at this year’s NSSpain. Let’s dive in.

iOS developers spelunking in the wineries of La Rioja

Uber’s RIBs

Pavel Mazurin & Victor Pena explained RIBs, the architecture/framework they are using to build Uber. The interesting thing with this concept is that it steers away from the idea that the view hierarchy should be the one driving the app and instead leaves that responsibility to the business logic. This is why there isn’t even a “V” in the name, as compared to other popular architectures like MVC, MVP, MVVM, VIPER… So if you take the View out, you’re left with smaller, reusable modules, all containing a Router, an Interactor and a Builder. Should you need a View, you can add it and, depending on its complexity, you may also add a Presenter, in which case you get a more complex module, similar to what you have in VIPER.

Breaking it into its components, the Interactor plays the most significant part, performing all the business logic, Rx subscriptions, state-altering decisions, data storing decisions or choices concerning which other RIBs should be attached as children. The Router is being told by the Interactor what other RIBs should be attached or detached. The Builder, assisted by the Component for dependency injection, instantiates all the RIB’s classes as well as other Builders for its children. The View just builds and updates the UI, handles user interaction and doesn’t contain any code that needs unit testing. The Presenter is stateless and it only transforms business models into view models and vice versa. State management is handled by attaching and detaching RIBs on the RIB tree.

This approach has the advantage of solving business logic in the same way across the whole team and to make it reusable, modular and maintainable. I imagine it also comes with lots of boilerplate, but it must have worked out pretty good for the people at Uber, as they are currently at around 600 of these modules.

Controlling the world

Stephen Celis talked about taking control of your environment. Things like date, locale, API calls, the entire world of your app could be controlled with One. Big. Singleton. This way, all the calls in your app that you wish to control could be replaced by calls to this Singleton. And from there on, you have the option to either perform their intended task or just alter them to perform any other task you want them to. This makes mocking and testing a breeze, but it may sound and look pretty scary: taking control of the world means that anyone in your team could destroy it if they wanted to.

Stephen argued that if that were the case, they would be doing it anyway, so if you trust your colleagues’ intentions now, you should also trust they won’t destroy the world willingly. To avoid accidents, you could add linter checks to make sure that these kind of actions are only allowed on your test targets, for example. And of course, you would still be able to spot possible problems in your code reviews. So if you’re willing to take the step, this idea will clear away most boilerplate code, one example being the protocols used only for injecting mocks - gone. It will also make it easier to change stuff and not having to modify the protocol, everyone that implements it, the tests… He also made a good case against dependency injection, especially when used to pass along dependencies from father to child, so even more boilerplate gone.

Overall, it was a great talk about reconsidering over-engineering in your app and making things simple, easy to test and easily controllable.

A better MVC

Dave DeLong had a refreshing take on the MVC, which, to sum it up, was basically “you’re holding it wrong”. He pointed that MVC is not a design pattern, but rather a philosophy. It says you should separate your different types of logic from your Views and have some Coordinator objects in the middle passing data and events back and forth. But it doesn’t say how exactly you should do it or where each piece of the code should go. And just because ViewController has Controller in its name, it doesn’t mean that it’s actually a Controller. Confusing maybe, especially considering the code examples you get from Apple, where everything is handled in the ViewController. But as Dave argued, the purpose of these samples is to show a certain implementation, not how to architect your project.

His approach was to break away from the idea that one screen equals one ViewController and instead divide your screens into as many VCs as responsibilities you can find for it. You’ll end up with one VC showing only an image asynchronously, one VC showing only one dividing line, a VC who’s just a container for other VCs and so on. In doing this, you’ll get a clear separation of concerns and smaller, highly reusable VCs, which can be divided into two categories: VCs that render data and VCs that render other VCs; but each one doing its specific task(s). Easy to write, easy to test, easy to maintain.

An important aspect of this philosophy is proper encapsulation and the use of generics. What goes in must come out. So if you have a VC showing a list of elements <T> and you pass it an Array<T>, then what you’ll get back from it must be of type T as well (and not an index for example). The other important thing is that the List Controller should not be in charge of the navigation. The List should not know what (if any) ViewController comes next when you tap one of its cells. This responsibility should be handed back to the Coordinator / Presenter / ParentViewController who will know what to do with this information. The List should only know how to list things. Breaking any of these concepts means breaking the encapsulation.

Taken from Dave’s slides

The main point of his talk was that you should derive your app architecture and the patterns you apply based on the problem you’re facing and not try to find the one-and-only solution that can be applied to any problem. Also, instead of throwing everything out and start over, try to learn from your past, to refactor your code and improve your architecture instead of replacing it.

Summing up

There is definitely a trade-off between going for standardisation and implementing problem-specific solutions and at 21Buttons we often find ourselves thinking we need a more flexible, yet well defined, architecture. We are looking forward to experiment with some of these concepts and implement them into our workflow. We already have some ideas about the bits we could mix with our own architecture and we will definitely be back with updates on how it went, so make sure to revisit us.

We came back with lots of ideas from NSSpain and we highly recommend it, both for the quality of the presentations and the nice people attending, but also for its organisers, doing their best to keep everyone happy. See you there, next year! Meanwhile you can watch all the videos from this year here.

They misspelled my last name, but that’s ffine 😉

--

--