paint-brush
Tricks for Using Async/Await in iOS Developmentby@ze8c
517 reads
517 reads

Tricks for Using Async/Await in iOS Development

by Maksym SytyiFebruary 26th, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

This article explores the usage of async/await in iOS development, a powerful paradigm for handling asynchronous code. It covers basic usage, error handling, running async code on the main thread, and advanced techniques like calling asynchronous functions in synchronous code and running multiple queries in parallel. Async/await simplifies asynchronous programming in Swift, enhancing app responsiveness and efficiency.
featured image - Tricks for Using Async/Await in iOS Development
Maksym Sytyi HackerNoon profile picture


Asynchronous programming has become a cornerstone of modern app development. In the iOS ecosystem, Apple introduced native support for asynchronous programming with Swift, including the powerful async/await pattern. In this article, we'll delve into the usage of async/await in iOS development


Understanding async/await

Async/await is a programming paradigm that streamlines the execution of asynchronous code, making it appear more synchronous and readable. It eliminates the complexities of managing callbacks or completion handlers, allowing developers to write asynchronous code that same as synchronous code. With Swift's async/await, developers can efficiently handle tasks such as network requests, database queries, or file I/O without blocking the main thread


Basic Usage of async/await in iOS:

  1. Define an async Function: To create an asynchronous function, simply use the async keyword in the function declaration. This signifies that the function will execute asynchronous tasks.


    func fetchData() async -> Data {
        // async operations
    }
    


  2. Use await Inside an async Function: Within an async function, utilize the await keyword to call other asynchronous functions or tasks. It suspends the current task until the awaited task is completed.


    func fetchImageData() async -> Data {
        let url = URL(string: "https://example.com/image.jpg")!
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            return data
        } catch {
            // Handle errors
        }
    }
    


  3. Mark the Caller as async: When calling an async function from another function, mark the calling function as async as well. This facilitates the use of await for calling async functions.


    func displayImage() async {
        let imageData = await fetchImageData()
        let image = UIImage(data: imageData)
        // Update UI with the image
    }
    


  4. Error Handling: Error handling with async/await is straightforward. Utilize try and catch blocks to handle errors thrown within async functions.


    func fetchImageDataWithThrowing(stringURL: URL) async throws -> Data {
        guard let url = URL(string: stringURL)
        else { throw NSError(domain: "Bad URL", code: 0) }
        
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }
    
    func displayData() async {
        let url = "https://example.com/image.jpg"
        do {
            let data = try await fetchImageDataWithThrowing(stringURL: url)
            // Process data
        } catch {
            // Handle errors
        }
    }
    


  5. Running async Code on the Main Thread: To update the UI on the main thread within async code, use await to dispatch code to the main queue.


    Task {
        await MainActor.run {
            // Update UI
        }
    }
    


    or


    Task { @MainActor in
        // Update UI
    }
    



Advanced Use Cases:

  1. Calling an Asynchronous Function in Synchronous Code: Utilize the Task structure to denote the scope of an asynchronous call within synchronous code.


    func upadeImg() {
        Task {
            let image = await fetchImageData()
            MainActor.run {
                // Update UI
            }
        }
    }
    


  2. Converting a Function with a Callback to Asynchronous: To create an asynchronous function, simply use the construct like withCheckedContinuation or withCheckedThrowingContinuation. This may help you if you want to use async/await at the network layer with iOS before version 15 and after 13.


    extension URLSession {
        func asyncDataRequest(url: URL) async throws -> Data {
            try await withCheckedThrowingContinuation { next in
                dataTask(with: url, completionHandler: { data, response, error in
                    if let error {
                        next.resume(throwing: error)
                    } else if let data {
                        next.resume(returning: data)
                    } else {
                        next.resume(throwing: NSError(domain: "Empty data", code: 0))
                    }
                }).resume()
            }
        }
    }
    


  3. Running Multiple Queries in Parallel: Use constructs like async let or withTaskGroup or withThrowingTaskGroup to execute multiple asynchronous methods concurrently, reducing lines of code and improving efficiency.


    func fetchUser() async -> User {
        async let imageData = await fetchImageData()
        async let fullName = await fetchUserFullName()
        
        return await User(avatar: imageData, fullName: fullName)
    }
    


    or withTaskGroup


    func fetchImages(imageID: [String]) async -> [Data] {
        await withTaskGroup(of: Data.self) { group in
            imageID.forEach { id in
                group.addTask {
                    await fetchImageData(byID: id)
                }
            }
            
            return await group.reduce(into: [Data]()) { tmpResult, imageData in
                tmpResult.append(imageData)
            }
        }
    }
    


Conclusion

Async/await in Swift presents a powerful and readable approach to asynchronous programming in iOS applications. By following the outlined steps, developers can simplify their asynchronous code, improve app responsiveness, and leverage Swift's robust asynchronous programming capabilities to create more efficient and responsive iOS apps