mvc

Architecture - View and Navigation Logic Abstraction

Views

Aside from pulling out protocol code and putting it into its own file (as covered in my most recent post), we can also pull out view code to reduce the size of our VCs. 

In a typical VC, say we are putting view setup code into our "viewDidLoad" method - we may instantiate several UI components and add them as subviews here. You may choose to create other methods that customize the subclassed UI components when called (which we do from viewDidLoad), ending the customization with adding the component as a subview. This may include using Visual Formatting Language to implement AutoLayout in code. See below:

class BloatedVC: UIViewController {

  private let restaurantName = UILabel()

  private let restaurantImage = UIImageView()

  private let blankSpaceSeparator = UIView()


  override func viewDidLoad() {

  super.viewDidLoad()

  view.addSubview(restaurantName)

  view.addSubview(restaurantImage)

  view.addSubview(blankSpaceSeparator)

  // add a ton of constraint code here, along with other customization including fram  e sizing, color, animations, etc.

  }
}

Here's a thought - instead of putting this code into a VC, why not factor it out into a subclass of UIView? We can have all the same code in that subclass, literally the exact same code, but instead of putting it all in our VC, we just create an instance of that new class in our VC and say

class BloatedVC: UIViewController {

  private var viewObject: ExtractedVCViewCode?

  override func loadView() {
  if let abstractedView = viewObject {
  self.view = abstractedView
    }
  }
}

It's important to note that we set up our view in "loadView", not "viewDidLoad". This is called by a VC when its view is currently nil, so you can instantiate the abstracted view in there.

The code for the abstracted view looks almost identical to the original code we had in "viewDidLoad":

class ExtractedVCViewCode: UIView {

  private let restaurantName = UILabel()
  private let restaurantImage = UIImageView()
  private let blankSpaceSeparator = UIView()

  struct LayoutConstants {
    static let padding: CGFloat = 15.0
    //other constants here for use in autolayout
  }

  override init(frame: CGRect) {
    super.init(frame:frame)
    addSubview(restaurantName)
    addSubview(restaurantImage)
    addSubview(blankSpaceSeparator)

    // add a ton of constraint code here, along with other customization including g     frame sizing, color, animations, etc.
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }
}

Navigation

Another quick (and to some, perhaps insignificant) way to reduce the amount of code in a VC is by separating out navigation logic - if a cell is clicked, instantiate this VC, pass in this restaurant as the data being provided, and present VC. This won't work for you if you're using IB segues (I prefer writing everything in code for many reasons unless I have a very simple idea I want to quickly prototype). 

First, we have two view controllers - the first displays a list of restaurants, the second displays information for one specific restaurant. We'll leave out typical navigation code, as it will be handled by a third class that neither VC knows about (yay for abstraction). Here's the gist of things - VC #1 declares a protocol with a method that takes a restaurant object as an argument - we also declare a weak var delegate that is of that delegate type. In "didSelectRowAtIndexPath", we invoke that method and say "whoever is implementing our delegate method, go ahead and execute now, here's the restaurant object you'll be needing". That's ALL THE CODE we have for navigation in our first VC.

The third class, that "navigation event handler", is what conforms to the protocol (the code below refers to this class as the "DefaultCoordinator"). This class is what will act as our delegate and actually execute our navigation code. VC #2 doesn't do anything in terms of navigation - it just exists here. The flow of things is VC #1 -> call method in navigation event handler and pass it restaurant object -> navigation event handler creates instance of VC #2, passes restaurant object over to it and pushes it onto the nav stack. Let's check out the code: 

class Restaurant: NSObject {
    var restoName: String
    var restoPic: UIImageView
    
    init (name: String, picture: UIImageView) {
        self.restoName = name
        self.restoPic = picture
        super.init()
    }
}

protocol RestaurantTableViewControllerDelegate:class {
    func RestoTableViewDidSelectRestaurant(restaurant: Restaurant)
}

class RestaurantListVC: UITableViewController {
    weak var navigationDelegate: RestaurantTableViewControllerDelegate?
    private var restaurants = [Restaurant] ()
    
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        let selectedResto = restaurants[indexPath.row]
        navigationDelegate?.RestoTableViewDidSelectRestaurant(selectedResto)
    }
}

