As I said in my previous post, today I would like to talk about some architectural stuff.
tl;dr; It’s worth investing some time in trying MVVM instead of MVC. Using a UIViewController container as a flow management point is also a good idea when leveraging the responder chain.
During my last days at Neofonie Mobile (btw, they’re hiring), I had the task of developing the architecture of an upcoming iPad app.
The app is the iPad version of an already existing iPhone app the company developed something like 3 years ago, and the non functional requirements are basically the following:
- The app is targeted for iPad
- It will be universal somewhere in the future (read: the iPhone app should be ported to the new architecture)
- It should be easily white-labeled. This means there has to be an easy way to make clones of the app and just change some configuration parameter to fit the needs.
My problem with MVC
During the last months there has been a lot of buzz about MVC standing more and more for “Massive View Controller”, and developers are pushing towards lighter view controllers in the name of maintenance and reusability.
This is to say that having the chance of creating an app from scratch, I didn’t want to do things “quick and dirty”™, and I opted for a different architectural pattern, which I learned about two years ago: MVVM.
MVVM stands for Model-View-ViewModel, and while it sounds like a tongue-twister, it’s actually a smart architectural pattern developed by Microsoft.
The basic idea behind the pattern is to keep the Model layer as in the MVC (i.e. model objects mirroring a database structure or a backing web-service), but to collapse the View and the Controller into the View layer (for iOS this means the XIB/Storyboard/UIView subclass and the corresponding UIViewController should only care about user interaction with the UI elements), leaving all the real work to the ViewModel layer.
I learned about this pattern when I still worked as a freelance and I had to develop a iOS app for a client who also wanted an Android version, and since their know-how was mainly C#, their IT department suggested to use Xamarin to enforce code sharing between the two platforms (mainly the networking layer, model objects and some business logic).
I have to say, at the beginning I wasn’t really happy about using a third party framework – I really believe mobile development should be done native, without any HTML tools and generally without any additional layer between the developer and the app – but I gave a look at this framework, and I decided it would have been a good idea to try it (Long story short, I regretted my choice not because of the architecture or because of the idea behind the framework, but because of the implementation, lack of documentation, and the maintainer’s tendency to push new versions of the thing that made you regret indeed of having already begun the development).
Of course developing a MVVM app in C# is like feeling at home, while porting this concept to Objective-C and iOS paradigms has proven to be slightly more difficult.
In my opinion MVVM is a great choice for mobile apps for a couple of reasons:
- Lighter ViewControllers (i.e. reusable)
- It’s really easier now to create different controllers using the same (or a slightly modified) ViewModel, without having to rewrite a lot of boilerplate code
- Tests. And this reason alone is worth the shot. You can test the ViewModels without even having the UI ready. UI Testing should be done separately, and with MVVM this is possible
- Last but not least, when developing an app that should target smartphones and tablets (read also: TVs, watches, and so on), you can focus on the UI for each platform and leverage the existing ViewModels for all the business logic work
Back to the app
Having introduced the architectural pattern, let’s go back to the app and see some functional requirements.
I can’t say too much here, and it’s actually not even that important (at least not as much as the non-functional requirements), so I will just say two things:
- The iPad app often shows the same UI components in different screens
- The iPhone app shows a single view at a time, while the iPad app pushes the concept of a UISplitViewController forward, by showing n views on screen at the same time (with only the left side one fixed on screen), with the whole navigation stack scrollable by the user.
A possible solution
I decided to slightly extend the MVVM architecture and introduce a new component, that I called FlowModel (with a corresponding FlowController).
The idea behind this is that a single feature (e.g. browsing recipes) consists of more screens (list of categories, recipes for one category, recipe details, sharing screen and so on), and there should be a shared coordinator object behind the scenes managing the pieces.
So a FlowController in the iPad app is the “feature container” (in the screenshot above it’s the whole container except the top bar, where the menu button sits – that’s a RootController responsibility), and it has a FlowModel as the equivalent of a ViewModel but just for coordinating the interaction flow and what to do and what screen to show after some user interaction. On an iPhone, the FlowController may not exist (or may merely be a UINavigationController), but the FlowModel object could be still there, and passed through the UIViewControllers as a baton.
Leveraging the responder chain
I stumbled upon an article a couple of weeks ago talking about the responder chain in iOS and how it could be leveraged to send UI actions (e.g. taps on buttons). I can’t find the link to the article anymore, but this is also a good resource if you want to learn something more about the topic.
In the architecture the responder chain is used to send messages not only related to UI actions, but also related to “interesting events” happening in the ViewControllers. For example, if in a ListViewController an item is tapped, an action will be sent to the responder chain with the model object along. To do this, there are a couple of things to take care of:
- The ViewModels and the FlowModels should be subclasses of UIResponder
nextResponder of a ViewModel is the related ViewController
- To send an action to the responder chain, the object could call
[[UIApplication sharedApplication] sendAction:action to:nil from:self forEvent:nil]
if the action takes no argument, and something like
id target = [self targetForAction:@selector(itemWasTapped:) withSender:self];
[target itemWasTapped:[self objectAtIndexPath:indexPath]];
if the action takes one (or more!) arguments. This is important because it lets us use the responder chain but also handle multiple arguments.
So why should we do this?
Think about the iPad app. If an item is tapped, the event will bubble up to the ViewController, but then? We don’t want to put interaction logic into the ViewController, because it could be reused in different screens or also in the same screen with different data and context. By sending an action to the responder chain, it will automatically be forwarded to the superview and the related ViewController, which in this case will be the FlowViewController. It is in my opinion a cleaner solution with as little dependencies as possible and it lets the developer handle events in a more powerful way than, let’s say, delegate objects.
Time for a sample
As I said, the ViewControllers are really lightweight here, so if we have a UITableView, its
delegate will be the ViewModel, in this case a ListViewModel class, inheriting from a BaseViewModel for the common behavior (and the
nextResponder implementation we talked about before).
Now the ListViewController doesn’t respond to
itemWasTapped:, so it will automatically bubble up to the FlowViewController and eventually to the FlowModel where we actually handle the tap, for example by instantiating a new ViewController – ViewModel instance, and pushing it on screen.
Adding a new feature
Let’s say we have a couple of features ready and we want to add a new one: in the left widget there will be a list of users, and tapping on a user shows the list of recipes he made, and tapping on a recipe eventually shows its details.
We can assume the ViewController / ViewModel pairs for the list of recipes and for a recipe details are already somewhere (Storyboard, XIB files), so we should only think about the following steps:
- Reuse the ListViewController and ListViewModel! (The data will be injected from the FlowModel)
- Create a UITableViewCell subclass to show users in a UITableView
- Create a UsersFlowModel to handle the interaction flow.
All the work will actually be done in the UsersFlowModel, where the data should be initialized (with a DataProvider, so from the web or from a database). Then we should add a
itemWasTapped: implementation to handle the taps on a user and on a recipe (or we can actually go forward and use blocks as selection handlers in the ListViewModel).
Where to go from here?
The architecture is far to be ready and it’s of course pretty rough. I didn’t have a lot of time to work on it and to engineer all the bits and details, and unfortunately I won’t be there to tinker with the bad solutions I’ve probably made.
Nonetheless, I feel quite happy with the results, and prototyping the app architecture and two feature flows has been pretty straightforward.
I think I will use this architecture also in the future apps I’ll develop, if it makes sense.
I don’t know what the next post will be about and when I’ll have the time to write one, since tomorrow I will start my new job at Quandoo, the week after I have my Combinatorial Optimization exam in Italy, and I still have to spend a lot of time on RNF, my runtime-based networking layer.
There is a chance that to speed things up a bit, I will hook the foundation bits of AFNetworking (namely the NSOperation subclass) to my library, to have a clear picture about how this could work. Then maybe I could write something about that. Stay tuned.