Improved routing and data passing

Routing in Clean Swift is the one thing that I wanted to improve the most from the previous version. I’ll walk through a specific, concrete use case first, because it’s easier to read and understand when given a specific context. Then, I’ll generalize it to make it useful for any route in your app.

The specific use case we’ll look at is routing from the ListOrders scene to the ShowOrder scene when the user taps on an order in the table view.

The old way of passing data

While the old way of passing data worked, it didn’t make me proud when writing:

In the above code, we first get the selectedIndexPath of the table view. Next, we retrieve the selectedOrder from the orders array. Finally, we get the destinationViewController from the segue, cast it to the proper UIViewController subclass. The actual data passing is done by setting order to selectedOrder.

This works but there are a couple annoyances.

Having to go through the output

The router doesn’t have a direct reference to the output, the interactor. It only has a reference to the viewController:

In order to get to the orders array defined in the interactor, we have to write viewController.output.orders. Likewise, to get to the order variable, we have to write showOrderViewController.output.order.

We could add a direct reference to the interactor in the router. But I don’t like that. If every time we need access to something, we just add that something, it’ll soon turn into a monolithic class. A class having direct access to many things is destined for some serious abuse. This is the kind of ad-hoc programming that we absolutely want to stay away from.

Having to know the exact type of the destination view controller

We also need to cast segue.destinationViewController to ShowOrderViewController first. All the methods about presenting other view controllers in the UIViewController class reference don’t need to know the specific class type. The destination can be any UIViewController subclass.

Apple’s sample code does this type cast only because it uses the MVC pattern and stores data right in the view controller itself. But in Clean Swift, we don’t store data in the view controller. We store data in the interactor because business logic acts on the data.

If there’s no data in the view controller, why do we still need to cast to a specific UIVIewController subclass? After all, we simply need to be able to present a view controller using the built-in methods.

This old way of passing data worked. But I think we can do better.

A data store protocol just for your data

The interactor contains the scene’s business logic AND stores the data the business rules act on. They really are two separate things. Let’s break them free.

The source

For the ListOrders scene, the ListOrdersBusinessLogic protocol specifies both the business rule fetchOrders() and the data orders:

Let’s break it up into two protocols:

The interactor still defines the data as it did before, but it also needs to conform to the new ListOrdersDataStore protocol:

The destination

We’ll do the same for the ShowOrder scene. Pull out the data:

And add the new protocol conformance:

Attach the data stores to the routers

I mentioned above I didn’t like the idea of having a direct reference to the interactor in the router. But now we don’t have to. We can add a new reference to the data store in the router.

This is a good example of restricting access by the use of protocol. The dataStore variable is typed to ListOrdersDataStore. But the underlying object is really the interactor. But we can’t access anything else in the interactor except those explicitly defined in the ListOrdersDataStore protocol. This means we can only access the data that we intentionally specify.

The ListOrdersDataPassing protocol wraps the data store so that the router in the view controller doesn’t need to know too much about the router. It only needs to know the router has routing logic and data passing capability. A router is not typed to a specific type. Rather, it’s composed of a list of protocols to convey its functions:

The ListOrdersViewController and ShowOrderViewController‘s setup() methods attach the data stores to the routers.

Now, in the router, we can get to the orders array in the source scene, and the order variable in the destination scene, without involving the view controller.

Presenting view controllers in iOS

First, a quick overview of how presenting view controllers work in iOS.

When presenting a view controller, you can trigger it programmatically using one of the many UIViewController methods such as show(_:sender:) as in:

Or, you can use storyboard segues. You can perform a segue manually like:

But you can also create segues right in the storyboard. When you do so, you need to provide a segue identifier. Then, in the view controller’s prepare(for:sender:) method, dispatch to an appropriate route based on this segue identifier:

If your view controller has 10 outgoing routes, your prepare(for:sender:) method will have 10 conditional clauses. You can already see where this can lead to.

Clean Swift makes performing segues automatic

The ListOrdersRoutingLogic protocol defines two outgoing routes:

But ListOrdersViewController‘s prepare(for:sender:) method doesn’t get any longer even if it has 10 routes. That’s because it relies on convention over configuration:

If you name your segue identifier the same as your scene name, it makes use of Objective-C selectors and dynamic method dispatching to send an appropriate routeToSceneWithSegue(segue:) message to the router. (Note the same selector is written a little differently for Swift and Objective-C.)

In the case of ListOrdersRouter, if you name your segue identifier:

  • “ShowOrder”, when a segue is performed, it’ll invoke routeToShowOrder(segue:).
  • “CreateOrder”, when a segue is performed, it’ll invoke routeToCreateOrder(segue:).

Now you know why the router inherits from NSObject, and ListOrdersRoutingLogic is made @objc. Objective-C selectors only work when the object is an Objective-C class.

Routing = Data Passing + Navigation

The 3 steps of the routing process are:

  1. routeToNextScene(segue:)
  2. passDataToNextScene(source:destination:)
  3. navigateToNextScene(source:destination:)

