Writing an iOS App from Scratch

AMARO
AMARO
Published in
7 min readSep 11, 2020

Written by Eduardo Pinto — Mobile Tech Lead at AMARO

When something goes wrong, everybody blames inherited code. It’s a fact. If the legacy codebase is too organized, we blame it to be full of nonsense boilerplate. If there are no code patterns, OOP is poorly implemented or there is no architectural framework, those are the things to blame. So, I guess I’ll follow suit and start with some historical background before going on to blame problems on what other people have done previously 😄.

At AMARO, we have sold women’s apparel since mid-to-late 2016 on our iOS app, meaning the app was bootstrapped earlier that year using Swift 2.0. Yes, by 2016 some people were already brave enough to jump headfirst on a new project using Swift, but it wasn’t until version 3.0 was out that the language got some syntax stability (I bet you remember the whole NS prefix going in and out). Knowing this, and that there was not much of a Swift code styling tradition at that time (most developers were used to writing iOS apps using Objective-C on an Apple MVC kind of framework), the first tens of thousands of lines of code we have in our app are pretty C-esque and by now look very dated.

The backend that we have today serves us an API that is not the common REST-JSON service-oriented architecture most people are accustomed to. This makes the app networking layer a bit different compared to what can be seen in other apps.

Fast forward to late 2018 and AMARO is thinking about changing to a more robust backend solution. Our backend was made in a time when our brand was still very local and never optimized for an international business plan, so our apps only knew about Brazilian address/delivery standards and, worst of all, never used localized strings (everything is static and hardcoded). Automated testing was all over the place: some stuff covered the right way, some stuff covered the wrong way, and the vast majority just not covered. It’s understandable, back when this work was done, we were a small startup that needed to deliver a product as fast as possible, but this kind of decision makes scaling the company to a global scale a very challenging task.

This is about when I arrived at AMARO. In mid 2019, I joined the company to lead the iOS team. At the time, we were in the middle of a backend re-platforming, so that it would be easier to scale in the future. The upcoming API for this new system used something like the regular REST-JSON based service-oriented architecture, so our whole networking structure would need replacing, alongside with most of the business logic that took place in the app.

The iOS team (and most of our mobile crew for that matter) were all new to the company, making it difficult to make assertive estimations over our legacy codebase. That’s when we realized we needed to write a new app from scratch.

Architectural Pattern: Clean-like

If we, from the iOS team, wanted to also keep scalable, we needed an architectural pattern that would solve our problems and also be very modular and easy to understand for new iOS developers on other functional teams. This pattern needed to be strict (to discourage design breakage), but not hindering (so it was still possible to adapt in the face of changes). We needed something that could be modular without being chaotic. This meant holding many conversations with the team about past experiences and various prototypes until we were able to come up with something SOLID (pun intended) enough for us to leap.

Having studied mobile software architecture for the past 5 years, one thing I’ve realized is that there’s no silver bullet. There’s no perfect architectural pattern, it all just boils down to what is going to be the most effective solution for the problem at hand.

Another thing that I’ve learned is that people often misunderstand the difference between presentation, domain, and data logic in an application, especially when it comes to mobile. This happens because most mobile apps do not have super complex business baked in (they are mostly consuming APIs that handle all the heavy lifting), so most of the time, the domain logic fuses with presentation and/or data. That’s what, in my opinion, happens to patterns like Apple MVC: they wanted a simple, easy-to-relate framework for people starting with small-ish apps and/or coming from other platforms and ended up with an architectural structure that makes it hard to understand the boundaries on UIViewControllers and Storyboards, for example.

In the end, most of the code chaos in these cases often sum up to three very important SOLID principles that are broken: The Single-responsibility principle (classes should have one single and clear responsibility, making changes contained to that class), the Dependency-inversion principle (one should depend on abstractions instead of concretions) and the Open-Close principle (Classes should be open for extension and closed to modifications). These three principles, in my opinion, are the most important when designing decoupled and modular software, which will result in more testable, maintainable, and predictable applications.

That said, I won’t go into the specifics of mobile architecture, there are lots of other Medium posts that are focused on deep dives (or even more thorough explanations for people new to the subject). I’ll focus more on how we made decisions and how we implemented a pattern that made the most sense for us. Other members of the team and I have lots of experience with Uncle Bob’s “Clean Architecture” inspired frameworks and that’s what we decided as a base of our architectural pattern design. Uncle Bob does a really good job explaining data, domain, and presentation boundaries in his papers and books, and, in some ways, knowing these boundaries well helps us modularize the architectural pattern itself by keeping our SOLID principles intact.

Presentation: MVVM-like

Early last year I took some interest in learning more about Flutter. It’s really interesting how Google made their framework work so well through Dart’s stock reactive API and developing presentation logic on Flutter apps using Streams and StreamControllers feels so obvious that sometimes it’s really hard going back to normal imperative programming.

At that time, I started comparing it to how I was accustomed to building screens on Swift and I felt that we already had lots of great resources for that. Maybe, even more, looking at how Flutter and Dart were still a bit embryonic at that time if we were to leverage RxSwift and RxCocoa to achieve a functional-reactive presentation layer inspired by the MVVM pattern.

MVVM (an acronym for Model-View-ViewModel) is a modularized design pattern used to abstract domain logic from UI components, often using functional-reactive frameworks, like ReactiveX, to bind data and events. While I’m not going to go deeper on MVVM specifics, We chose it due to this streamlined way of reflecting data changes and also perform any presentation logic over streams. It’s very modular, we can test streams very well with the likes of RxSwift’s RxTest and the two-way binding (both data and events) makes it intuitive to provoke events and expect outcomes.

StateController: Domain — UIViewController interaction

Instead of using a kind of nonsense name like ViewModel for a thing that is not an actual view model, we came up with the StateController concept. It is the architectural component that interfaces with both the UIViewControllers, which are 100% presentation logic, and use cases, which are 100% domain logic. The StateController also handles the state of specific user experience, in a state machine fashion as the name implies, and feeds broken-down ViewModel objects, which are what we like to call actual view models: really simple data structures that describe an UIView state to the UIViewController. It’s also its responsibility to receive events from the interface, channeled by the UIViewController, and trigger any state changes or use cases. That is all achieved with RxSwift and RxCocoa two-way bindings for a pretty fluid functional-reactive kind of data flow.

Most of the actual cool boilerplates and templates were written in this part. UIViewController that needed a StateController would proceed to be called a StatefulViewController and its stateless counterpart a StatelessViewController (pretty Flutter-esque, right?). We also implemented a very robust protocol-oriented dependency injection support, helping us inject testing mocks and stubs. This also made our life a lot easier by automating scene boilerplate creation based on templates and how they talk to their StateController communication protocols/contracts.

Talking about automation, this text has its own context already, but I can tell all this architectural work helped us a lot in the past months to pave the way for a new application. Our team achieved an almost test-first mentality, where we think about scenarios and edge cases before going head-first into coding. We experienced very low rates of trivial bug reports, very low rates of rework caused by something that was not 100% ready for development and we implemented it wrong without asking first and, I think most importantly, the whole team developed a greater understanding of the business behind the application. It’s a huge step forward when we think about creating a development culture that focuses on empowering the developers to take chances to build something great.

Next, we are planning on continuing our narrative with a follow-up article on how we put automated tests in place (both unit and UI tests) and also how we automated and integrated repetitive tasks to our full suite. Stay tuned!

--

--

AMARO
AMARO
Editor for

We build the future of retail through best-in-class technology and data