Synchronous Operations

Do you know the original iPhone was released a little more than 10 years ago? Back in the days, apps were simple. Remember a fart app could generate millions of downloads and dollars?

Fast forward to today. Modern iOS apps are so much more complex than those back in 2008.

The networking, persistence, and performance requirements all push your apps to perform more and more asynchronous operations. The Clean Swift architecture is ideal for that with its unidirectional flow of control in the VIP cycle.

So much about asynchronous operations. What about synchronous operations? There is still a need for synchronous operations in your apps.

In this post, I’m turning back the clock. I’ll talk about the various approaches the Clean Swift architecture can take to handle synchronous operations.

The code for the sample project used to explore synchronous operations in this article can be found on GitHub.

Asynchronous Operations

First, let’s take a quick look at how Clean Swift deals with asynchronous operations. A normal VIP cycle involves the view controller, interactor, and presenter.

In the view controller:

Swift

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@IBOutlet weakvarvipCycleLabel:UILabel!

@IBAction funcvipCycleButtonTapped(_sender:Any)

{

letrequest=Home.VIPCycle.Request()

interactor?.vipCycle(request:request)

}

funcdisplayVIPCycle(viewModel:Home.VIPCycle.ViewModel)

{

letresult=viewModel.result

vipCycleLabel.text=result

}

When the user taps a button, the vipCycleButtonTapped(_:) IBAction method is invoked. This method sends the request to the interactor.

In the interactor:

Swift

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

funcvipCycle(request:Home.VIPCycle.Request)

{

generateResult{(result)in

letresponse=Home.VIPCycle.Response(result:result)

presenter?.presentVIPCycle(response:response)

}

}

private funcgenerateResult(completionHandler:(String)->Void)

{

letdateFormatter=DateFormatter()

dateFormatter.dateStyle=.none

dateFormatter.timeStyle=.medium

letresult=dateFormatter.string(from:Date())

completionHandler(result)

}

The interactor calls the private generateResult(completionHandler:) function to generate a timestamp as a String, and then passes the result to the presenter.

In the presenter:

Swift

1

2

3

4

5

6

funcpresentVIPCycle(response:Home.VIPCycle.Response)

{

letviewModel=Home.VIPCycle.ViewModel(result:response.result)

viewController?.displayVIPCycle(viewModel:viewModel)

}

In this simple use case, there is no formatting to be done in the presenter. So it just passes the result along and back to the view controller.

The view controller’s displayVIPCycle(viewModel:) method is invoked which sets the vipCycleLabel‘s text property to the resulting string.

There is nothing asynchronous about the VIP cycle. But it suits asynchronous operations very well. A different method (i.e. displayVIPCycle(viewModel:)) is called, rather than the original (i.e. vipCycleButtonTapped(_:)), when the result is ready. It works similar to the delegation pattern. The interactor can delay invoking the presenter until the result is ready.

This feature of the VIP cycle is very useful for asynchronous operations. The result site is different than the call site, and is invoked when the result is ready.

Synchronous Operations

But what about synchronous operations? Especially when you need the result in the same calling method now, instead of a different delegate method later?

Your interactor or worker method needs to return the result to the caller right away. Not wait until later to call a delegate method or completion handler. The call site and the result site need to be the same.

We’ll look at 5 different approaches:

State Variable

Return Value

In-Out Parameter

Completion Handler

VIP Cycle

But first, let’s change the generateResult() method to return the result as a function return value instead of using a completion handler as in the asynchronous case.

Swift

1

2

3

4

5

6

7

8

9

private funcgenerateResult()->String

{

letdateFormatter=DateFormatter()

dateFormatter.dateStyle=.none

dateFormatter.timeStyle=.medium

letresult=dateFormatter.string(from:Date())

returnresult

}

State Variable

The state variable approach is the easiest but may not be the best. You store the result in the interactor in a state variable, and then read it from the view controller.

In the view controller:

Swift

1

2

3

4

5

6

7

8

9

10

@IBOutlet weakvarstateVariableLabel:UILabel!

@IBAction funcstateVariableButtonTapped(_sender:Any)

{

letrequest=Home.StateVariable.Request()

interactor?.stateVariable(request:request)

letresult=(interactor?.result)!

stateVariableLabel.text=result

}

In the interactor:

Swift

1

2

3

4

5

6

7

varresult:String=""

funcstateVariable(request:Home.StateVariable.Request)

{

result=generateResult()

}

When the view controller invokes the interactor’s stateVariable(request:) method, the interactor calls generateResult() to get the result and store the result in the result variable, right in the interactor itself. Back in the IBAction method, the next statement after calling the stateVariable(request:) method simply reads the interactor’s result back, and uses it to set the stateVariableLabel‘s text property.

Return Value

The return value approach is the most direct. The interactor doesn’t store the result. It simply returns it and it’s up to the view controller to decide what to do with it.

In the view controller:

Swift

1

2

3

4

5

6

7

8

9

@IBOutlet weakvarreturnValueLabel:UILabel!

@IBAction funcreturnValueButtonTapped(_sender:Any)