When the routeToNextScene(segue:) method is called, it in turn invokes the passDataToNextScene(source:destination:) method, and then, if necessary (i.e. not a segue route), the navigateToNextScene(source:destination:) method.

This warrants an explanation.

A segue route is triggered by either the storyboard, or manually by calling performSegue(withIdentifier:sender:). The prepare(for:sender:) method will be invoked regardless. Next, the segue will be performed by iOS. That means you don’t need step 3. If you do make a call to navigateToNextScene(source:destination:), your destination view controller will be presented twice! You certainly don’t want that.

A non-segue route means you need to programmatically present your destination view controller, such as by calling show(_:sender:). This method call should be moved inside the navigateToNextScene(source:destination:) method.

Makes sense?

The new way of passing data

The route from the ListOrders scene to the ShowOrder scene is a segue route, so we only need to perform steps 1 and 2. The routeToShowOrder(segue:) method first grabs a reference to the destination store. Yes, we still need to jump through some hoops here to get to the ShowOrderDataStore instance. But we’re passing data here, so we must know the specific data store type to access the data within. And this hoop jumping is done in the routeToNextScene(segue:) method only. Your passDataToNextScene(source:destination:) and navigateToNextScene(source:destination:) methods are isolated from this detail.

Here’s the routeToShowOrder(segue:) method:

The code to pass a selected order from the ListOrders scene to the ShowOrder scene looks like:

Notice the inout? Since we’re changing data in the destination data store, we need to make it an inout parameter in the method signature. In effect, this makes it a var instead of a let.

After we retrieve the selected order from the source scene, passing it to the destination scene becomes very straight forward.

You can also create separate objects to be the data stores. But I find it easier to work with when the interactor implements both business logic and data stores. The business logic rules can act on the data in the object, instead of having to establish another API.

What about presenting view controllers?

This particular route is a segue route, so we don’t need to implement the navigateToNextScene(source:destination:) method. But if we were to make this a programmatic route, the navigateToShowOrder(source:destination:) method would look like:

We’re not changing the destination view controller. We simply present it. So we don’t need inout here.

If you like, you can also perform your own custom transitioning animations here.

You’ve just seen a specific route to understand how the new routing works. Next time, I’ll write about how to generalize this so this can work for all routing scenarios that you’ll encounter in your app. Stay tuned.

Raymond
I've been developing in iOS since the iPhone debuted, jumped on Swift when it was announced. Writing well-tested apps with a clean architecture has been my goal.

