An iOS app architecture exploration

Dimitri James Tsiflitzis
10 min readDec 28, 2016

--

While building your iOS app; or software in general for that matter, you are aware of several structural issues that arise when writing code. Code has to be easily readable and each module has to be responsible for a specific functionality set that fits or communicates well with the other pieces of code that make up a system.

This results in a code base that is testable, maintainable and easily transferrable to other developers. In brief, code needs architecture. In this post, I’ll be exploring a few ways that will assist you in achieving the above goals in your own iOS projects.

Light View Controllers

Apple MVC

Most (citation needed) iOS Apps use MVC or a derivative thereof as their application architecture. In this architectural pattern a class is either a Model, a View or a Controller. The entire Cocoa SDK is built in a way that it will seem natural for most developers to produce code this way, which is a good thing. So you begin development, you read the docs and for the most part you architecture adheres to MVC. What happens in many cases though is that a large quantity of what you wish to achieve doesn’t clearly belong in either the Model, the View or the Controller. In this case all this ambiguously architected code ends up in the Controller portion of the Architecture resulting in heavy/massive/large view controllers. Making these view controllers lighter is a challenge in iOS development and is something all developers, at all levels, confront at some point. The solution? Light view controllers. Below we look at several ways to reach this goal which is also a great starting point in iOS app architecture in general.

Apple MVC

Table Views and Collection Views

UITableViewController

The UITableViewController is a dedicated view controller for table views. If you decide to use them, you will eliminate a lot of the boilerplate code that arises by using a tableview in a view controller that implements a UITableView data source and delegate. However, you have to keep in mind that the view controller’s view is a UITableView displayed full screen so you are restricted in your layout options.

Container & Child View Controllers

You can still use the UITableViewController in more complex layouts by having it as a view controller within a container view where it’s separated from its parent. This way it will continue to take care of its table view while letting the parent do its own thing. This is actually my own preferred approach since it allows for more flexibility while at the same time affording me some future proofing against a requirement that might pop up. When using this approach though the need will arise for communication between the child and the parent. For solutions to this, check out the chapters on the Notification Centre and Delegates.

Handle Cell Configuration internally

You may have come across this often time. Your table view data source has full knowledge of the properties and inner workings of your cell.

Instead of your view controller being responsible for you cell’s state, we can bring all of these assignments you see above into the table view cell subclass.

And then we have simplified the data source right away.

And thus, you cell objects functionality has been decoupled from your view controller.

Use a Data Source Class

A good idea is to factor out your table view data source into a separate class from the view controller it’s attached to. This is a common method and contributes to the decoupling of your modules and lighter view controllers.

Now, all your main view controller has to do is to assign a data source to your table view property and has become lighter in the process.

Also, for the above paradigm, instead of having your list of items maintained in you main view controller, they could be managed by a separate class too.

This way your data source class will look like this.

Factoring out your data source into another class will make you code more maintainable since you will be able to make changes in isolation. Ideally your code might become more reusable in the event that multiple view controllers in your project start using your new data source.

Container View Controllers

Using container view controllers you can have the content of multiple view controllers combined into a single interface. Within Cocoa you are exposed to container view controllers very often via UINavigationController, UITabBarController or UISplitViewController. Container view controllers can be added either in interface builder.

Container View Controller

Or in code thus.

Prior to iOS 5 Container View Controllers could only be used by Apple so creating something similar was inelegant and unnecessarily complex. By doing this.

This created something like this.

Add VC subview graph

Container view controllers are convenient because they relay all messages concerning rotation or layout to all its child view controllers. This is not possible just by adding a subview so in previous OS versions developers had to get rather “creative” in order to achieve that.

The fact that each View controller takes care of its view, overrides, messages and its overall internal state and functionality makes them advantageous when many of these are child view controllers to a root view controller. It’s an immediate way of having your code broken up into independent modules which are ideally self-contained and independent from their parent view controller which would also make them reusable. A challenge on the road to independence and the coupling between child and parent is communication. References between the two should be discouraged so it would be good to use an alternative approach such as the Notification Centre or KVO discussed below.

Delegates

You will run into the delegation pattern very early in your initial exposure to Cocoa or iOS/Mac OS programming when you first encounter UIKit. The delegating object holds a reference to the delegate object and at the appropriate time sends a messages or calls a method on it. If the delegate has opted to implement the delegate method it will respond to the message by performing a certain function such as reloading a data source or some other action when a web view has failed to load for instance. A couple of constraints to keep in mind is that the delegating object must know the delegate, via reference, and is a single recipient in a more intimate one-to-one fashion. Since the delegating object requires a reference to the delegate there is weak coupling. For example, the delegating UIScrollView sends scrollViewDidScroll: to its delegate each time it scrolls.

The syntax for delegation is well defined and there are compiler time warnings for cases where there are unimplemented methods making traceability and maintenance easier.

Delegation

