Blog

Latest Post

Swift Tip: Separating UI and Model Code

Our current Swift Talk series looks at different techniques to refactor a large view controller, using an example from the open source Wikipedia iOS app.

In the first three episodes, we extract pure functions, model code, and networking-related code. You may notice a common theme to all these refactorings: in each episode we try to separate the code which updates the UI from the code that provides the data for updating the UI.

For example, in last week’s episode we start with a method like this:

class PlacesViewController: UIViewController {
    func updateSearchCompletionsFromSearchBarTextForTopArticles() {
        // ...
    }
}

This method is called whenever the text in the search bar changes. It performs these main tasks:

  • Request search completions from Wikipedia for the current search term
  • Transform the results to the type needed to drive the UI
  • Request location-based completions if the first request doesn’t return enough results
  • Transform the second batch of results
  • Update the UI with the completions after each request
  • Control the state of a progress bar at various success and failure points

The first thing we do is to separate this method into two; one method deals with the logic for search completion fetching, and the other only updates the UI:

func updateSearchCompletionsFromSearchBarTextForTopArticles() {
    fetchSearchSuggestions(for: searchBar?.text ?? "") { result, done in
        // Update UI
    }
}

func fetchSearchSuggestions(for searchTerm: String, completion: @escaping ([PlaceSearch], Bool) -> ()) {
    // ...
}

To prevent breaking any other code in the view controller, we keep the existing method signature. However, the original method now only deals with updating the UI in response to search suggestions; all the fetching logic is handled by the second method, which uses a completion handler to asynchronously report its results.

With this separation in place, we can easily move the non-UI method out of the view controller — for example into a model or a service class.

You can watch the full refactoring in Swift Talk #105.

We often find this approach very helpful for writing maintainable view controllers. When we refactor, we always try to pull out code from a view controller that fetches, transforms, or prepares data. The view controller should be left with the task of updating the views.

 

Previous Posts

Swift Tip: Quick Performance Timing

When we want to quickly test the performance of some code, we paste the following function into our project:

@discardableResult
func measure<A>(name: String = "", _ block: () -> A) -> A {
    let startTime = CACurrentMediaTime()
    let result = block()
    let timeElapsed = CACurrentMediaTime() - startTime
    print("Time: \(name) - \(timeElapsed)")
    return result
}

The above helper measures the execution time of the function we pass in, and prints it out to the console. Because it’s generic over the result, we can use it to measure single expressions:

let result = measure { (0..<1_000_000).reduce(0, +) }

Or we can use it to to measure the time of a method call:

measure {
    computeLayout()
}

For real performance testing, we like to use Instruments (the new os_signpost API looks like a great addition for exactly these kinds of things) or XCTest’s measure, but when all we need is a quick check, the above snippet is really useful.

For your own code, there are many variations you could write: include the line number and name of the calling function, timing asynchronous calls, use more precise logging, and so on.

 

Swift Tip: Extracting Pure Functions

In Swift Talk #103, Refactoring Large View Controllers (a public episode), we extract two pure functions out of an existing view controller. We chose to use the Wikipedia iOS app because it’s a large, well written, and open-source application.

In the PlacesViewController, there is a method that computes whether or not two regions are significantly close to each other: isDistanceSignificant(betweenRegion:andRegion:)

class PlacesViewController /* ... */ {
    func isDistanceSignificant(betweenRegion searchRegion: MKCoordinateRegion,
        andRegion visibleRegion: MKCoordinateRegion) -> Bool {
        // ...
    }
}

If we look at the method signature in isolation, we can’t determine whether it uses the state of PlacesViewController. Fortunately, in this case it doesn’t, so we can move it out of the class and it will still work.

By moving the method out of the view controller, we make it completely obvious that it is independent of any state in the view controller. We can now test it in isolation, without having to instantiate the view controller (and its many dependencies).

To test it, we pass in the required parameters and verify the result. When you have a pure function like isDistanceSignificant(between:andRegion:), this is all you need to do for a test, there is no state to set up.

