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/awaitfor cleaner, more readable asynchronous code. - Use
async letfor parallel execution of independent tasks. - Use
TaskGroupto manage structured concurrency. - Always check
Task.isCancelledfor 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.