Swift Concurrency
Introduction to Concurrency in Swift
Concurrency in Swift allows executing multiple tasks simultaneously without blocking the main thread. It improves the performance and responsiveness of applications, especially in networking, file handling, and UI updates.
With Swift 5.5+, Apple introduced modern concurrency features such as:
- async/await – Simplifies asynchronous programming.
- Task & TaskGroup – Manages concurrent operations.
- Actors – Ensures thread safety for shared resources.
This guide explains Swift concurrency with real-world examples.
1. Understanding async
and await
in Swift
Swift introduced async
functions to perform non-blocking operations.
Declaring an async
Function
func fetchData() async -> String
{
return "Data fetched
successfully"
}
Task {
let result = await fetchData()
print(result)
}
Output
async
marks a function as asynchronous.
await
pauses execution until the function returns a result.
2. Performing Network Requests with async
and await
import Foundation
func fetchUsers() async throws -> [String] {
let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
let (data, _) = try await
URLSession.shared.data(from: url)
let users = try
JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]]
return users?.compactMap { $0["name"] as? String } ?? []
}
Task {
do {
let users = try
await fetchUsers()
print(users)
} catch {
print("Failed to
fetch users:", error)
}
}
Output
try await
ensures error handling in async functions.
URLSession.shared.data(from:)
works natively with async/await
.
3. Running Multiple Tasks Concurrently with async let
Swift allows running multiple asynchronous operations in parallel.
func fetchPosts() async -> String
{
return "Posts fetched"
}
func fetchComments() async -> String {
return "Comments fetched"
}
Task {
async let posts = fetchPosts()
async let comments = fetchComments()
let result = await "\(posts) & \(comments)"
print(result)
}
Output
async let
starts tasks simultaneously.
await
ensures all operations complete before execution continues.
4. Using Task
to Manage Background Work
Task
allows launching concurrent tasks from synchronous code.
Task {
let data = await fetchData()
print(data)
}
Runs in the background without blocking the main thread.
Running a Task with Priority
Task(priority: .high) {
print("High priority task
running")
}
Priorities: .high
, .medium
, .low
, .background
.
5. Task Cancellation in Swift
Tasks can be canceled to avoid unnecessary processing.
func downloadFile() async {
for i in 1...10 {
if Task.isCancelled {
print("Download canceled")
return
}
print("Downloading... \(i * 10)% completed")
try? await Task.sleep(nanoseconds: 1_000_000_000) // Simulating
delay
}
print("Download completed")
}
let task = Task {
await downloadFile()
}
// Cancel task after 3 seconds
Task {
try? await
Task.sleep(nanoseconds: 3_000_000_000)
task.cancel()
}
Output
Downloading... 20% completed
Downloading... 30% completed
Download canceled
Task.isCancelled
ensures efficient resource management.
Tasks stop execution immediately when canceled.
6. Structured Concurrency with TaskGroup
A TaskGroup
allows running multiple related tasks concurrently and waiting for them to complete.
func fetchData(for id: Int) async
-> String {
return "Data for ID \(id)"
}
func fetchMultipleData() async {
await withTaskGroup(of: String.self) { group in
for i in
1...3 {
group.addTask {
await fetchData(for: i)
}
}
for await result in group {
print(result)
}
}
}
Task {
await fetchMultipleData()
}
Output
Data for ID 2
Data for ID 3
Efficient parallel execution of multiple tasks.
withTaskGroup
ensures all tasks complete before proceeding.
7. Using Actors for Thread Safety
Actor
ensures data consistency by preventing race conditions in concurrent operations.
actor BankAccount {
private var balance: Int = 0
func deposit(amount: Int) {
balance += amount
}
func getBalance() -> Int {
return balance
}
}
let account = BankAccount()
Task {
await account.deposit(amount: 100)
print("Balance: \(await
account.getBalance())")
}
Output
actor
ensures safe access to shared data.
await
is required when accessing actor properties/methods.
8. Async Sequences in Swift
Swift allows processing streaming data asynchronously using AsyncSequence
.
struct Counter: AsyncSequence, AsyncIteratorProtocol {
var count = 0
mutating func next() async ->
Int? {
count += 1
return count <= 5 ? count : nil
}
func makeAsyncIterator() -> Counter {
return self
}
}
Task {
for await num in Counter() {
print(num)
}
}
Output
2
3
4
5
AsyncSequence
provides efficient handling of streaming data.
Best Practices for Swift Concurrency
- Use
async/await
for cleaner, more readable asynchronous code. - Use
async let
for parallel execution of independent tasks. - Use
TaskGroup
to manage structured concurrency. - Always check
Task.isCancelled
for graceful cancellation. - Use actors to ensure thread safety in shared data.
Conclusion
Swift concurrency provides powerful tools for handling asynchronous programming with async/await
,
Task
, TaskGroup
, and Actors
. These features ensure safe, efficient, and
scalable concurrent programming.