21 Comments

  1. Does this pattern work with unwind segues?
    If I’m not mistaken, unwind segues are run through the receiving (destination) view controllers… so this wouldn’t catch the ‘routeToXXXX’ in sending (source) view controller which would end up bypassing sending data backwards. right?

    1. Hi Gregory,

      An unwind segue is still a segue, right? The same thing happens.

      With a forward segue going from scene A to scene B, the sourceVC and sourceDS is A, and the destinationVC and destinationDS is B. With an unwind segue, the sourceVC and sourceDS is B, and the destinationVC and destinationDS is A.

  2. Why should router mine data from store? Why should router care, what indexpath is selected? You (well, we 🙂 ) use models to pass data structs between members of the VIP cycle. Shouldn’t controller just simply inject data struct as a parameter to the router (e.g. router.navigateToShowOrder(data: ShowOrderData)? Router than just hands the data to the next scene. Controller has all the necessary information for the next scene, and knows what to send up the next scene. Let the controller just hand the data to the router, instead of bothering the router with indexpath and (possibly complex) model data structure. I have done this in last few apps, and it seems to work well so far. This way the router is totally decoupled from interactor and only manages routing and passing the data. Not preparing the data. But maybe I am missing something. What is your opinion on this?

    1. Hi Vilém,

      Why does the view controller need to do order = interactor.orders[index]? A view controller should only deal with display logic. Finding an order to show in the next scene is not really a responsibility of the view controller. It’s the job of the interactor.

      Assume we choose not to use segue, but instead use IBAction to trigger the route. We can create a SelectOrder use case and have the interactor save this selectedOrder. When the VIP cycle completes and comes back at displaySelectOrder(), we can then trigger the route: router.routeToShowOrder(). In your passDataToShowOrder() method, you only need to do destination.order = source.selectedOrder.

      This makes total sense. You can certainly do it this way. I would even argue you should do it this way, especially when you need to do more than just remembering the selected order to show, such as when you want to refresh the order to make sure it has the latest attributes, or when you need to pull associated data such as payment info that’s not available in the ListOrders scene.

      However, in the case of the CleanStore app, I use storyboard segue and don’t need to do anything extra. So I choose to stick this little bit of logic in the router. After all, it’s a two-liner in the passDataToShowOrder() method. It’s already abundantly clear to me what it’s trying to do by the name of the method.

  3. What if I want to route to other storyboard where the first first view controller is navigation view controller followed by a scene (data should be passed to that scene)?

    Method router.responds(to: selector) returns false under prepare for segue function in the starting scene.

    1. Hi Mike,

      In your First scene’s router’s routeToSecond(segue:) method, you can initialize the destination view controller from your other storyboard. Then, in the navigateToSecond(source:destination:) and passDataToSecond(source:destination:) methods, you can do anything necessary to pass data to the Second scene and present it. The templates have examples to show several different situations.

  4. Hi Ray,

    Assume i am using PickerView
    – PickerView is in ViewController
    – Webservice call is done in Interactor & getting few values
    – Where i have to save that(webservice reponse) values( to access from Viewcontroller for picker )?

    Method – 1
    protocol RegisterBusinessLogic{
    var pickerData: [String]! {get set}
    }
    if i use method 1 – i can access “pickerData” from Viewcontroller as interactor!.pickerData.count

    or

    Method – 2
    protocol RegisterDataStore{
    var pickerData: [String]! {get set}
    }

    If i use method 2 how i can access “pickerData” from ViewController.

    As i understood “RegisterDataStore” is to store values for the self or for the destination if we route to another

    Which is the correct approach Method 1 or Method 2 ? If Method 2 is right how can i access from Viewcontroller ?

    1. Hi Fevicks,

      Here is the thinking process:

      1. If pickerData needs to be accessible by the view controller, declare it in the BusinessLogic protocol.
      2. If pickerData needs to be passed to another scene, declare it in the DataStore protocol.
      3. If both 1 and 2 are needed, declare in both protocols.
  5. I need to implement LoginWithFacebook feature in my application.
    Can I pass viewController as a parameter to interactor?

      1. Actually I have a Facebook worker in my application that actually calls the Facebook SDK functions. Now facebook SDK’s function takes UIViewController as a parameter.

        As I know about Clean Architecture, when user will tap on “Facebook Login” button, it will call the IBAction function (which is in UIViewController). Now In this IBAction function, application will call the interactor’s function which will then call the Facebook Worker function and hence interactor will get the result and send it to presenter. And then presenter will update the UIViewController’s UI.

        1. Hi Asif,

          If Facebook’s SDK requires you to pass an instance of UIViewController, you’re going to have to do that, unless you don’t use the SDK. You can pass it to the interactor then to your Facebook worker, without using it along the way. Just pass it through.

  6. How about passing data to the same view? Example is a tableview with 2 prototype cells. First one is the list of selected items and 2nd is the list of items to select. So whenever user selects one of the items in the list for 2nd item, tableview is updated. I implement this by:
    1. Pass the indexPath on the interactor on didSelectRow event
    2. Interactor – Update the model(remove selection from list of all items) and save selected items.
    3. Interactor – Call presenter and pass model and selected items.
    4. Presenter – generate again the whole view model for the tableview. ( Sort, view model list for selected and unselected list)
    5. Presenter – call view to update table.
    6. ViewController – tableview reload data.

    I am now confused on why I need to perform update on both interactor and presenter. Any comments on this?

    1. Hi Andy,

      In your example, everything happens in the same scene. That’s not routing. Routing, by definition, means navigating from one scene to another. Your use case can be easily solved with the delegation pattern.

  7. I have a table cell that will open “Connect device” scene if there is no device and “Device details” scene if there is device connected.
    So the same table cell but two different destinations depending on a condition.
    Should I use segue with general identifier like “DeviceScene” and check the condition in the router? Or there is a more elegant way to do this?
    Not sure if it’s good to put such logic and required data into the router…

  8. Hi Raymond,
    I have been using Clean Swift for over a year now and just love it and enjoy the way it makes coding (and life :P) easier. Thank you so much.

    I have some doubts here. Please help.
    These cases are when I present a child vc from parent vc and give data back to parent vc on dismiss of child vc.
    1. How will the parent view controller know that the data is updated by child view controller’s router and it needs to refresh the data when child is dismissed?
    2. What if I want the parent vc to do some validation on the data given to me by child vc and display an error alert on the parent vc?

  9. Hi Raymond,
    I have a question: if i need to use dependency injection and pass instances of some services/workers of the interactor to the next scene interactor?
    I need to adopt the same approach, by declaring these objects in the DataStore protocol or by using another specific protocol for these objects of the interactor that need to be passed to the next scene?

    Thanks

  10. Hi Raymond
    I have a question
    I want to go next VC after I got answer from API and in my source view controller I have a func it say to me go to next view controller(it’s a presenter display logic method), how should I tell interactor to do his job,
    calling -> sourceIntractor.navigateToDestination(segue: ……) is not avilable.

  11. Hi Raymond
    Lets say my app has different modules like Bill Payment, Fee Payment etc. If I am writing code for my Bill Payment module, Do I need to have a router for every single screen for that module? I first land in Bill List controller, then from there, I go to Bill Detail Controller, and then Bill Amount controller, and then in last Review Payment controller. Do every screen here needs to have its own router?

  12. I have VC1 That presents VC2 That also Presents VC3, How can i detect the dismissla of VC3 to update VC2 and then to update VC1.

    I am following the same architecture.

Leave a Comment

Your email address will not be published. Required fields are marked *