Synchronising the Asynchronous in Swift
I’m sure you’ve already had been in situations, where you had asynchronous tasks and you had to make them execute synchronously, because there were dependencies between them. Like imagine the scenario of scheduling a meeting
- calling some login API to get a token
- get a list of available persons at certain time
- get a person(s) details
- create a meeting with persons available
Before you start shouting that this is just a poor example of API, rest assured I agree with you. But it’s our reality all too many times.
But, there is a solution out of the box: use Operations and link dependent tasks on the queue! That sounds like a text book example for what we want to achieve.
(NS)Operations
Let’s use the Operations and make it work:
We can see we have an Operation SumOfTwoAsyncOperations
. We can clearly see its business logic performs the business logic within an async call on the global, backthread queue.
And here we have a function sumOfTwoAsyncOperations
, which takes 4 numbers and adds number1
and number2
in operation1
and number3
and number4
in operation2
. We can see that we also created a “dummy” operation3
of type BlankOperation
with a sole purpose to confirm the last executed operation, which should be operation3
and print the message into the console.
So, if we now call this function:
with parameters 4 and 5, then we would expect to get this output in the console:
Operation 1 Sum of 4 + 4 = 8
Operation 2 Sum of 5 + 5 = 10
Operation 2 finished!
Well.. NOT!
We get this:
Operation 2 finished!
Operation 2 Sum of 5 + 5 = 10
Operation 1 Sum of 4 + 4 = 8
We get the opposite order, but even that is not guaranted. Actually, we have no real control over the results. If we remember discussion in this article, it was mentioned dependency management and some other Operations don’t come completely free or not free at all.
What actually happens is that we definitely start and execute operations in wished order, but since they are asynchronous, we don’t have control over when they actually finish, meaning, calling the callback.
Semaphores to the rescue!
OK, how about if we make operations synchronous, like this:
Yes, why not? As you can see, we’ve used semaphores, which are really an easy and probably the best way to do the job, especially because you can set the time limit thus make sure the thread doesn’t get blocked forever.
Never use any technique to potentially block the thread indefinitely. Always limit the wait time, making sure if for some reason task doesn’t finish, your app doesn’t freeze of gets unresponsive at any time.
also
Never block the main thread, considering dependencies it can happen all too quickly, that a casual bug somewhere else, burried deep, can eventually freeze the app.
For all this to work, we also need to make our operation asynchronous and override 2 standard methods as described in Apple’s documention:
I’d say, this is quite a hefty amount of work for something, what seemed almost like a trivial task. ☹️ Esentially, what this so-called isAsynchronous
does, is telling the runtime to apply semaphores (most probably) before isFinished
changes its value. Seems like a kind of old-fashion way of doing it…
Well, I would like to explore some other possibilities, too.
GCD
Let’s see, how would this look like with GCD and no ugly nesting:
… or, just the boilerplate:
This code does the same as the one before with Operations. Looks leaner and easier to read. It would definitely be my choice.
Dispatch Groups
We could do something similar with Dispatch Groups as well:
… or again, just with the boilerplate:
We can see that it doesn’t differ much from the example with semaphores before, you can also use wait
with the timeout (which we actually didn’t really need here), but I would still rather choose the semaphores, simply because their purpose is clearer and fits the issue better.
Dispatch groups, however, should always be the weapon of choice, when a group of tasks needs to be executed before proceeding and the tasks don’t have mutual dependencies.
Promises we made
Yes, I hear the voices: this can be done better with the PromiseKit! I agree and disagree.
I love Promises! I love them in the languages with native support, like Javascript. They flatten potentially nesting structure into beautifully readable closures. However, if you want to pass parameters between the .then
closures, then the code cold get ugly. At least until Ecmascript 8, when we got async
and await
keywords, which solve the problem.
But these are all language features. PromiseKit for Swift is an SDK feature, extending the classes. I have few problems with that approach:
- neverending catch-up with iOS SDK story
- very powerful dependency
- complementing the language instead of adding the feature
- not part of the Swift steering committee’s initiative (Evolution), meaning we can’t be sure the transition will be swift and easy and the code certainly won’t be included into the language itself
- doesn’t offer
async
andawait
keywords out of the box, but you have a lovely wrapper from Yannick to handle this
I could list a few more, but my personal view is that all these in comparison to simple GCD semaphore steps is not a big advantage. At least not in most of the cases.
You can download this short example repo and play with it, make your own judgement and examples.