Swift - Concurrency



Concurrency provides the ability for Swift program to execute multiple tasks or operations simultaneously. Or we can say that it allows us to write asynchronous and parallel codes in a structured way. We are allowed to suspend and resume asynchronous code. Concurrency will improve the responsiveness and performance of the application.

Asynchronous Functions

In Swift, asynchronous functions or methods are the special type of functions or methods that can perform non-blocking and asynchronous operations. Such types of functions can be suspended in between their execution or they can also pause to wait for something. Also, we are allowed to mark the places where we want to suspend the execution of the function.

To create a synchronous function or method we have to use the async keyword in the declaration part after the parameter list and before the return arrow.

Syntax

Following is the syntax for asynchronous function −

func functionName(parameter) async -> returnType{
   // statement
}

To suspend the execution of the asynchronous function or method until the awaited asynchronous operation completes, we have to use the await keyword in front of the call. It makes concurrent code easier to read.

Following is the syntax for suspending the execution of asynchronous functions −

await functionName(parameter)

Asynchronous function or method can also throw an error and we can handle that error with the help of try and catch block.

Following is the syntax for handling errors by asynchronous functions −

func functionName() async throws -> Data {
   // asynchronous code that may throw an error
}

do {
   let data = try await functionName()
   // process data
} catch {
   // handle error
}

We can also call asynchronous functions in parallel by using the async keyword in front of let while defining a constant and then writing the await keyword whenever we use the constant.

Following is the syntax for calling asynchronous functions in parallel −

// Calling asynchronous function in parallel
async let result1 = functionName1(parameter)
async let result2 = functionName2(parameter)

// Suspend execution
let suspendExecution = await[result1, result2]

Example

Swift program for asynchronous function.

// Asynchronous function
func randomFunc() async -> Int {
   Int.random(in: 10...40)
}

let result = await randomFunc()
print("Random Number:", result)

Output

It will produce the following output −

Random Number: 13

Task and Task Group

In Swift, task and task groups are the main concepts that help us to work with asynchronous code in a structured manner. A task is a unit of work that can be executed concurrently. All the asynchronous codes that execute are part of the task. Generally, a task can perform one operation at a time but when we create multiple tasks then Swift will schedule them to run simultaneously.

Whereas a task group is used to manage a collection of tasks and collectively perform operations on them. Here the tasks are arranged in a hierarchy, which means each task in the given group has the parent task and child tasks and this relationship is important because −

  • While working with a parent task, you cannot forget to wait for its task to complete.

  • If you set the priority of the child task to high then the priority of the parent task will automatically be set to high.

  • If the parent task gets cancelled, then the child task will also cancelled.

  • The local values of tasks can easily propagate to child tasks

Task Cancellation

We are allowed to cancel the execution of the task. Or we can terminate the ongoing task before it is completed. It is helpful in various cases like cleaning up resources and stopping long running operations. We can cancel the task with the help of the cancel() method provided by Swift.

Example

Swift program to create and cancel a task.

import Foundation

class MyTaskManager {
   private var task: DispatchWorkItem?

   func createTask() {
    
      // Create a DispatchWorkItem
      task = DispatchWorkItem {
        
         for i in 1...5 {
            if self.task?.isCancelled ?? false {
               print("Task cancelled")
               return
            }
            print("Running task - \(i)")
         }
         print("Task completed successfully")
      }

      // Execute the task on a background queue
      DispatchQueue.global().async(execute: task!)
   }

   func cancelTask() {
    
      // Cancelling the task 
      task?.cancel()
   }
}

let obj = MyTaskManager()

// Create and run the task
obj.createTask()

// Wait for a while 
sleep(2)

// Cancel the task
obj.cancelTask()
Output

It will produce the following output −

Running task - 1
Running task - 2
Running task - 3
Running task - 4
Running task - 5
Task completed successfully

Actor

As we know tasks are used to break our programs into isolated or concurrent pieces, which makes them run safely at the same time. And to add some information between these tasks Swift provides Actors. Actors are reference types and it allows only one task to access its mutable state at a time. That makes code run safely in multiple tasks and can easily interact with the same instance of an actor. In actor, if outside code tries to access the properties directly, then we will get an error.

We can declare an actor with the help of the actor keyword. Also, we are allowed to create instances of actor just like class and structure to access the properties and methods of the actor. To suspend the execution of the actor we can use the await keyword.

Syntax

Following is the syntax for calling asynchronous functions in parallel −

// Calling asynchronous function in parallel
async let result1 = functionName1(parameter)
async let result2 = functionName2(parameter)

// Suspend execution
let suspendExecution = await[result1, result2]

Example

Swift program to demonstrate how to create and use actors.

// Creating actor
actor MyCounter {

   private var num = 0

   func incrementValue() {
      num += 1
   }

   func getValue() -> Int {
      return num
   }
}
 
// Creating instance of actor
let obj = MyCounter() 

// Accessing actor methods
await obj.incrementValue()
let value = await obj.getValue()
print("Value: \(value)")

Output

It will produce the following output −

Value: 1

Sendable Type

As we know tasks and actors can divide the program into a piece of code that runs concurrently. Inside the task or instance of the actor program contains mutable states like variables and properties and they are also known as concurrency domain. Now some of the data does not support concurrency domain, so Swift provides a sendable type to share data from one concurrency domain to another.

We can create a sendable type by declaring conformance to the sendable protocol. It does not have any code requirements but has semantic requirements. Generally, there are three ways for a type to be sendable:

  • The type must have a value type and its mutable state is created by other sendable data.

  • The type does not have any mutable state and its mutable state is created by other sendable data.

  • The type that assures the safety of its mutable state.

Some types in Swift are always sendable such as structure and enumeration.

Advertisements