Dealing With Asynchrony in a Synchronous Swift World

 

Getting Started with the Realm Mobile Platform - Guided Demo

Take a deep dive into the Realm Mobile Platform. You’ll learn the key features of the platform and how to build a Realtime application with it on both the iOS and Android Platforms.

In this talk, I’ll cover callbacks, futures, spinlocks, and other options for dealing with asynchrony in a synchronous Swift world.


Code running in multiple threads is often the source of complexity. As a result, we need a way to keep things organized.

Blocking Operations

Here’s a basic example in Commodore 64 BASIC:

10 PRINT"HELLO" 
	20 GOTO 10

but because we’re more advanced:

10 PRINT"WHAT IS YOUR NAME?"
	20 input N$
	30 PRINT "HELLO, ":N$

Here, we get input from the user, and it’ll return a print statement with what the user typed in. Note that on line 20 is a blocking operation. The program will allow the cursor to blink forever until we type something in and hit return.

Another characteristic of a blocking operation is that we want some data to be returned.

In another example, it is similar, but instead of the question “WHAT IS YOUR NAME?”, it’ll print “PRESS ANY KEY TO CONTINUE”. I’ll use GET instead of INPUT, because I just want a single character.

10 PRINT"PRESS ANY KEY TO CONTINUE"
	20 GET A$
	30 PRINT"THANK YOU FOR PRESSING A KEY"

When I run this program, without touching the keyboard, it runs straight through.

"PRESS ANY KEY TO CONTINUE"

"THANK YOU"

In Commodore BASIC, a GET returns immediately. By changing line 20 to save A as equal to GOTO 20, it will continue to wait.

20 GET A$: IF A$="" GOTO 20

The problem is solved, and when I press a key, it will print “Thank you for pressing a key”.

Different Blocking Calls

There are two types of blocking calls.

  • You make the call, it waits, then finishes.
  • Synchronous call - a call is made, and it immediately finishes.

To have the first type of call in iOS or MacOS apps, it will simply stall until it complete. A solution might be to use dispatch queue async and put this on the background queue. But, there are two problems:

  • When the operation finishes, there will not be a notification.
  • There might be a return value, perhaps an integer, string, or some other variable that is not easily accessible in the background.

Callbacks

Below is a perform operation which takes an input, it will then fork a process and run in the background before completing:

int perfromOperation(char *input);

To know when this is complete, we add a callback:

int performOperation(char *input, void(callback)(char *));\

But suppose there are ten of these operations in flight, and one of them returns. I may not know what it’s from. I can add a context parameter, here it’s a void start.

int performOperation(char *input, void(callback)(char *, void *), void *context);

This unique ID context is available in Swift in UIKit. The callback mechanism here is much more friendly:

button.addTarget(self,
					 action: #selector(buttonTapped),
					 for: .touchUpInside)

The target here is an object, and the action is a selector, which is a type of callback. Some APIs will even give userInfo, which is a friendlier context, as it’s a dictionary.

```Swift
var userInfo: [AnyHashable: Any] { get }

An API using this NSNotifications.

But the problem with callbacks is that the distance between when the call is made and the callback itself is too far. For example, it made be called in viewDidLoad, but the action method is called much lower in the file, creating a mental distance.

Closures

A way to close this gap is to use closures:

nc.addObserver(forName: NSNotification.Name("..."),
				   object: nil,
				   queue: nil) { (notification) in
					  //code
	}

In the more recent APIs, we have an add observer that provides a closure. It’s good to observe something, then put the code for it nearby.

For methods that do not have a nice closure or a block-based API, there are third-party tools. For example, the below library wraps KVO:

button.rx.tap.subscribe { (event) in
		// button tapped!
	}
 ```

In `RxSwift`, a nice feature is being able to provide a closure for an action, such as with a button.

I categorize these types of operations as **operation-centric asynchronous**.

### Time Distance

Suppose I have a ViewController with a label, and its text is to be set from a network call. There will be many layers between the call to fetch the data to setting the label value. 

Consider this example:

```swift
	nc.fetchString(for: .label) { string in
		self.label.text = string
	}

Then network controller will then talk to the URLSession:

session.dataTask(with: url) { (data, response, error) in
		guard let data = data else { return }
		completion(String(data: data, encoding: .utf8))
	}

This closure is two levels deep as opposed to something that is much deeper. How can we deal more complex asynchronous calls?

Futures Libraries

Examples of future libraries are PromiseKit, and BrightFutures. The following is an example from Bolts, a project by Facebook. A key feature here is that it wraps objects up into tasks.

The following is a function that will return a task:

func stringTask() -> Task<String> {
		let taskCompletionSource = TaskCompletionSource<String>()

		return taskCompletionSource.task
	}

Inside the task is a string, much like a container. It has a generic parameter for a string. The function will run, returning immediately.

In between will be the asynchronous operations.

func stringTask() -> Task<String> {
		let taskCompletionSource = TaskCompletionSource<String>()

		DispatchQueue.global().async {
			sleep(10)
			taskCompletionSource.set(result: "Hello world async!")
		}

		return taskCompletionSource.task
	}

For demonstrative purposes, it will sleep for 10 seconds, then result in the string value. Despite being wrapped up in dispatch async, the function will still run through and return immediately.

If you want to test asynchronous code, set up an expectation, then wait for the expectation. In between is asynchronous work to fulfill the expectation.

let futureString = stringTask()
	
	while !futureString.completed {
		// spin
	}

If the task is complete, access the string:

if futureString.completed {
		futureString.result // String
	}

If you choose not to do a while loop for 10 seconds, use a closure.

```Swift
let futureString = stringTask()

futureString.continueWith { task in
	if task.completed {
		self.label.text = task.result
	}
}

How Is This Different?

Prior to this, I had to specify what I wanted to do, package it into a closure and send it out, without knowing who will be calling it and when.

Here, in the ViewController, all of the logic to fetch the string will return a pending string. It’s shifting the burden of work in favor of the person who needs the value.

This is more of a value-centric asynchronous operation, where we have the value and the use of the value much closer together, in terms of distance.

In contrast, there are operation-centric operations, where you’re not necessarily interested in the value.

Why Swift

  • Strong typing and strong typing generics.
  • Closures.
  • Native concurrency model in Swift (in development).

Conclusion

If you’re interested and want to learn more, you can look up PromiseKit, BrightFutures, and Bolts. There’s also RxSwift and ReactiveCocoa.

If you have any feedback or questions for me, you can get in touch with me on Twitter.


Greg Heo

Greg Heo

Greg is an iOS and Swift nerd, full-time Canadian, and part-time aspiring limerick writer. He spends his days working at Instagram hand-cropping those square photos, and his nights wading through the Swift repositories and writing about his findings at swiftunboxed.com. You can find him on Twitter as @gregheo.

Transcribed by Joseph Buelow