Breaking a Monolith: Using Cocoa Frameworks in iOS

Hi! My name is Chris Thomas, and I’m a Lead iOS Developer at John Lewis & Partners, working on our main customer-facing app and in-store apps.

John Lewis & Partners has had an iOS app for several years. In that time it’s had several iterations, we’ve built something for iPhone and for iPad. We’ve used Objective-C and we’ve used Swift. About three years ago we began the process of combining the iPad and iPhone offerings into a single, universal app, that relied entirely on Swift.

It took 18 months to completely rebuild the app. When we started we had a number of things we wanted to achieve:

  • get features in front of customers quickly
  • release every fortnight
  • fully automate our regression suite.

To achieve that we looked around at a few of the architectural patterns out there and chose VIPER. I won’t go into the details of VIPER here but when I first joined the team I found a few articles (I’ll list those at the end) that I read to try and get familiar with the concepts.

I joined the team just before the release of the new app. Up to this point the features being built were fairly static, we’d build a page in an area of the app and then move on to the next one. We amassed a huge number of tests for each feature and we pretty much had a test per scenario in the acceptance criteria. We’d achieved what we had set out to do.

We knew that we could now release frequently, the large number of automated tests saw to that. However, we were starting to see issues. The tracking we did on our tickets was showing a steady incline in time taken to complete and those automated tests took nearly 40 minutes to run, and to top it all off the developers had stopped having as much fun developing code as they wanted.

We realised that while using VIPER had improved our test coverage, it had also created a lot of bloat in features that could have been built a lot more simply. It was also needlessly complex and hard to navigate.

As a group we were looking to figure out ways of improving our codebase. We had something that was stable, well tested and most importantly worked. We didn’t want to lose that. What we wanted was a way to add new things, improve existing things and deliver them faster.

Some of our biggest problems came from the fact our tests took so long to run. Following our own engineering principles (see Mike’s blog or the principles themselves) we try to pair as much as we can and always run our tests before we push our code, so waiting 40 minutes every time we’ve written something is a bit much. Especially, as in reality, it’s 40 minutes plus then fixing several broken tests and re-running them. So that’s usually about 60 minutes.

It took this long because we had to run everything. We had a single, monolithic app, and needed to be sure that the changes we made here hadn’t broken anything somewhere else in the app.

So what could we do?

Introducing Frameworks

We decided that one way to change the way we develop was to try and break down the codebase into individual frameworks. This idea isn’t new; Cocoa Touch Frameworks have been available in iOS development for a while. The premise is similar to any micro-service/micro-ui architecture you’ve probably heard website teams talk about.

We thought that by adding frameworks to the app and building our features using them, this would help us split up the code into smaller, more manageable chunks. We could separate concerns, make shareable code easily accessible (rather than hidden in a VIPER stack) and hopefully take advantage of some of the new parallelisation that Xcode 10 shipped with. We could rely on a hierarchy of dependencies that meant we could be more specific with the tests we ran (which would take less time). We could choose architectural patterns that matched the complexity of the code we wrote.

Where to Start?

That’s a hard question. Our team hadn’t done much like this before and, much like that phrase about skinning cats, there are so many ways to go about slicing up a codebase (just look at the number of different architectural patterns out there) — where do you start?

Well, we started with some small things — we built a framework for some extensions we’d written to core Swift functions (we called this Bedrock), some UI Components that we thought were used in multiple places in the app, and some custom layouts for UICollectionViews. The reasons we started here is because it is what we needed at the time, and the impact of them going wrong was small.

We then got a little bigger with our ambitions and built a small feature using frameworks. We added an animated splash screen. Unfortunately this wasn’t received with universal acclaim, and we decided that we needed to remove it. However, having built the entire thing in a separate framework all we needed to do was delete a couple of lines of code in the main app, two build settings and then — just like that — it was gone. We’ve tried to remove old VIPER code and it’s been far less enjoyable.

What we had

What we’ve got now

We’ve created quite a few frameworks over the 12 months we’ve been doing this, more than you see here. As we have progressed we’ve consolidated. We’ve changed our minds about what goes where, so while this was true at time of writing, it’s probably not true at time of reading.

Some of them are shared (Core) and cover functionality that is used in multiple places in the app (and in fact are shared across multiple apps!) and others are feature-specific.

We also build demo apps for every new feature we create. This means we have really small, self-contained apps that provide even faster feedback.

What are we trying to achieve/what have we achieved?

There are a lot of blog posts written about how a particular pattern is fantastic, but experience has told me that ALL patterns have their positives and negatives. VIPER can be good if used wisely (at least this is what I’m told, but I’m old, bitter and scarred by my experiences with it 😉), MVC, MVVC, MVP, Coordinators, name some more… They all have their place, but if you have a single monolithic app with lots of features it’s probably hard to use more than one. Using frameworks breaks up the app and separates concerns. This, in turn, allows us to pick an architectural pattern for each feature that matches the problems we are trying to solve. Each framework only exposes a set of public APIs, so what does it matter if they are built differently, so long as (and here’s the important bit) each one is internally consistent?

By adopting frameworks we think we have managed to:

  • Easily add code/Easily delete code we didn’t want anymore
  • Experiment with our features
  • Have feature-appropriate architecture
  • Decrease our time to deliver (again)
  • Onboard developers faster
  • Give junior developers a ‘safer’ environment to learn and play
  • Reduce our feedback loops in development

Things to think about

  • Clever names. Or, more accurately, names you think are clever/funny. These will come and bite you in the rear when it comes to explaining what that particular framework does [No, even if you think it’s obvious, believe me, it isn’t!]
  • Trying to run before we could crawl - In doing so we ended up having to recreate a lot of functionality that we might have wanted to hold off on, until we were more familiar with what we were trying to achieve.
  • Dependencies - import becomes a much bigger deal. Using frameworks forces your devs to really think about how the code is structured. This is a good thing, but can be time consuming. Also prepare for a few bang-head-on-desk moments while you try and figure out just which package you forgot to import correctly
  • Importing 3rd party frameworks into 1st party frameworks - not easy to google for solutions but luckily my colleague Colin has your back
  • Performance - We use dynamic frameworks. We haven’t noticed a considerable increase in the time to launch, but most of our customers use iOS 12 or greater, and luckily in iOS 12 Apple fixed a bug that would have caused this

Final Thoughts

Frameworks are great if you are looking for a way to break up a large project into more manageable chunks, build features using a variety of code patterns or architectures, and experiment with what you are building.

It’s my opinion that no one code pattern solves all problems, and forcing developers down one route or another is probably not great in the long run. Frameworks have given us flexibility, but if you’re writing a one-page app that is never going to grow they probably aren’t right for you. If you do decide to use them, though, I hope you find them as helpful as we have at JL&P!

Others references I read while writing code (and this post)

Articles I’ve referenced:

Mike Neilens’ post on JL&P Software Engineering Principles

The JL&P Software Engineering Principles themselves

VIPER articles I read when I joined the team:

--

--

Chris Thomas
John Lewis Partnership Software Engineering

I’m currently a lead iOS Developer working for John Lewis & Partners.