π« Callback hell - Nested closures leading to unreadable, difficult-to-maintain code.
π« Error-Handling Headaches - Complex error propagation with callbacks or completion handlers.
π« Thread-Safety Issues - Manual thread management, prone to race conditions and bugs.
π« Complicated Synchronisation - Dispatch groups, semaphores, and complex logic to coordinate tasks.
π« Unreadable Asynchronous Code - Hard-to-follow, deeply nested code paths.
β
Async/Await - Simple & Clean: Write async code that reads like sync code and alternative to nested closures.
β
Structured Concurrency - Task Management: Automatically manages task lifecycle and ensures tasks are properly completed or canceled.
β
Actors Isolation - Thread Safety: Prevents data race by isolating state and Only one task accesses mutable state at a time.
β
Tasks - Building Blocks: Create, manage, and cancel async operations easily and No need for manual thread management.
β
Cleaner Code - Less Boilerplate: Focus on logic, not threading issues and Async code is now as readable as sync code.
β
Strict Concurrency - Swift 6 further enhanced capabilities introducing stricter concurrency checks
Thanks to Swift 5.5 and Enhanced in Swift 6
𧡠NSThread - Manual thread management
π GCD - Task management with queues
π Operation Queues - Task dependencies and ordering
π Combine - Reactive programming model
β
Swift 5 - Async/Await & Structured Concurrency
π‘οΈ Swift 6 - Complete Currency by default and Data Race Safety
β³ Async/Await
π Tasks and πΈοΈ Structured Concurrency
π Actors and Actor Isolation - Foundation
πΒ Concurrency Interoperability with Objective-C
πΒ Async handlers
Full Isolation Enforcement
Async/Await, which simplify and streamline asynchronous programming, Structured Concurrency and Tasks, which bring order and safety to managing concurrency. Finally, weβll dive into Actors, Sendable that ensures thread-safe operations.
Each topic will be covered with features, version history, pros and cons and practical examples.
Basic Syntactic Building Block
π Suspension Points: Mark functions as async to enable the use of suspension points within the function.
β³ Awaiting Results: The await keyword introduces a suspension point, where the function pauses until the asynchronous operation completes.
π Error Handling: Integrates seamlessly with do-catch for handling errors in asynchronous functions.
Function that simulates an asynchronous task
// Define a function that simulates an asynchronous task
func performTask(taskNumber: Int) async {
print("Task \(taskNumber) started.")
do {
try await Task.sleep(nanoseconds: 1_000_000_000) // Simulate some asynchronous work (1 second)
print("Task \(taskNumber) completed.")
} catch {
print(error)
}
}
Networking Example with Generics
// Async-await Networking Example with Generics
func sendRequest<T>(urlStr: String) async throws -> T where T : Decodable {
guard let urlStr = urlStr as String?, let url = URL(string: urlStr) as URL?else {
throw NetworkError.invalidURL
}
let (data, response) = try await URLSession.shared.data(from: url)
guard let response = response as? HTTPURLResponse, 200...299 ~= response.statusCode else {
throw NetworkError.unexpectedStatusCode
}
guard let data = data as Data? else {
throw NetworkError.unknown
}
guard let decodedResponse = try? JSONDecoder().decode(T.self, from: data) else {
throw NetworkError.decode
}
return decodedResponse
}
Simplifying Asynchronous Programming
Streamlining Concurrency and Alternative to Callback/Closures
Introduced: iOS 15
Changes
Swift 5.5: Introduction of Async/Await - Proposal SE-0296
Swift 6: Continued improvements and complementary use with GCD, OperationQueue, and Combine
Readability, Error Handling and alternative to Completion Handler/Closures
Reduces complexity associated with Callback Hell or nested closures
Reduces chance of race conditions or deadlocks
Limited backward compatibility
Complex Integrations when interacting with existing GCD or OperationQueue
Fundamental Unit of Concurrent Work
π Sequential, Asynchronous and Self-contained
π Task Groups: Use TaskGroup to manage collections of tasks that run concurrently and complete as a group.
π Task Prioritisation: Assign priorities to tasks (e.g., .userInitiated, .background) to optimize performance.
π Β Task Cancellation: Easily cancel tasks using the cancel() method, propagating cancellation to child tasks.
Task(priority: .high) {
print("high Task Started")
try? await Task.sleep(nanoseconds: 1_000_000_000)
print("high Task Completed")
}
Task(priority: .userInitiated) {
print("userInitiated Task Started")
try? await Task.sleep(nanoseconds: 1_000_000_000)
print("userInitiated Task Completed")
}
Task(priority: .medium) {
print("medium Task Started")
try? await Task.sleep(nanoseconds: 1_000_000_000)
print("medium Task Completed")
}
Task(priority: .low) {
print("low Task Started")
try? await Task.sleep(nanoseconds: 1_000_000_000)
print("low Task Completed")
}
Task(priority: .utility) {
print("utility Task Started")
try? await Task.sleep(nanoseconds: 1_000_000_000)
print("utility Task Completed")
}
Task(priority: .background) {
print("Background Task Started")
try? await Task.sleep(nanoseconds: 1_000_000_000)
print("Background Task Completed")
}
// Independent - Useful when you start the task that isn't tied to current context.
Task.detached {
print("Detached Task started at \(Date())")
try? await Task.sleep(nanoseconds: 2 * 1_000_000_000)
print("Detached Task completed at \(Date())")
}
// Group Tasks
func stucturedConcurrencyWithTaskGroup() async {
await withTaskGroup(of: String.self) { group in
for i in 1...3 {
group.addTask {
try? await Task.sleep(nanoseconds: UInt64(i) * 1_000_000_000)
return "Group Task \(i) completed"
}
}
for await result in group {
print(result)
}
}
}
Task {
await stucturedConcurrencyWithTaskGroup()
}
// Task Cancellation
func taskWithCancellation() async {
let task = Task {
for i in 1...3 {
try? await Task.sleep(nanoseconds: 1_000_000_000)
try await Task.checkCancellation()
print("Task iteration \(i) completed")
}
return "Task completed"
}
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
task.cancel()
}
do {
let result = try await task.value
print(result)
} catch {
print("Task was canceled")
}
}
Task {
await taskWithCancellation()
}
Thread.sleep(forTimeInterval: 7)
The Building Block of Swift Concurrency
Fundamental Unit of Concurrent Work
Introduced: iOS 15
Changes
Swift 5.5: Introduction of Tasks - Proposal SE-0304
Swift 6: Improvements in Task Management and Task Isolation
Built-in Cancellation
Works well with async-await
Reducing common concurrency issues such as Thread explosion and so on
Managing multiple tasks
Limited backward compatibility
Complex scenarios such as cancellation and task groups
Organised and Efficient Task Management
𧡠Thread Safety: Structured concurrency ensures that all spawned tasks are properly handled within their scope.
π Task Lifespan Management: Automatically handle the lifecycle of tasks, cleaning up resources when tasks complete.
π Error Propagation: Any error in child tasks is automatically propagated up to the parent task, ensuring robust error handling.
private let baseURL = "https://picsum.photos/"
private(set) var images: [UIImage] = [UIImage]()
private func fetchImage(imageUrl: String) async throws -> UIImage {
do {
guard let url = URL(string: baseURL + imageUrl) else {
throw URLError(.badURL)
}
print("Val: \(imageUrl)")
let (data, _) = try await URLSession.shared.data(from: url, delegate: nil)
if let image = UIImage(data: data) {
print(image)
return image
} else {
throw URLError(.badURL)
}
} catch {
if let error = error as? URLError, error.code == URLError.cancelled {
print(error)
} else {
throw error
}
}
return UIImage()
}
func fetchMultipleImagesArray() async throws {
async let image100 = fetchImage(imageUrl:"100")
async let image101 = fetchImage(imageUrl:"101")
async let image102 = fetchImage(imageUrl:"102")
async let image103 = fetchImage(imageUrl:"103")
async let image104 = fetchImage(imageUrl:"104")
let asyncLetResult = try await ([image100, image101, image102, image103, image104])
await MainActor.run {
images.append(contentsOf: asyncLetResult)
}
}
Task {
do {
try await fetchMultipleImagesArray()
} catch {
print("Failed to download images: \(error)")
}
}
/// Output:
Val: 104
Val: 101
Val: 103
Val: 100
Val: 102
Image: UIImage:0x6000030018c0 anonymous {104, 104} renderingMode=automatic(original)
Image: UIImage:0x600003001830 anonymous {101, 101} renderingMode=automatic(original)
Image: UIImage:0x600003009170 anonymous {102, 102} renderingMode=automatic(original)
Image: UIImage:0x600003009200 anonymous {100, 100} renderingMode=automatic(original)
Image: UIImage:0x6000030017a0 anonymous {103, 103} renderingMode=automatic(original)
Organising Concurrent Tasks
Ensuring Safe and Predictable Task Management
Introduced: iOS 15
Changes
Swift 5.5: Introduction of Structured Concurrency - Proposal SE-0304
Swift 6: Enhanced support and refinements for structured concurrency.
Safety and Predictability
Integration with Async/Await, Async-let - Proposal SE-0317
Error Handling and Isolation of concurrent operations
Limited backward compatibility
Complexity in Large codebases
Coordination and Safety in Concurrency
𧩠Task Coordination: Orchestrate/Coordinate multiple tasks with controlled access to shared data.
π Data Isolation: Isolates internal state, allowing only one task at a time to modify it.
π‘ Concurrency Safety: Prevents data races by managing concurrent mutations effectively.
Class vs Actors
class Counter {
var value = 0
func increment() -> Int {
let currentValue = value
Thread.sleep(forTimeInterval: 0.1)
value = currentValue + 1
return value
}
}
let counter = Counter()
Task.detached {
print("Class πΎ \(counter.increment())")
}
Task.detached {
print("Class π₯ \(counter.increment())")
}
actor SafeCounter {
var value = 0
func increment() -> Int {
let currentValue = value
Thread.sleep(forTimeInterval: 0.1)
value = currentValue + 1
return value
}
}
let safeCounter = SafeCounter()
Task.detached {
print("Actor β½οΈ \(await safeCounter.increment())")
}
Task.detached {
print("Actor π \(await safeCounter.increment())")
}
Safeguarding Shared State
Isolating State to Prevent Data Races
Introduced: iOS 15
Changes
Swift 5: Not available
Swift 5.5: Introduction of Actors - Proposal SE-0306
Data safety and simplicity
Reference type that helps in concurrency environment
Integration with async/await and Task and helps in Isolation
Actor Reentrancy
Limited backward compatibility
Complexity in Large codebases
An actor is suspended during a task
Another task enters the actor while the first task is suspended
Causes by actorβs reentrant behaviour
A thread-safe type whose values can be shared across arbitrary concurrent contexts without introducing a risk of data races.
Sendable and Sendable Closures SE-0302
Sendable == Thread Safety
Introduced in Swift 5.7
Sendable is just a Protocol
Most base types of Swift Foundation are Sendable
Value Types
/// Value Types
struct User: Codable, Sendable {
let name: String
let age: Int
}
/// Functions and Closures- @Sendable
func fetchData(endPoint: String, resultHandler: @Sendable @escaping (Result<User, Error>) -> Void) {
resultHandler(.success(User(name: "Saba", age: 1)))
}
/// Reference types that internally manage access to their state
actor Account {
private var balance: Int = 0
func deposit(amount: Int) {
balance += amount
}
func getBalance() -> Int {
return balance
}
}
/// Reference types with no mutable storage
final class Address: Sendable {
let houseNo: Int
let streetName: String
let cityName: String
init(houseNo: Int, streetName: String, cityName: String) {
self.houseNo = houseNo
self.streetName = streetName
self.cityName = cityName
}
}
We will look into the Isolation in the upcoming articleβ¦
This is a free third party commenting service we are using for you, which needs you to sign in to post a comment, but the good bit is you can stay anonymous while commenting.