Recently I've spent some time thinking over my current iOS development approaches. I was trying to figure out how to make it more efficient.

My current goal is to develop apps faster, so that it would cost less for the end client.

At the same time I want to preserve extendable app architecture. For sake of comfortable and clean development process with ability to handle unexpected turns of clients' imagination.  

In other words, I'm trying to find an optimal solution for a trade-off between speed and flexibility, with fixed high quality.

Sounds like an impossible problem.

My previous proved setup

I few years ago I came to the following approach for building iOS apps, which I described in "Show me your VIPER". Also shared some thoughts on practical usage of it in "VIPER: Lessons learned".

Long story short it can be called simply VIPER + Service Oriented Architecture.

Get rid of VIPER

Though VIPER receives a lot of hate these days, I still consider it to be a pretty good thing. A good way to separate responsibilities of screens in a clean and consistent way.

All the cons related to tons of boilerplate code I've managed to solve with code and file generation. So VIPER itself doesn't slow me down significantly.

Except special cases, when I have complex UI, composed from several other screens, where each screen is also a VIPER module.

Especially, when there are too many ways of interactions between those modules, especially including layout-related interactions. Those cases brings a real headache, mainly created and worsen by VIPER.

Sometimes it's also annoying when just tiny change in screen logic leads to changes in several classes and protocols.

Throw away Core Data immediately

I used to love Core Data as a single solution for several problems:

  • Single source of truth for entities
  • Observable state changes allowing to build data-driven UI
  • Cache
  • Object graph in-memory management
  • Persistence that I received for free out of the box

It allowed to build the whole app in a cool data-driven manner:

  • App UI relies entirely on storage,
  • All side effects results, like network requests only update the storage when response received
  • Storage changes trigger updates of the UI

A kind of handcrafted unidirectional architecture, where direction is not actually restricted somehow.

Real Cons

Sorry Marcus Zarra, but Core Data is incredibly expensive.

There is a lot of additional work required to cover out CoreData's stuff with protocols, create DTOs, etc. As I don't want Core Data's managed objects, contexts and other details to spread across the whole app codebase.

Maintenance of Core Data model scheme also is not something I've always dreamed of.

So now Core Data is my main trash bin candidate.

Network Layer

I used to bring Alamofire for making network requests and Moya as a network abstraction layer.

I came to a conclusion that I don't need them any more at all.

URLSession already provides pretty simple and convenient API for writing handy API Clients. Multipart requests, SSL pinning, file download/upload is also pretty simple and can be done if needed.

UI

By the beginning of 2020 I've almost turned Storyboards and Xibs into legacy and started to use only code for building UI. Mostly with autolayout.

Now I'm moving to SwiftUI if minimum target OS version allows and I'm going to use UIKit only in cases where there is no SwiftUI equivalent.

Unidirectional App Architecture

I got rid of Core Data but I still need a single source of truth, clean way to handle observable changes for data-driven UI and unidirectional flow of data.

So I decided to move to unidirectional stuff like Flux/Redux/Elm for iOS.

I didn't pick some existing framework for that. Instead, I've researched some examples on github for inspiration and worked out (and still working on) my own stuff that I would be comfortable with.

I've also managed to make it suitable both for SwiftUI an UIKit crafted UI. So I can use it for any iOS version.

Cache, Persistence, Object management.

If needed I can configure cache on network layer with URLSession caching capabilities. If object graph grows too large I can play with some LRU in-mem cache. If persistence is required I can just write the whole state to file. If volume grows too large I can add database just as a side effect.

What I love most in my new approach, is that the core is already quite good and can be extended with anything else on-demand.

I bet that it will allow me to speed up my development at least as soon as I get used to it.

Tooling

I've started to use development automation tools more heavily. Currently I use

  • SwiftGen for type safe usage of assets and strings
  • SwiftLint with strict rule set for cleaner code
  • Fastlane as a building tools helper
  • Github Actions for CI/CD pipeline

Many of those things has always seemed to be on overkill for indie devs or small teams but on practise in turned out to be a cool helper. Yet another quality gate that brings a little more confidence and reduce stress.

When I'm gonna move?

In fact I've already done. I've been trying out all those things at once on my side project for several weeks already and I'm pretty happy how is it going.  

Generally I've already got used to Redux + SwiftUI but I still learn a lot of SwiftUI specific stuff almost every other day.

And I'm still not as fast on it as on UIKit before. Hopefully will catch up soon.