As a developer I’ve always enjoyed reading architecture-related articles. What I liked most about them was how they pointed out the possible downsides and upsides of using one type of architecture over another.
iOS Architecture can be a headache
That’s the first time I’m writing such a piece but, please, bear with me. It is based upon my 3 years of experience developing iOS applications professionally. During this time I have worked on various types of projects: from simple apps being just pretty representations of JSON to really advanced projects where we took advantage of framework-oriented programming. As I mentioned before, I have read many books and articles on software architecture, tried many approaches, failed multiple times, and learned from these failures. Even though I believe I still have a lot to learn, having all the experience under my belt gives me a pretty solid foundation to finally share my opinion.
There’s a strong chance you’ve heard about MVC, which is the default architectural pattern for developing iOS applications. Among all of the architectural patterns we use on iOS, there’s probably no other pattern that brings so much controversy. You probably know too that there are multiple alternatives to MVC such as MVP, MVVM, or VIPER. All of them have their own advantages and disadvantages, can be implemented in many different ways and there’s no happy medium that you could use for all use cases (because if such a thing existed, there would be no point in having a choice, right?).
“MVC stands for Massive View Controller”
The sentence above is one of the most often used sentences when it comes to any iOS Architecture related subject. I’ve seen it multiple times in various architectured related posts, heard it during meetup or conference talks and overall it is also one of the “undeniable facts” willingly repeated by the community.
But there’s one thing which can be easily forgotten about the theory of MVC standing for Massive View Controller. It is a horrible, dreadful lie which can be harmful – especially for junior developers. It actually originates from a Twitter joke from early 2013 and, in my opinion, is taken way too seriously even 5 years later.
MVC stands for Model View Controller. It is an architectural pattern introduced in the late 1970s, which gained a lot of popularity a few years later. We can easily say that this architecture was present during the birth of the enterprise software development we know today and is widely used on multiple platforms, not only iOS. It is a crucial element of many popular development frameworks such as ASP.NET MVC, Laravel, Spring, or Ember.js. It also doesn’t differ much from Android’s implementation of Model View Presenter.Apple chose MVC as the default iOS architecture for a good reason. First of all, it fits the ecosystem perfectly as the macOS development world had used exactly same pattern before the introduction of iOS. It allowed a smooth and painless transition for already skilled macOS developers. On top of that, MVC is a perfect match for beginners – it’s simple, as it requires only three major layers to be implemented. With other iOS development tools provided by Apple such as Storyboards or Target-Action, and Delegate patterns, it’s obvious that iOS Development is meant to be simple – at least at the early stages – to allow the developers to prototype rapidly and not to scare them off.
Understanding the problem
So far, everything sounds great, so why exactly does MVC receive so much hatred in the community? As it turns out, it’s the simplicity again.
If something is simple, it doesn’t necessarily mean it’s easy. Actually, it often takes a moment to understand how the MVC pattern can be used properly as a software development tool in the world of iOS. I believe that most of the problems with MVC in production environment are usually caused by the lack of proper understanding of other essential iOS patterns such as Delegate.
Many bespoke software development tutorials or even WWDC videos, teaching us how to take advantage of certain elements of the SDK, show us examples where the Controller is a delegate of another element. This other element sends callbacks, which are later received and interpreted by the delegate.
Now let’s try and imagine a real use case based on this approach: an application displaying certain Points of Interest on the map depending on the user’s location. Here’s a quick list of parts that need to be implemented in its simplest form:
- One View Controller responsible for providing user interaction & presentation of the data
- An instance of CLLocationManager to fetch the current location of the user
- An instance of MKMapView for handling the map instance to display the POIs
- An instance of URLSession to download POIs data based on the location
Out of these three, at least CLLocationManager and MKMapView require an external delegate. If they both delegate to the controller, the controller will implement at least two additional protocols. Also, if the URLSession calls are executed directly in the controller (let’s say in the viewDidLoad method or via Target-Action, like it’s often shown in educational materials), the controller will quickly start to swell. Now imagine your client wants you to cache these POIs in the Core Data storage to make the application work offline. We’re only adding more code, and it’s a quick way for your Controller to become massive.
Fixing the Controller
Now let’s think about the same use case a little bit differently. The controller is still meant to provide an interaction with the user, but it’s not said it’s meant to be the delegate for all of your objects. Instead, let’s try an approach where your Controller, in fact, delegates the tasks to other objects:
- An instance of CLLocationManager delegates tasks via Controller to a third-party object implementing the CLLocationManagerDelegate protocol
- An instance of MKMapView delegates tasks via Controller to a third-party object implementing the MKMapViewDelegate protocol
- An instance of URLSession is wrapped around inside a third-party object which exposes the required API methods via conforming to its corresponding third-party protocol
This way, the entire delegate logic is no longer stored inside the Controller but inside the third-party objects. This also brings us some of the benefits that wouldn't have been possible in the previous approach:
- The controller doesn’t include any business logic. Instead, it only provides UI-logic interaction by delegating the tasks to third-party objects and receiving callbacks optional from these objects to present their data.
- The entire business logic is moved into separate third-party objects that conform to specific protocols, allowing these objects to have their own responsibilities and to be unit tested and mocked if necessary.
- The same approach can be used for any other cases added to the screen in the future, for example, Core Data storage for POIs.
- The source code kept inside the controller is greatly decomposed allowing us to keep the controller small and flexible.
If there’s one architectural pattern you should master apart from MVC, Target-Action, or Delegate, it’s definitely Composite. Composite, by its nature, leads to other great architectural practices. But having just these four in mind is enough to make sure that we can prevent our Controllers from becoming massive. An untestable, large codebase written with the MVC pattern can (and should) be a myth. Bad usage of architectural patterns is always a serious problem.
Does it mean that all tutorials, conference talks, or WWDC videos should use an approach similar to the one above just to save us from bad practices? Of course not. These sources often teach us to use the Controller as a delegate for one simple reason – they’re not meant to show us good architectural practices. Instead, they’re meant to show us how to use a certain functionality, such as Core Location Manager in the simplest, most seamless way possible. And it is important to keep it simple, just to make sure that every developer at every stage of their career is able to benefit from any tutorial without unnecessary confusion.
But are MVVM or VIPER really superior to MVC? First of all, they’re more composed by default, which initially gives us a better clue on how the code should be structured. A good understanding of these architectures greatly decreases the risk of making a mistake due to their more detailed nature. But still, it shouldn’t make MVC look bad – the architecture is fine as long as its implementation is correct. And if you’re not sure which one to choose for your application – stay tuned for the second part of this series, where we will discuss the architecture you’ll fall in love with: the MVA pattern.