TANASCHITA.COM
Articles about Swift and iOS Development by Natascha Fadeeva

iOS Interview Questions and Answers for Senior Developers Part 4 - Concurrency

Check your knowledge on threading concepts and concurrency in iOS

Published 30. May 2020 - 6 min read

I recently got into the position of leading the technical interview when my client was searching for a new senior iOS developer.

It's a challenging task to evaluate the skills and knowledge of another developer. So in this series, I want to share my results of the most useful iOS and Swift questions and answers with you. I categorised the questions into different topics:

  1. Swift Programming Language (covered here)
  2. Networking (covered here)
  3. Persistence & Databases (covered here)
  4. Concurrency (covered in this post)
  5. Architecture & Design Patterns (coming soon)
  6. Testing (coming soon)
  7. Continuous Integration (coming soon)

This post covers questions and answers on concurrency. The purpose of these interview questions is to test the developers general knowledge of threading concepts. And also to check their specific knowledge on concurrency in iOS.

Multithreading in practice

So let's get started.

1. What possibilities do you have to run code on a background thread in iOS?

The first possibility is to use the old plain threads, but by using them directly, you would have to deal with a low-level API.

A higher level of abstraction is provided by Grand Central Dispatch (GCD). When using GCD, you can execute blocks of code on a DispatchQueue - either one you create by yourself or one provided by the iOS system.

Another possiblity is to use Operations which are built on top of GCD.

2. What is the difference between a serial and a concurrent queue?

Serial queues execute one task at a time in the order in which they are added to the queue. So a serial queue guarantees that a task will finish first before another task will start. Concurrent queues allow multiple tasks to run at the same time. The code below shows how these two types of queues are created.

let serialQueue = DispatchQueue(label: "serialQueue")
let concurrentQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)

3. When you review the code below, would you suggest an improvement?

DispatchQueue.global(qos: .background).async {
    let text = loadDescription()
    label.text = text
}

Yes. UIKit can only be used from our app’s main queue, so the label's text shouldn't be set on a background queue. One possible way to solve this is to call the UI update code on the main queue.

DispatchQueue.global(qos: .background).async {
    let text = loadDescription()
    
    DispatchQueue.main.async {
        label.text = text
    }
}

4. What is the difference between asynchronous and synchronous tasks?

Synchronously starting a task blocks the calling thread until the task is finished. Asynchronously starting a task returns directly on the calling thread without blocking.

5. How can you cancel a running asynchronous task?

In GCD, you can achieve that with DispatchWorkItem. By setting it up with the task that needs to be done asynchroniously and calling the cancel method when needed.

let workItem = DispatchWorkItem { [weak self] in
    // Execute time consuming code.
}

// Start the task.
DispatchQueue.main.async(execute: workItem)

// Cancel the task.
workItem.cancel()

By using Operations, it's almost the same as with GCD. You can use the cancel method on the operation.

let queue = OperationQueue()
let blockOperation = BlockOperation {
    // Execute time consuming code.
}

// Start the operation.
queue.addOperation(blockOperation)

// Cancel the operation.
blockOperation.cancel()

6. How can you group asynchronous tasks?

By using GCD, you can use DispatchGroup to achieve this.

let group = DispatchGroup()

// The 'enter' method increments the group's task count.
group.enter()
dataSource1.load { result in
    // Save the result.
    group.leave()
}

group.enter()
dataSource2.load { result in
    // Save the result.
    group.leave()
}

// This closure is called when the group's task count reaches 0.
group.notify(queue: .main) { [weak self] in
    // Show the loaded results.
}

When working with operations, you can add dependencies between them.

let operation1 = BlockOperation {
    // Execute time consuming code.
}
let operation2 = BlockOperation {
    // Execute time consuming code.
}
let operation3 = BlockOperation {
    // operation1 and operation2 are finished.
}
operation3.addDependency(operation1)
operation3.addDependency(operation2)

7. What is a race condition?

A race condition can occur when multiple threads access the same data without synchronization and at least one access is a write. For example if you read values from an array from the main thread while a background thread is adding new values to that same array.

Data races can be the root cause behind unpredicatable program behaviours and weird crashes that are hard to find.

8. How can you avoid race conditions?

To avoid race conditions you can access the data on the same serial queue or you can use a concurrent queue with a barrier flag. A barrier flag makes access to the data thread-safe.

var queue = DispatchQueue(label: "messages.queue", attributes: .concurrent)

queue.sync {
    // Code for accessing data
}

queue.sync(flags: .barrier) {
    // Code for writing data
}

The barrier flag approach has the benefit of synchronizing write access while reading can still be done concurrently. By using a barrier, a concurrent queue becomes a serial queue for a moment. A task with a barrier is delayed until all running tasks are finished. When the last task is finished, the queue executes the barrier block and continues it's concurrent behavior after that.

9. How can you find and debug race conditions?

You can use Apples Thread Sanitizer to debug race conditions, it detects them at runtime. The Thread Sanitizer can be enabled from the scheme configuration.

Written by

Natascha Fadeeva
Author and creator of this site

Contact