{

letrequest=Home.ReturnValue.Request()

letresult=(interactor?.returnValue(request:request))!

returnValueLabel.text=result

}

In the interactor:

Swift

1

2

3

4

5

funcreturnValue(request:Home.ReturnValue.Request)->String

{

returngenerateResult()

}

When the view controller invokes the interactor’s returnValue(request:) method, the interactor calls generateResult() to get the result and returns it as the function return value. The view controller captures the return value and assigns it to the returnValueLabel‘s text property.

In-Out Parameter

You can think of the in-out parameter approach as pass-by-reference when we’re talking about how function arguments are passed when called. When you pass an argument by its reference, you are passing a pointer to the memory location where the variable is stored. If the function assigns a new value to this parameter, the new value overwrites the old value in the same memory location. This new value will be observed after the function call finishes.

The opposite is pass-by-value, which is the default in Swift. A copy of the value stored in the argument is made and stored in a new memory location. Inside the function, the parameter is treated as a let constant, so it cannot be changed.

You can explicitly tell it to pass by reference by specifying the inout keyword for the parameter in the function signature. This allows you to change the value inside the function. The new value will persist after the function call.

A good analogy can be found in the Swift language. A struct is a value type, whereas a class is a reference type.

In the view controller, you first instantiate the result variable, and then invoke the inOutParameter(request:result:) method. You also pass result as a function argument, as a container for the result. Notice the & before result in the method invocation. It is to signify that the variable is to be passed by reference instead of by value. The function signature of the inOutParameter(request:result:) method also requires the inout keyword after the variable name and before the type. After the method invocation, result will already have the result, so you can simply assign it to the inOutParameterLabel‘s text property.

Completion Handler

You’ve already seen the completion handler approach above, when we looked at asynchronous operations. Who’s to say it cannot be used for synchronous operations!?

When the view controller invokes the interactor’s completionHandler(request:completionHandler:) method, in addition to the request, it also passes in an inline completion handler block. The interactor calls the generateResult() method and then invokes the completion handler and passes the result back to the view controller. Back in the view controller, when the completion block is called, the result parameter contains the result. And you can assigns it to the completionHandlerLabel‘s text property, all within the completion block.

VIP Cycle

Clean Swift’s VIP cycle can also be used for synchronous operations. It looks very similar to how it works for asynchronous operations, except the generateResult() methods returns immediately and does not use a completion handler.

In the view controller:

Swift

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@IBOutlet weakvarvipCycleLabel:UILabel!

@IBAction funcvipCycleButtonTapped(_sender:Any)

{

letrequest=Home.VIPCycle.Request()

interactor?.vipCycle(request:request)

}

funcdisplayVIPCycle(viewModel:Home.VIPCycle.ViewModel)

{

letresult=viewModel.result

vipCycleLabel.text=result

}

In the interactor:

Swift

1

2

3

4

5

6

funcvipCycle(request:Home.VIPCycle.Request)

{

letresponse=Home.VIPCycle.Response(result:generateResult())

presenter?.presentVIPCycle(response:response)

}

In the presenter:

Swift

1

2

3

4

5

6

funcpresentVIPCycle(response:Home.VIPCycle.Response)

{

letviewModel=Home.VIPCycle.ViewModel(result:response.result)

viewController?.displayVIPCycle(viewModel:viewModel)

}

The view controller and presenter look exactly the same as for the asynchronous case. The interactor calls the generateResult() method, and uses the return value to create the response, without any completion block. In a way, it’s even simpler and straightforward.

However, the result site is not the same as the call site, as far as the view controller is concerned. The call site is in the vipCycleButtonTapped(_:) method, whereas the result site is in the displayVIPCycle(viewModel:) method. This deviates from what was mentioned before we looked at the first approach. Is this wrong?

No, the VIP cycle here is still synchronous. Everything happens in the main thread. There is not a background thread or completion handler. The generateResult() method returns the result right away as a return value. It doesn’t call a completion handler to pass the result back. So what gives?

The VIP cycle can be used for both asynchronous and synchronous operations (when the call site and result site can be different).

When the result is ready, the only thing we need to do is to set a label text property. This label is available as a variable in the view controller. You can access it from any method inside the view controller. So the VIP cycle works and should be preferred.

So when should we use the other approaches? The answer is when the call site and the result site must be the same. When does this happen? It happens when things are out of our control. When are things out of our control? It is out of our control when we’re dealing with Apple’s API or a third party framework/library.

Take the UITableView‘s tableView(_:cellForRowAt:) method as an example. When you implement this method in your view controller, it requires you to return a UITableViewCell object as the return value. This necessitates that the call site is the same as the result site. So a normal VIP cycle can’t be used here.

In a future post, I’m going to show you a couple modifications of the VIP cycle that work for this particular synchronous case. Stay tuned by subscribing in the form below.

Get the Clean Swift Xcode Templates

Join over 15,000 developers and subscribe to get my Xcode templates. Learn how to apply the VIP cycle to your projects. Extract business and presentation logic into interactor and presenter. Route to other scenes and pass data. Write fast, maintainable unit tests. Be confident making changes.