A Modern Architecture for Swift iOS apps

One of the most important things about programming in a team, especially at an agency, is some kind of structure that the whole team follows. A team where each person does their own thing, while giving each person more freedom, ends up in a mess. For the past few weeks I’ve been researching this, trying to come up with a structure for the team I work with to follow that solves the issues that come from each member doing their own thing but without constraining each member too much. Here are the details of what I’m gonna propose we adopt:

  1. Standard project structure
  2. MVVM
  3. Bare minimum reactive programming
  4. Unit tests
  5. Dependency Injection though a design pattern

Let’s take each of these in more detail:

1. Standard project structure

I feel this one is most necessary when you’re working at an agency, where a new project is started every 4 or 5 months. With a standard structure for each app, if I have to work on a project that I’ve never seen before because there’s a bug where the app doesn’t download some data from the API for example, I already know where that network request will be located before I’ve even checked out the code. Without a standard structure for all the apps we build, I’d have to waste time just trying to figure out how it’s organised, where that time could have been put into solving the problem.

2. MVVM

Currently we still use Apple’s MVC, meaning all our view and business logic is tightly coupled in the ViewController file. This makes it very difficult to reuse code and testing becomes really hard. More than that, if a different team member comes onto the project and they see 400 or 500 line ViewControllers and have to find the place where a label’s text changes, they’re gonna be wasting their time looking through all that code. So instead, I propose we use MVVM, a way of decoupling the view and business logic into the separate ViewController and ViewModel files. The ViewController should only deal with setting the initial UI and updating it, as well as delegating button taps and other actions to the ViewModel. The ViewModel handles the business logic. One issue I found when trying to test this was how to most effectively update the view when the model changes.

I could use a delegate that the ViewController conforms to that has some sort of global updateView() function and call that in the ViewModel when the data changes, but that means if just a single property changes on the model, every element on the view needs to be updated to exactly the same thing except for one label. Instead, I want to introduce a stripped down version of a single part of reactive programming to the team.

3. Bare minimum reactive programming

A few months ago I starting working on Android projects and with the Android team. One of the things the Android team uses is RxJava and RxAndroid. These are incredibly powerful libraries that allow them to do async tasks with ease, swapping between threads when observing on and subscribing on, mapping and flatMapping observables, etc. The concept of Observales is one I want to use in the iOS team, because it solves the problem of how do you inform the view when the data has changed. Your data can just be an Observable, and the view subscribes to these observables and each time the data changes, the view gets notified. Now, RxSwift and RxCocoa exist, and I could very easily suggest we use those libraries, but I think including such massive libraries in our apps while the Swift ABI is still unstable isn’t a particularly good idea, especially because we’re an agency.

So instead I created my own Observable class. This doesn’t have the states that Rx observables have like onNext, onComplete and onError – it’s just a single state. When the data changes, the closure is called in the view. That’s it. So the class looks like this:

Screen Shot 2017-10-07 at 11.13.59

That’s it. So now, on the ViewModel we can just wrap everything in an Observable class like this:

let stringObject = Observable<String>(“This is a test”)

and on our ViewController we can ‘subscribe’ to this observable:

viewModel.stringObject.observe { [weak self] in testLabel.text = $0 }

When that stringObject’s value property changes, that closure gets fired which sets the text of the label. This way we haven’t had to create a delegate, or update each element every time we want to update only one of them. This is the best way I could think of of solving this problem without introducing any third party dependencies.

4. Unit testing

Amazingly we do no unit testing. I think it’s probably a good idea if we start. Unit tests allow us to quickly test vital sections of the whole app with just a single button press which is especially useful at an agency. For example there was a time where I fixed a bug in an app called Pearl and Dean where the app wouldn’t play videos in landscape. I saw some code in the app that I believed was trying to fix the problem but didn’t work, so I took it out and replaced it with my working code. Unfortunately, that code I took out was actually a fix for a different video player bug. Luckily the guy who put the code in that I removed was in the next day and saw the change, but the point is that unit tests would help solve a lot of issues like this. At the end of each sprint we have between 7 and 14 hours to dev test the work we did in that sprint, and I think it will be a good idea to use just 3 hours of that time to add new unit tests, fix broken ones, or fix code that now fails the previous tests.

5. Dependency Injection

Dependency Injection is a difficult issue to solve in an elegant way. The Android team uses Dagger, perhaps the best DI library on Android simply because it doesn’t use reflection and it isn’t that bad. But setting it up is still a pain. Swift has Swinject and Typhoon, but after looking at these I don’t think either one is particularly great, and again, introducing a third party dependency that the app would rely on so much in a time where the ABI is unstable really isn’t a good idea.

One of the biggest projects I’ve worked on here was an internal app for Transport for London, the people who do all the transport stuff in London, surprisingly. In that app we decided to use a very primitive form of dependency injection where we’d inject the CoreDataStack into each view controller that needed it by intercepting the segue. There were good intentions, but the implementation really wasn’t very useful. One of the biggest problems was, for example in a flow, if ViewController1 had the CoreDataStack injected into it, and ViewController5 needed it, but VC2, VC3, and VC4 didn’t have it – how do you get the CoreDataStack to VC5? The ‘solution’ was to inject it to VC2, then VC3, then VC4, and then into VC5. So now 3 ViewControllers had a property they didn’t actually need, just to pass it along.

One of the most interesting ways of doing DI in swift that I’ve come across actually uses a design pattern rather than introducing a third party dependency. Sometimes referred to as the FlowCoordinator or AppCoordinator, it’s an extension on MVVM that takes the transitioning responsibilities from each ViewController and gives it to a FlowCoordinator, which handles presenting the next ViewController, as well as holding all the properties needed for each ViewController and ViewModel and injecting them in. You can also make use of polymorphism to abstract your dependencies as protocols rather than a specific implementation, so you can inject a mock object that conforms to the same protocol in the test target.

This is a big idea and so rather than just putting it at the end of an already long blog post, I’ll instead write about it separately and post it for next week!

So those 5 principles are what I’ll propose that our team adopts, and I think they solve the problem of the team not being in sync while also keeping the freedom that each developer has come to love.

You can see all of this in action in this template project:

https://github.com/Stickerbox/iOS-Template-Project

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s