Interlude: Objective-C Categories, Extensions and Swift extensions.

In Objective-C categories are a language feature that allow you to add new functionality to existing classes whether you have access to the original source file or not. Objective-C extensions are a category case that allow you to define methods anywhere but their definitions must be declared in the main interface file.

Objective-C Categories

With the use of categories large implementation files can be broken up into smaller ones. Each file can address specific functionality and large monolithic code bases can be broken up into modules.

You can read more about categories and how to define them here.

Swift extensions are similar to Objective-C categories but they are anonymous. They even allow you to extend existing types without access to the original source files.

Some use cases for categories and extensions could be to factor out private functionality, have different modules for you delegates, data sources or actions and protocol conformance in general.

Notification Centre

The Notification Centre is an iOS singleton that implements a mechanism wherein notifications, via a name, and an object of your choice and a contextual user info object can be broadcast to the entire running process of your app. It’s beamed out there and if you know what to listen to you’ll hear it. If you listen to UIApplicationWillEnterForegroundNotification you will know when your app just took up your device screen. I love this way of communicating since it’s as decoupled as it gets on iOS. However, you might discover that when you get started with the notification centre that debugging might be somewhat difficult. It gets easier as you get more experienced though.

Objects register with the notification centre to receive notifications (NSNotification objects) using the addObserver methods. Each invocation of this method specifies a collection of notifications so observing objects may register as observers of different notifications by calling these methods.

Each Cocoa program has a default notification centre. You typically don’t create your own.

This is how you post an NSNotification.

This is how you observe an NSNotification.

And of course, you’ll need to define the method that will be executed.

Key Value Observing (KVO)

Key value observing is a mechanism that allows objects, as specific instances, to be notified of changes on properties of other objects. In the context of this post this approach is suitable for communication between the Controller and the Model. The Controller, who keeps the View updated to the values in the Model, will be notified by these changes and synchronise the View accordingly.

Key Value Observing

In order to use KVO, identify a use case where it could be useful for you, register an observer to a property of your choosing and make sure you listen to changes on that property. Objects of which changes in their properties you would like to listen to must be NSObjects.

When to use the Notification Centre, Delegation or Key Value Observing

When?

Patterns

Singletons in IOS

This is a very common architecture pattern in iOS that is even used by Apple often time as evidenced by their use cases such as NotificationCenter.default which we talk about below or UIApplication.shared. It’s a method you could use to pass data back & forth within your application.

Strategies

This is a beloved design pattern, and a personal favourite, that fits Swift like a glove. In essence you define an algorithm or behaviour, abstracting it in an interface of sorts and concealing the implementation in derived classes from clients users and calling environments. With the strategy pattern you can compose objects out of behaviours by using a protocol. Let’s see that in practice below. We define our behaviour as a protocol and offer a single implementation.

We can keep defining new classes that make use of the animation behaviour and if we want to use them we can do something similar to this.

What we have achieved here is a method of having behaviours as class properties that can be easily added to or dropped from a class. Furthermore, having multiple behaviours in our classes make for some easily adjustable code and compelling designs.

MVVM

Model View Viewmodel is an architectural pattern similar to MVC. You could see it as an MVC tweak. It’s main characteristic is that it decouples the View from the Model and moves the presentation logic out of the View Controller and into this new object type, the View Model which is a UIKit independent representation of the View. If you’re familiar enough with MVC (or not!) you might realise that MVVM is a slight but useful MVC augmentation. In addition to decoupling and MVC compatibility it makes the testability of your presentation logic improved mainly because you won’t have to instantiate your View controller.

MVVM

Let’s take a more in depth look at this pattern by way of example shall we?

This is a simple model that might pop up in your app and it represents a Car Model

In our View Controller we might want to display a formatted date and a combination of the other 2 fields. During your first month or so at MVC school your viewDidLoad method contained something similar to this…

… which is perfect MVC.

If we have a Car view model:

Then our view controller code will look like this:

Please keep in mind the simplicity of the above example. The members of Car cannot be changed after initialisation. If you opt for mutable properties, and you will most of the time; consider the communication methods mentioned just above in order for the view controller to synchronise with the model.

What we’ve accomplished here is making our view controller lighter by moving some code away from it. The nice thing about it is that it could be implemented straight away in your existing MVC architecture. It also makes testing much simpler since you won’t have to instantiate your view controller.

I wish you the best in your ongoing exploration of MVVM.

In Conclusion

We have gone through several methods you could use in order to make your apps codebase more readable, maintainable and modular. Eventually your aim should be to develop effective techniques to make your code simpler in ways that could benefit you and the people working with you.

Thank you

And thanks to @BashaChris for this posts beautifully crafted hand drawn diagrams.

--

--

Dimitri James Tsiflitzis

Developer, scrum enthusiast, @sequeapp building productivity apps; formerly @nimber, @peopleperhour, @taxibeat; game dev @sprimp and orgniser @cocoaheadsgr