---
title: 'Introducing Perform: Easy dependency injection for storyboard segues'
teaser: 'How to use Perform to simplify your code and make Dependency Injection great
  again.

  '
tags: ios,swift,perform,open source,news
author: Adam Sharp
published_on: 2016-09-13
---

We're big fans of building UIs in Interface Builder with Storyboards. But
unfortunately, when it comes to passing data around your app, they sometimes
fall short.

We wrote [Perform][] to help us solve the problems we were having using
[Dependency Injection][DI] with Storyboards and Segues.

  [DI]: https://www.objc.io/issues/15-testing/dependency-injection/

## The problem with `prepare(for:sender:)`

(Known as `prepareForSegue(_:sender:)` in Swift 2.)

Imagine you're writing an iOS app that displays an activity feed. You begin
with a simple `UITableViewController` subclass, and the only type of cell is a
status update. When a status update is tapped, you push a detail screen
showing the conversation on that post.

Here's how you pass data along to the conversation screen:

```swift
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  viewModel.selectStatus(at: indexPath)
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  if let conversation = segue.destinationViewController as? ConversationViewController {
    conversation.status = viewModel.selectedStatus
    viewModel.clearSelection()
  }
}
```

There are some things about this code you find problematic:

- The code to show the conversation screen is spread over two methods.
- You're keeping track of extra state so that in `prepare(for:sender:)` you can
  pass along the selected status.
- It's not obvious, but `ConversationViewController.status` is of type
  `Status!`, and `viewModel.selectedStatus` is `Status?`. If `selectStatus`
  was unexpectedly `nil`, you won't know until some code in
  `ConversationViewController` causes a crash.
- If the type of the destination view controller changes, this code will
  silently fail, likely causing another surprising crash.

However, it works! You move onto the next feature: sharing a status. You add a
"Share" button to the cell, which presents a modal compose screen so the user
can type a custom message before posting to their own feed.

```swift
@IBAction func share(sender: Any?) {
  // `indexPath(for:in:)` is a helper to find the index path of a subview
  // inside a table view cell
  guard let indexPath = indexPath(for: sender, in: tableView) else { return }

  viewModel.selectStatus(at: indexPath)
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  // other segue handlers...

  if let nav = segue.destinationViewController as? UINavigationController,
    let share = nav.topViewController as? ShareStatusViewController
  {
    share.sharedStatus = viewModel.selectedStatus
    viewModel.clearSelection()
  }
}
```

This method is starting to get long, and now there are a couple of new problems:

- Because the modal compose view controller is inside a
  `UINavigationController`, we have to perform an intermediate cast just so we
  can access its `topViewController`.
- We've reused `viewModel.selectedStatus`. What if the value is actually a
  stale selection that we forgot to clear? There's not really any way to know,
  and that would be really challenging to debug.

## How Perform solves these problems

Let's refactor this code to use Perform. The first step is to declare all the
information about our segues in one place, using Perform's `Segue` type.

```swift
import Perform

extension Segue {
  static var showConversation: Segue<ConversationViewController> {
    return .init(identifier: "ShowConversation")
  }

  static var shareStatus: Segue<ShareStatusViewController> {
    return .init(identifier: "ShareStatus")
  }
}
```

`Segue` is a very simple type --- here's the entire definition:

```swift
public struct Segue<Destination: UIViewController> {
  public let identifier: String

  public init(identifier: String) {
    self.identifier = identifier
  }
}
```

Its sole property is the segue identifier, and it lets us specify the expected
type of the `Destination` view controller.

With our segues defined, we can now use the `perform(_:prepare:)` method to
initiate the segue _and_ prepare the destination view controller in one step ---
by just passing in a function or closure:

```swift
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  let status = viewModel.status(at: indexPath)

  perform(.showConversation) { conversationVC in
    // conversationVC has already been cast to the correct type
    conversationVC.status = status
  }
}
```

While we're at it, let's fix sharing, too:

```swift
@IBAction func share(sender: Any?) {
  guard let indexPath = indexPath(for: sender, in: tableView) else { return }
  let status = viewModel.status(at: indexPath)

  perform(.shareStatus) { shareVC in
    // Perform automatically extracted shareVC from the navigation controller!
    shareVC.sharedStatus = status
  }
}
```

That's it! We can now **delete our entire implementation of `prepare(for:sender:)`.**
Not to mention all the code to manage selections!

### How does it work?

Perform uses the excellent [Aspects][] library to dynamically implement
`prepare(for:sender:)`; it then locates and casts the destination view
controller, and passes it along to your `prepare` function.

  [Aspects]: https://github.com/steipete/Aspects

## Inject your dependencies with confidence

Storyboards and segues aren't perfect, but they can be a really valuable tool
for building your app's UI. And with just a little help, we can remove most
of the pain of passing data around between view controllers.

Check out Perform [on GitHub][Perform].

  [Perform]: https://github.com/thoughtbot/Perform
