Concurrency Control
iOS Development with Swift - Concurrency Control
Section titled “iOS Development with Swift - Concurrency Control”Concurrency allows an application to perform multiple tasks seemingly at the same time. In iOS, this is crucial for keeping the user interface (UI) responsive while performing long-running operations like network requests, data processing, or complex calculations in the background. Without proper concurrency management, these tasks could freeze the UI, leading to a poor user experience.
Swift offers several mechanisms for managing concurrent tasks. We’ll primarily discuss Grand Central Dispatch (GCD) and the modern Swift Concurrency model (async/await).
Grand Central Dispatch (GCD)
Section titled “Grand Central Dispatch (GCD)”GCD is a low-level C-based API, exposed in Swift, for managing concurrent code and executing operations asynchronously. It works with ‘dispatch queues’, which are data structures that manage tasks in a First-In, First-Out (FIFO) order.
Think of a queue like a line at a movie ticket counter: the first person in line gets served first. Tasks submitted to a dispatch queue are executed based on the queue’s type and system resources.
Types of Dispatch Queues
Section titled “Types of Dispatch Queues”- Serial Queues: Execute one task at a time, in the order they are added. Each task must complete before the next one begins. Useful for protecting shared resources or when order of execution is critical.
- Concurrent Queues: Can execute multiple tasks simultaneously (up to the system’s core capacity). Tasks are still started in the order they are added, but they can run in parallel and finish in any order.
GCD provides several standard queues:
DispatchQueue.main: A serial queue that executes tasks on the application’s main thread. All UI updates MUST be performed on this queue.- Global Concurrent Queues: System-provided concurrent queues with different Quality of Service (QoS) classes (e.g.,
.userInitiated,.background,.utility,.userInteractive). QoS helps the system prioritize tasks.
You can also create custom serial or concurrent queues:
// Custom serial queuelet mySerialQueue = DispatchQueue(label: "com.myapp.mySerialQueue")
// Custom concurrent queuelet myConcurrentQueue = DispatchQueue(label: "com.myapp.myConcurrentQueue", attributes: .concurrent)Submitting Tasks to Queues
Section titled “Submitting Tasks to Queues”Tasks are submitted to queues as blocks of code (closures).
async: Submits a task to a queue and returns immediately (asynchronously). The calling thread doesn’t wait for the task to complete. This is used for offloading work to background threads.sync: Submits a task to a queue and waits for it to complete before returning (synchronously). Be cautious usingsyncon the main queue from the main thread, or on a serial queue from itself, as it can lead to deadlocks.
GCD Example: Background Task and UI Update
Section titled “GCD Example: Background Task and UI Update”A common pattern is to perform a long-running task on a background queue and then update the UI on the main queue:
func performBackgroundTask() { // Get a global concurrent queue (background priority) DispatchQueue.global(qos: .background).async { // Simulate a long-running task print("Background task started on thread: \(Thread.current)") Thread.sleep(forTimeInterval: 3) // Simulate work for 3 seconds let result = "Task Complete!" print("Background task finished.")
// Switch back to the main queue to update UI DispatchQueue.main.async { print("Updating UI on thread: \(Thread.current)") // self.myLabel.text = result // Example UI update } } print("performBackgroundTask method finished executing on thread: \(Thread.current)")}Swift Concurrency (async/await)
Section titled “Swift Concurrency (async/await)”Introduced in Swift 5.5, async/await provides a more modern and readable way to write asynchronous code. It allows you to write asynchronous operations as if they were synchronous, without complex nested closures (callback hell).
Functions marked with async can be ‘paused’ using await when calling other async functions. The system can use the thread for other work while the awaited function is executing.
Async/Await Example
Section titled “Async/Await Example”Here’s how you might fetch data and update the UI using async/await:
@available(iOS 13.0, *)func fetchData() async -> String? { // This function itself is async print("fetchData started on thread: \(Thread.current)") // Simulate a network request try? await Task.sleep(nanoseconds: 2 * 1_000_000_000) // Sleep for 2 seconds let fetchedData = "Data from server" print("fetchData finished on thread: \(Thread.current)") return fetchedData}
@available(iOS 13.0, *)func processAndDisplayData() { // Create a new Task to run asynchronous code from a synchronous context Task { print("processAndDisplayData Task started on thread: \(Thread.current)") if let data = await fetchData() { // UI updates should be on the MainActor await MainActor.run { print("Updating UI with data on thread: \(Thread.current)") // self.myLabel.text = data // Example UI update } } } print("processAndDisplayData method finished executing on thread: \(Thread.current)")}Key concepts in Swift Concurrency:
async: Marks a function as asynchronous.await: Pauses execution until anasyncfunction completes.Task: A unit of asynchronous work. You can create tasks to runasynccode.Actor: A reference type that protects its mutable state from concurrent access.MainActoris a special actor that represents the main thread, ensuring UI updates are safe.
While GCD is still fundamental and widely used, Swift Concurrency (async/await) is generally preferred for new asynchronous code in Swift due to its improved readability and safety. Understanding both is beneficial for an iOS developer.
Practice using these concurrency mechanisms, especially when dealing with network operations, file I/O, or any task that might take significant time, to keep your app’s UI smooth and responsive.