Designing for change

· 3 minute read

The Wikipedia entry for technical debt starts by defining it as:

Work that needs to be done before a particular job can be considered complete or proper. If the debt is not repaid, then it will keep on accumulating interest, making it hard to implement changes later on.

And goes on to state that:

Analogous to monetary debt, technical debt is not necessarily a bad thing, and sometimes technical debt is required to move projects forward.

This is a great way to put it; debt isn’t inherently bad if it yields important short-term benefits that wouldn’t have otherwise been achievable. Ensuring those benefits are reaped by choosing the right kinds of debt is crucial, however.

Over the past two months, I’ve been working on a brand new company. At this juncture, it’s easy to think that one shouldn’t be concerned about accumulating technical debt for quite some time. That for a while, raw speed is more important than doing everything “correctly.” That to be burdened by technical debt would be a good problem to have, implying that you’ve found a meaningful foothold after launching. That if your 1.0 is too technically sound, you weren’t moving fast enough.

I do think there’s some truth to this, but of course it isn’t so black and white. You absolutely shouldn’t concern yourself with certain kinds of technical debt while racing towards your initial launch, with so many questions still swirling around value proposition and product/market fit. Maybe you have a class whose implementation consists solely of one giant method. It works, seemingly, but its logic is hard to understand and even harder to modify. Test coverage isn’t great and you’re sure there’re edge cases that don’t work as expected, that’d be apparent if you just spent some time breaking it down into a number of smaller methods.

This is the kind of technical debt that I have no problem introducing into my codebase at this early stage. Technical debt might be worth introducing early only if it’s going to make you faster in the short-term. But specific types of debt do exactly the opposite.

One common form of technical debt is building components that are more tightly coupled together than they should be. Perhaps your view controllers each know about the next view controller that is to be displayed1. Maybe various parts of the codebase are all intimately aware that Core Data is being used for your persistence layer. This makes it prohibitively difficult to make rapid changes, specifically at the time when codebases tend to undergo the most: during their infancy.

Maybe we’re just using NSUserDefaults for now, since it’s so easy. Now we’ve graduated to a simple NSCoding-based cache. Soon we’re ready for a full on database like YapDB or Realm or Core Data. Let’s say that your onboarding flow is comprised of six different screens. Now it’s comprised of five. Now those same five in a different order. This type of churn is to be expected in a young codebase. Today you’re using Alamofire but tomorrow you might not be.

These are logical progressions for a new application to go through, and developing “correctly” by keeping these boundaries loosely coupled will facilitate making changes when it’s most important to do so. Established codebases don’t switch persistence mechanisms three times over the course of two months, but a new one very well might. As such, a decision that’d make a change like this overly difficult is exactly the type of debt that I won’t tolerate no matter how fast I‘m supposed to be moving.

Properly architecting may seem like it’s going to be more time consuming, but in practice, it won’t, as long as you’re investing in the right approach. For a new codebase, I can’t think of a tradeoff more important than keeping your components decoupled rather than worrying too much about their implementations. Design for change when your code is going to be at its most volatile.

  1. If afflicted by this particular problem, meet coordinators

Properties vs. methods

· 4 minute read

Here’s a potentially controversial thought that keeps coming back to me as I spend more and more time developing in Swift: I don’t think that properties should be part of a type’s public interface.

The Swift Programming Language states that “Properties associate values with a particular class, structure, or enumeration.” This is all well and good, since associating types with values is not functionality that could otherwise be provided by methods alone. I certainly don’t have a problem with using properties to implement a type. I’m solely bothered by this implementation detail subsequently leaking out into a type’s API. Let’s take a look at an excerpt from String’s public interface:

public struct String {
  public var lowercaseString: String { get }
}

From this, a caller knows that stringInstance.lowercaseString can be called without parentheses. On the other hand, the standard library authors could’ve just as easily implemented String as follows:

public struct String {
  public func lowercaseString() -> String
}

In which case, we’d call stringInstance.lowercaseString() to achieve the same result. But why on Earth should a caller need to concern themselves with this distinction?

