Pragmatic iOS app architecture
Lately, I’ve been reading a lot of articles on app architectures. There are many such articles, with many different opinions and solutions. It’s great that developers are sharing their experiences, the pros and cons that might help us decide which road to take in our future projects.
I agree that there are many nice architectures, cleverly designed, with nice separation of concerns, that address the pitfalls of the other approaches. However, I also think that there is no app architecture that fits all projects.
How do we measure whether an architecture is good for a project? Well, there are several parameters that I think are relevant in this evaluation.
First, the components of the app should be nicely organized and decoupled. They shouldn’t know much about the internal details of the other components.
Second, as Uncle Bob puts it, architecture should be “screaming” about the business domain of the project. Is it a transport application? Or maybe one for financial institutions? This is the piece of information you get from the project by just quickly glancing at the code as a newcomer. Having an architecture that speaks for itself is crucial for maintaining and growing the product, especially when adding more people.
Then, of course we have scalability. How easy/hard is it to add more features? Having an elegant solution might save us a lot of time and money in the future.
Another parameter would be how the architecture fits the requirements of the business domain. Is it a heavy data driven app? Does it have a lot of forms that require user input? What is the complexity of the app that we are going to build? Is it a “5 screens” app or “50 screens” app?
Other thing that needs to be considered is the development team efficiency. Will the team be able to pick up quickly on the new architecture and possibly different concepts? Will they be able to work independently on features, without blocking themselves? Imagine having an architecture with a single storyboard and everyone in the team has to work on the user interface. Merging hell waiting to happen.
Testing is another important element in the architectural decisions. What are the key components that we want to test? When it comes to testing, I belong to the group that thinks that we must test only the parts that are worth testing. I’m not a fan of doing many useless tests that just increase the code coverage. Code coverage is just a deceiving statistics. You might have 90% test coverage, but miss the crucial parts. On the other extreme end, I’ve seen perfectly designed projects, with nicely isolated components, that have zero coverage.
This leads me to another critical part when deciding on app architecture — the context. What is the deadline and budget for the project? What is the trade-off that we must do in terms of quality — time of delivery? Engineers need more time to design and implement the project in the most elegant manner, sales need it faster. Everyone has their own interest, and we must be aware of balancing the two sides. Low quality product will not last long, but late product might not be needed in the market anymore.
That’s why I think when it comes to making architectural decisions, we need pragmatism, neutrality and understanding of the bigger picture, and for me this is the driver for such decisions. We don’t need bias to any approach and we should not throw away other approaches. That only limits our toolset and shrinks our possibilites when making decisions.
The good old MVC
I’m pretty puzzled why so many people just throw away the Model-View-Controller pattern. It stands for Massive View Controllers they say. It will not work for big projects. Well, I’ve seen (and worked on) pretty large projects that are nice, clean and maintainable and follow the Model-View-Controller. We have a lot of concepts and patterns that help us reduce the size of our view controllers, such as delegation, composition, dependency injection, protocols, pure functions, service / utility classes, navigation centers. With these techniqies, testing is not that hard either.
*MVC pattern — image taken from Apple’s documentation
But of course, it all depends on the type of app. Probably a data driven app such as Facebook will not benefit a lot from MVC. There are so many different types of cells and content, that it’s almost impossible to have the good old MVC here. A more reactive approach with uni-directional data flow is more suitable in such cases. Check this great presentation about Facebook’s architectural decisions.
From the other patterns that circulate in the iOS world, MVVM is an interesting pattern. I’ve used it in a project, with RXSwift extensions and it works nice. The challenge I have with it is that it might make your project too dependent on a third party framework, such as RXSwift. The pattern was invented by Microsoft, and there it works better, because there’s a native data binding support. Sometimes, I get the feeling we fight the iOS SDK with this one. Another challenge is the learning curve for the team, since it’s a different way of programming. In any case, it’s good upgrade on MVC and it’s a valuable option when making your decision.
Clean Architecture with VIPER and Clean Swift
I’ve read one interesting quote that VIPER is what happens when you allow Java enterprise programmers into the iOS world. VIPER has the ultimate separation of concerns, it’s testable and it follows Uncle Bob’s Clean Architecture. However, those great benefits come with a lot of boilerplate code, too many different parts and possible over-engineering. I haven’t used it directly — I’ve done one tech review on such project and I must say it looks clean. But sometimes even a small change might propagate to a lot of different layers of the app. Is it worth it? The answer is — it depends, on the many factors discussed above.
VIP, or the Clean Swift is another port of Clean Architecture, similar to VIPER. From the outside, it looks good, but haven’t used it say something from experience. Again, there are many different decoupled components here, that will hopefully make your app more testable and robust. There are Xcode templates that help you to get started with it.
One different approach, which I haven’t encountered in other articles, but we have used it in a project is Eclipse inspired plugin architecture.
First, what problems does it solve and why we have decided to go with it? Well, we had a project where the client didn’t know how the end product should look like. We had a task to create a flexible framework, which will build a lot of different apps consisted of several composable modules. The client would need those for A/B testing, to figure out which are the key features that the users want. In one app, we had for example two modules. On other, 5 different modules. The menu was also pluggable, which meant we can easily replace a tiles menu with rotating circle menu or with navigation drawer or tab bar. It was super flexible. And the best thing of it all was that the look and feel of the app was configurable from a text file. This meant we could trigger a totally different app by simple change on the backend.
The App Container knows how to read the configuration for the app and to present the modules. It has no idea which modules it needs to present, nor how are the modules implemented (native, hybrid).
The modules are standalone projects that developers can work on fully in isolation. Developers need to implement the required methods from the container in order to be integrated in it. Modules can also provide extension points, where you can inject other module’s functionality.
Common functionality (networking library, data layer) are extracted in core libraries which the modules can use. The build system combines everything and creates different apps based on the provided configuration.
This was done with heavy use of protocols. The architecture fitted really good with our requirements. However, there are challenges here as well, such as harder communication between the modules and code re-usability (since the modules are pretty isolated).
Uni-directional data flow
We will finish our architectural journey with experimental, but very interesting architecture, with uni-directional data flow. Inspired by Redux and Flux, ReSwift and other similar architectures, represent the app state in a single data structure. In this data structure, we keep the UI and the state of the models that our app is using. Views subscribe and react whenever that state is changed — they are observers. The state can be changed only by sending actions to the store that holds the app state. To perform the state changes, we use reducers. These kinds of architectures will help you to reason about your app state much easier. They are really helpful when a past state needs to be restored (such as undo functionality). However, time will tell whether they are suitable for more complex apps.
So which architecture should you use? Of course, there’s no straightforward answer. If the answer was so obvious, we wouldn’t have so many blog posts and discussions. Everyone would have been able to choose the best one all the time. Also several different architectures might work really good with a given problem domain. What are your thoughts on this topic? Do you have a preffered app architecture?
Originally published at martinmitrevski.com on March 1, 2018.