class DefaultCoordinator: RestaurantTableViewControllerDelegate {
    weak var navigationController: UINavigationController?
    
    func RestoTableViewDidSelectRestaurant(restaurant: Restaurant) {
        let VC = SingleRestaurantVC (restaurant: restaurant)
        navigationController?.pushViewController(VC, animated: true)
    }
}

class SingleRestaurantVC: UITableViewController {
    private var singleRestaurant: Restaurant?
    
    init (restaurant: Restaurant) {
        self.singleRestaurant = restaurant
        super.init(style: .Plain)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

As you can see, the navigation code for the VC we are navigating away from is minimal. The DefaultCoordinator (which is acting as our navigation event handler) can conform to multiple protocols declared by multiple VCs - all navigation logic can be abstracted away and handled by the DefaultCoordinator. The (potential) downside is that once an app gets very complex with dozens of navigation options, it may be nicer to see the destination VC right there in the "from" VC (instead of tucked away in this coordinating class).

There are so many ways to reduce the amount of code in a VC - it's worth experimenting with. I find that it's easier for me to look at view controllers very conceptually instead of worrying about code involved - this lets me make up a sort of "map" in my head that allows for me to be more free-thinking in my architecture choices.

Architecture - MVC and MVVM

MVC - the typical architectural pattern for many web languages, Objective-C and Swift included. Separation of concerns exist nicely in this pattern - the most immediate problem we have is that Cocoa Touch has bundled our V and C together into a ViewController. Thus, we have view logic and "glue" logic all in one place. Any idea what this may lead to...? That's right! We end up creating a different type of MVC - a massive view controller!

Our views and controllers end up very tightly coupled, and a ton of logic is thrown into our VCs because we can't think of any other good place to put it. MVVM - Model, View, ViewModel, attempts to alleviate this by having a ViewModel act as the new "controller". This lets the ViewController class created by Apple to be JUST a view - no controller logic there. The controller is then truly factored out and acts as a link between the view (which is represented by a ViewController subclass) and our model. This architectural pattern relies on data binding between the view and the ViewModel - that is, when a change is triggered in the ViewModel (based on a change in the model), the changes should cascade down to the view through data binding - KVO, anyone?

This isn't really all that different from TRUE mvc - this is a problem, as far as I'm concerned. All we've done is create a new repository to dump things that we don't know where else to put - instead of putting this logic in a VC alongside view code, we lump it into the viewModel. Yeah, our ViewController classes have less code now and are more focused and testable, but instead of having bloated ViewControllers, we have bloated ViewModels. THIS IS NOT A COMPELLING REASON TO CHANGE ARCHITECTURE PATTERNS - the two good things I can say about MVVM are that 1. You own your ViewModel, whereas you were previously putting a lot of logic into a ViewController (which is created by Apple and not you) and 2. ViewControllers are more testable with this approach. 

What, then, can we do to truly improve our architecture? For one, create as many classes as you need to make sure that each file has single (or minimal) responsibility. Don't have a class called "BicycleVC" - instead, break that down into "BicyclePartsVC", "BicycleMaintenanceVC", etc. You don't even have to break it down into ViewControllers! Your files can just be regular objects that are used within another file, a controller somewhere else. By breaking down your files and naming them appropriately, all the while obsessively making sure that no class has too much responsibility, you greatly improve your architecture. No single class, not even a controller, will be saddled with too much responsibility and thus won't be saddled with too much code. 

ANY OTHER DEVELOPER SHOULD BE ABLE TO LOOK AT YOUR CLASS NAMES AND, WITHOUT LOOKING AT THE SOURCE CODE, DETERMINE WHAT EACH CLASS DOES AND HOW THE "PIECES FIT TOGETHER". 

Lots of data source code in a TableViewController subclass you're using? Factor it out. Lot's of networking code that encompasses multiple services being lobbed into your VC? Factor it out into several smaller classes, and, if it suits you, use a facade class to allow access to those (private, hopefully) internal APIs. When you think about it, a ViewModel is essentially a facade class used to access the model that is powering your software. Focus less on popular patterns and more on modularizing your classes, forcing minimal responsibility onto each class, and compulsively giving your files detailed names.