Over the years, I’ve heard many variations on the following: properties are for accessing instance variables, while methods are for performing actions. I have two major problems with this line of thinking:

  • If properties are for accessing instance variables, having them be part of a class’s public API provides unnecessary transparency into how that class is implemented, violating encapsulation.
  • We all know full well that properties aren’t just for accessing instance variables. Computed properties were and are commonly used in Objective-C. Swift even perpetuates this further by providing first-class language support for them1.

While Swift eschewed Objective-C’s brackets in favor of dot-syntax for everything, it didn’t actually shake having different invocation syntaxes for properties and methods. I wish it had.

Consistency when calling a property accessor or a method would be beneficial for other reasons as well. Every zero-argument method could be implemented as a property, but adding an argument would then change the call site from foo.bar to foo.bar(argument: baz) instead of from foo.bar() to foo.bar(argument: baz). Isn’t that kind of weird?

Instance methods can also be curried, and in fact, are already curried class methods. You can get a handle to them as follows:

struct SomeType {
  func doSomething() {
      // ...
  }
}

let somethingDoer = SomeType.doSomething

You can’t do this with properties though, meaning you can’t employ tricks like this with higher-order functions, the way that you could if they were argument-less methods.

Properties provide a hugely beneficial, expressive way to instruct the compiler how to generate boilerplate accessors and mutators for you. Swift expands on this by providing a whole slew of additional ways to not only define them, but also hook into changes being made2. I’m really glad I don’t need to implement all of this myself.

At the same time, I can’t help but feel like Swift is missing an opportunity to provide a conceptually simpler programming model by removing this distinction from its types’ public interfaces.

  1. Calling Objective-C methods like -[NSArray count] using dot-notation was a surefire way to be chided, due to count not having been a property. Then Apple changed count to be a property instead of a method, ostensibly because array.count is nicer than [array count]. In the process, I’m assuming they didn’t stop computing the count in favor of storing it in an instance variable. 

  2. As an aside, I do worry that properties are overused as a result of how nice it is to define them, syntactically. It’s incredibly tempting to have a user property whose didSet block updates a persistent store and notifies a delegate, when a method named something like persistUserAndUpdateDelegate: is far more informative to someone reading the call site. I like these two posts on the subject matter. 

Navigation coordinators

· 10 minute read

Coordinators are an iOS design pattern that I’ve become a big fan of over the past few months. You should read Soroush’s post introducing them (and watch his conference talk as well), but in short, coordinators allow you to keep your view controllers isolated and reusable by preventing any one controller from knowing about any other. Each controller has its own delegate protocol, and a separate coordinator object acts as all of their delegates, performing all of the requisite routing. This makes it trivial to change the order in which view controllers are displayed, add support for new device classes, and so on and so forth.

One problem that I’ve been trying to work around is how coordinators are to be effectively used when a navigation controller is involved. Here’s an example in which we encapsulate an application’s signup flow inside of an “account creation coordinator.” You can envision a user tapping a Sign up button and being presented with a modal flow for creating an account. At the call site, that code would look like:

func signUpButtonTapped() {
  let accountCreationCoordinator = AccountCreationCoordinator(rootViewController:
      currentViewController, delegate: self)
  accountCreationCoordinator.start()

  /*
   We should assume that every coordinator may eventually maintain an array of
   child coordinators, which allows us to nest them and ensure that everything
   stays retained as long as it needs to be.
   */
  childCoordinators.append(accountCreationCoordinator)
}

The coordinator implementation would start out something like this:

final class AccountCreationCoordinator {
  // MARK: - Inputs

  private let rootViewController: UIViewController
  private weak var delegate: AccountCreationCoordinatorDelegate?

  // MARK: - Mutable state

  private let storage = Storage()

  private lazy var navigationController: UINavigationController = UINavigationController(
    UserNameAndPasswordViewController(delegate: self))

  // MARK: - Initialization

