Massive view controller / data source

When I first started developing on Apple’s platforms (around april 2005), I was immediately impressed by strong architectural patterns embedded into core frameworks. It was part of that attention to detail Apple was and still is notorious for. It spreads into everything they do, from hardware, to software as well as SDK under the hood. In this blog post I’ll talk about one of those patterns and more specifically - problems it may bring and how to deal with them.

Software design patterns & MVC

There are many software OOP design patterns existing as blueprints and used by developers worldwide. Perhaps one of the most known collections of patterns is the “Gang of Four” book. Regardless of origin, the main driving force behind design patterns is to allow developers to design decoupled software systems. While that might not seem like a big deal, decoupled in software directly translates into simple to extend and maintain when used correctly. And this is becoming more and more important in the trend of increasingly more complex applications.

While there are many design patterns, I’ll take closer look into MVC this time. MVC or Model View Controller is actually a combination of several other patterns. The basic idea is to separate code responsibility into three distinct layers: model or business layer which manages data that’s being displayed by view layer. And there’s controller layer that mediates the data between the two. It’s great pattern for decoupling components and is easily followed as it’s permeated throughout both AppKit and UIKit frameworks in the form of NSViewController and UIViewController.

Dark side of the Moon… er… controller

However there’s dark side to it: all too soon, our poor view controllers become a dump yard of delegate and data source methods as well as a host of other more or less appropriate content. In all that mess, their primary role - mediator between model and view - gets lots. And MVC turns into MVC - Model View Controller into Massive View Controller… Let’s be honest: most of the time the only reason for ending up with massive view controller is simply because it’s so convenient place to add all that stuff to. Regardless of reason, how do we deal with it?

Data source

Data source implementation - UITableViewDataSource, UICollectionViewDataSource, NSTableViewDataSource, NSOutlineViewDataSource and similar - is probably one of the first responsibilities our shiny brand new view controller gets assigned to. At this early stage there’s nothing wrong with it, but that may just as well be the first step towards massive view controller. So what can we do with it?

Let’s take a short deviation before dealing with answer to above question. When first learning about object oriented programming, most of us get hooked to idea of inheritance. It is arguably one of the most powerful qualities, but often, or at least at the beginning, way too overused, often turning our class structure into single big monolithic tree. Actually, as it turns out, one of the most efficient ways of promoting code reuse and decoupling is composition. Keeping up with that tree comparison - it helps turn our code base into a forest of trees with many shallow class hierarchies. And as you’ll see soon, that’s the key to fixing massive view controller!

Typical implementation in view controller

Here’s minimal data source implementation for UITableView:

class MyViewController: UIViewController, UITableViewDataSource {
	
    func viewDidLoad() {
        self.items = [ "a", "b", "c" ]
        self.tableView.dataSource = self
    }
    
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.items.count
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let item = self.items[indexPath.row]
        let result = tableView.dequeueReusableCellWithIdentifier("identifier", forIndexPath: indexPath)
        result.textLabel.text = item
        return result
    }
    
    lazy var items = [String]()
    
    @IBOutlet var tableView: UITableView!
}

Refactor into separate data source class

Steps at a glance:

  1. Create separate class for handling data source.
  2. Move methods from view controller into this class.
  3. Assign the class as data source.

Let’s first take a look at our new data source class:

class MyDataSource: UITableViewDataSource {
	
    init(tableView: UITableView) {
        self.items = [ "a", "b", "c" ]
        self.tableView = tableView
    }
	
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.items.count
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let item = self.items[indexPath.row]
        let result = tableView.dequeueReusableCellWithIdentifier("identifier", forIndexPath: indexPath)
        result.textLabel.text = item
        return result
    }
    
    private (set) internal var items: [String]
    private var tableView: UITableView
}

As you can see, UITableViewDataSource methods are now located inside this class, together with items array. Most often than not, items array needs to be accessed from outside data source class. For example to determine selected items or measure cells. Therefore I made items property as readonly for external but read write for internal access. But other than that, the implementation is straightforward.

And here’s our stripped down view controller:

class MyViewController: UIViewController, UITableViewDataSource {
	
    func viewDidLoad() {
        self.dataSource = MyDataSource(tableView: self.tableView)
        self.tableView.dataSource = self.dataSource
    }

    private lazy var dataSource: MyDataSource! = nil
    
    @IBOutlet var tableView: UITableView!
}

Results & implications

What did we achieve? Sure there are (couple) more lines of code and it may seem like an overkill for this simple example, but many data sources are much more involved which also makes the refactor more worth it. What is more important: responsibilities are now clearly separated. This makes it simpler to unit test both components for example. However there are other benefits: we can reuse the same data source class on other view controllers. Or we can have multiple data sources for handling different states or filters on the same view controller - just assign new data source to table view as needed and reload content.

There may be drawbacks to using external data source: sometimes it may lead to more coupling between the two classes than you’d like. For example there may be notifications coming from the model you’d need to handle on both sides. Or need to share parts of code between them. Both are real concerns but should also be relatively simple to solve. Exact solution depends on concrete situation and may range from external class that provides common methods, converting data source classes into hierarchy of classes (many times combined with template method pattern), or simply making one object “master” and having responsibility to inform the other about changes that are required on both.

With that in mind - no need to rush and extract data source by all means. However as view controllers start growing, extracting data source may prove to be useful and versatile swiss army knife to keep in your pocket. Most of the time benefits far outweigh potential annoyances.

Pros:

  • More manageable view controller code.
  • Clearer separation of responsibilities.
  • Simpler data source reuse between controllers.
  • Multiple data sources on the same controller.
  • Simpler to unit test both components.

Cons:

  • Potential code coupling on controller and data source class.
  • May require setting up data sharing between both classes.

When to use:

  • View controllers code becomes unmanageable.
  • Reuse data source on multiple view controllers.
  • Multiple data sources on single view controller.

Conclusion

Data source pattern is pervasive in Cocoa. It allows versatile, sometimes even surprising implementations while using the same familiar API. However it may lead to bloated view controllers. And when it does, it’s simple to extract it to separate class. The solution brings some complexity in the form of additional classes, however it also results in better separation of concerns. However, as said above, no need to rush; pay attention to the flow of your view controllers and extract only when needed.

Download sample code from GitHub. Next time, I’ll look in other ways to improve controller code.

PS It’s been a long time since I posted technical article on my blog. These types of articles demand more time and that is somewhat of a scarce resource lately :) Regardless I made a resolution to make my blog more useful to fellow iOS and Mac developers again. This is just the first in series of articles on software architecture and refactor. I have couple related topics on my mind, but if you have specific question, or code you’d like me to discuss, feel free to use links below to contact me!



Want to reach us? Fill in the contact form, or poke us on Twitter.