In MVC, the view controller has a lot of state and dependencies: the model, the view hierarchy, and all of its own properties. This makes code inside the view controller both difficult and slow to test, regardless of whether you’re testing manually or with unit tests.

Locating functions that can be extracted from the view controller can make those functions much easier to test, and you’ll be making your view controller just that little bit smaller — a win-win. 😊

For a second example, keep watching! We go on to refactor a method that adjusts the frame of a subview, and extract almost all its code into another pure function.

Thanks to the support of our subscribers, the first episode in this series is public. To follow the rest of this series, become a subscriber.

Swift Tip: Type-Safe Initialization Using Storyboards (Part 2)

In last week’s Swift Tip, we showed a simple way to configure view controllers by adding a configure method:

func configure(context: NSManagedObjectContext, recording: Recording) {
    self.context = context
    self.recording = recording
}

In our new book, App Architecture, we show an alternative approach. In the MVVM-C implementation, we use Storyboards to lay out the view controllers, but provide static wrapper methods that construct the view controllers with the required parameters:

extension UIStoryboard {
    func instantiatePlayerNavigationController(with recording: Recording) -> UINavigationController {
        let playerNC = instantiateViewController(withIdentifier: "playerNavigationController") as! UINavigationController
        let playerVC = playerNC.viewControllers[0] as! PlayViewController
        playerVC.viewModel.recording.value = recording
        return playerNC
    }
}

Instead of segues, we move the logic into a coordinator class, and manually trigger presentation:

extension Coordinator: FolderViewControllerDelegate {
    func didSelect(_ recording: Recording) {
        let playerNC = storyboard.instantiatePlayerNavigationController(with: recording)
        splitViewController.showDetailViewController(playerNC, sender: self)
    }
}

This makes it even harder to make a mistake when presenting view controllers. Instead of using segues, we can ensure that all parameters are present upon construction.

Read our book App Architecture for a full description of the technique, or watch Swift Talk Episode 5 for a demonstration of a similar technique.

Swift Tip: Type-Safe Initialization using Storyboards (Part 1)

Love them or hate them, Storyboards are used in many iOS projects. In this week’s Swift Tip, we look at a simple way to make them more robust.

When you present a new view controller using Storyboards, it’s usually a multi-step process: first, the segue is triggered, and at a later step, you configure the segue. This makes it easy to miss configuration code.

For example, in the code below, we set the two properties that the PlayViewController needs:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "showDetail",
        let playVC = segue.destination as? PlayViewController {
        playVC.context = self.managedObjectContext
        playVC.recording = selectedRecording
    }
}

It’s easy to forget one of the above properties, because it’s often not clear which properties are required, and because view controllers typically have many stored properties. What’s more, when you’re in the RootViewController, you don’t always have all the details of PlayViewController to hand. Because of the multi-step initialization process, we can’t put this in the initializer either (the PlayViewController has already been constructed by the time we end up in prepare(for:sender:)).

As an improvement, we can adopt the convention that each view controller has a configure method which takes the necessary parameters:

func configure(context: NSManagedObjectContext, recording: Recording) {
    self.context = context
    self.recording = recording
}

Now, in prepare(for:sender:) you can rely on autocomplete and the compiler to make sure you provide all the necessary parameters:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "showDetail",
        let playVC = segue.destination as? PlayViewController {
        playVC.configure(context: self.managedObjectContext, recording: selectedRecording)
    }
}

This approach is much more robust when refactoring; for example, we can add another required parameter to the configure method, and the compiler will indicate all the places we need to change. As long as we are disciplined about calling configure rather than setting the properties manually, we’re safe.

In Part 2, we show an alternative approach taken from our new book, App Architecture. In the MVVM-C implementation, we use Storyboards to lay out the view controllers, but provide static wrapper methods that construct the view controllers with the required parameters. Instead of segues, we move the logic into a coordinator class, and manually trigger presentation.

We demonstrate a similar refactoring in Swift Talk Episode 5.