  init(rootViewController: UIViewController, delegate: AccountCreationCoordinatorDelegate) {
    self.rootViewController = rootViewController
  }

  // MARK: - Coordinator

  func start() {
    rootViewController.presentViewController(navigationController, animated: false)
  }
}

// MARK: - UserNameAndPasswordViewControllerDelegate

extension AccountCreationCoordinator: UserNameAndPasswordViewControllerDelegate {
  func userNameAndPasswordViewController(viewController: UserNameAndPasswordViewController,
      didSubmitCredentials credentials: Credentials) {
    storage.credentials = credentials

    // Next: Push a view controller for selecting an avatar
  }
}

It’d be easy enough to simply push an AvatarSelectionViewController onto the navigation controller, but let’s think about this a bit more. Avatar selection likely requires integrating with a UIImagePickerController, and perhaps even third-party APIs like Facebook or Twitter. To include this integration code inside of the AvatarSelectionViewController would be to go against the spirit of coordinators. We could put all of that code right here, inside of the AccountCreationCoordinator, but then that code wouldn’t be as reusable. Once they’ve registered, we probably want to allow our user to modify the avatar from somewhere inside of the application as well, so the best course of action here is to package all of this code up into an AvatarSelectionCoordinator, and have that coordinator be a child of our AccountCreationCoordinator.

extension AccountCreationCoordinator: UserNameAndPasswordViewControllerDelegate {
  func userNameAndPasswordViewController(viewController: UserNameAndPasswordViewController,
      didSubmitCredentials credentials: Credentials) {
    storage.credentials = credentials

    let avatarSelectionCoordinator = AvatarSelectionCoordinator(delegate: self)
    childCoordinators.append(avatarSelectionCoordinator)

    navigationController.pushViewController(avatarSelectionCoordinator.rootViewController,
      animated: true)
  }
}

(You’ll notice that I’ve called rootViewController on avatarSelectionCoordinator, instead of calling a start method. Exposing a read-only root view controller is useful for integrating coordinators in a variety of different places, navigation controllers being one but also when configuring your window’s rootViewController in your application delegate.)

The next step would be for our AccountCreationCoordinator to implement the AvatarSelectionCoordinatorDelegate protocol:

extension AccountCreationCoordinator: AvatarSelectionCoordinatorDelegate {
  func avatarSelectionCoordinator(coordinator: AvatarSelectionCoordinator,
      didSubmitImage image: UIImage) {
    storage.avatar = image

    childCoordinators.remove(coordinator)

    // Next: Push the next view controller in the flow
  }
}

Here’s the problem: while this works great so long as the user keeps moving forward through our onboarding flow, what would happen if they tapped the navigation controller’s back button? The AvatarSelectionCoordinator would still be retained by the childCoordinators array, and another AvatarSelectionCoordinator would end up being added to the same array if the user was to submit the username and password form again. Not good.

One solution would be to have AccountCreationCoordinator monitor the navigation controller’s view controller stack, and ensure that a coordinator is cleaned up if its root view controller is no longer included. This would involve:

  • Having AccountCreationCoordinator conform to UINavigationControllerDelegate
  • Having AccountCreationCoordinator maintain a mapping between root view controllers and their respective coordinators

This is fairly straightforward, but this isn’t account creation-specific behavior. We’ll want this same behavior everywhere in our application where coordinators might come into contact with navigation controllers.

When trying to come up with a more elegant solution, I found myself drawing inspiration from a quote from Soroush’s original coordinator doctrine:

You’re not sitting around waiting for -viewDidLoad to get called so you can do work, you’re totally in control of the show. There’s no invisible code in a UIViewController superclass that is doing some magic that you don’t understand. Instead of being called, you start doing the calling.

Flipping this model makes it much easier to understand what’s going on. The behavior of your app is a completely transparent to you, and UIKit is now just a library that you call when you want to use it.

Instead of having our account coordinator be called, let’s see if we can’t instead call something that treats UINavigationController as a library.

protocol RootViewControllerProvider: class {
  var rootViewController: UIViewController { get }
}

typealias RootCoordinator = protocol<Coordinator, RootViewControllerProvider>

final class NavigationController: UIViewController {
  // MARK: - Inputs

  private let rootViewController: UIViewController

  // MARK: - Mutable state

  private var viewControllersToChildCoordinators: [UIViewController: Coordinator] = [:]

  // MARK: - Lazy views

  private lazy var childNavigationController: UINavigationController =
      UINavigationController(rootViewController: self.rootViewController)

  // MARK: - Initialization

  init(rootViewController: UIViewController) {
    self.rootViewController = rootViewController

    super.init(nibName: nil, bundle: nil)
  }

  // MARK: - UIViewController

  override func viewDidLoad() {
    super.viewDidLoad()

    childNavigationController.delegate = self
    childNavigationController.interactivePopGestureRecognizer?.delegate = self

    addChildViewController(childNavigationController)
    view.addSubview(childNavigationController.view)
    childNavigationController.didMoveToParentViewController(self)

    childNavigationController.view.translatesAutoresizingMaskIntoConstraints = false

    NSLayoutConstraint.activateConstraints([
      childNavigationController.view.topAnchor.constraintEqualToAnchor(view.topAnchor),
      childNavigationController.view.leftAnchor.constraintEqualToAnchor(view.leftAnchor),
      childNavigationController.view.rightAnchor.constraintEqualToAnchor(view.rightAnchor),
      childNavigationController.view.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)
    ])
  }

  // MARK: - Public

  func pushCoordinator(coordinator: RootCoordinator, animated: Bool) {
    viewControllersToChildCoordinators[coordinator.rootViewController] = coordinator

    pushViewController(coordinator.rootViewController, animated: animated)
  }

  func pushViewController(viewController: UIViewController, animated: Bool) {
    childNavigationController.pushViewController(viewController, animated: animated)
  }
}

// MARK: - UIGestureRecognizerDelegate

extension NavigationController: UIGestureRecognizerDelegate {
  func gestureRecognizer(gestureRecognizer: UIGestureRecognizer,
      shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    // Necessary to get the child navigation controller’s interactive pop gesture recognizer to work.
    return true
  }
}

// MARK: - UINavigationControllerDelegate

extension NavigationController: UINavigationControllerDelegate {    
  func navigationController(navigationController: UINavigationController,
      didShowViewController viewController: UIViewController, animated: Bool) {
    cleanUpChildCoordinators()
  }

  // MARK: - Private

  private func cleanUpChildCoordinators() {
    for viewController in viewControllersToChildCoordinators.keys {
      if !childNavigationController.viewControllers.contains(viewController) {
          viewControllersToChildCoordinators.removeValueForKey(viewController)
      }
    }
  }
}

This NavigationController class can be used in conjunction with coordinators throughout our application, making it easy for us to compose coordinators inside one another for maximum reusability.

UIViewController composition is a really powerful pattern that I’ve been using to great effect in a new project I’ve been working on. Yes, it inherently requires more boilerplate plumbing. For example, my NavigationController above only exposes the ability to push a new view controller onto the navigation stack; none of the other public methods or properties that classes interacting directly with a UINavigationController instance would be able to call are available. I think this is a worthwhile tradeoff, however. It’s not a huge deal to proxy some method calls through if I want my custom class to expose more of UINavigationController’s capabilities, and in the meantime, I’m benefiting from exposing a minimal surface area with very little mutability. If I ever want to do something more elaborate, like implement my own replacement for UINavigationController, I wouldn’t need to make code changes at any of NavigationController’s call sites.

While being diligent about using coordinators to route between view controllers has been a valuable exercise, there have been cases like this one where it would’ve been easy to succumb to how UIKit tends to imply ones code should be structured. The original thinking behind coordinators was that your application’s flow should be modeled by your own objects, with `UIKit` components serving as an implementation detail wherever possible. It shouldn’t be too surprising that going back to that original mantra led to exactly the solution that I needed in this instance. I presume that’ll continue to be the case in the future.