Tasks in Swift explained with code examples – onlinecode

Tasks in Swift explained with code examples – onlinecode

In this post we will give you information about Tasks in Swift explained with code examples – onlinecode. Hear we will give you detail about Tasks in Swift explained with code examples – onlinecodeAnd how to use it also give you demo for it if it is necessary.

Tasks in Swift are part of the concurrency framework introduced at WWDC 2021. A task allows us to create a concurrent environment from a non-concurrent method, calling methods using async/await.

When working with tasks for the first time, you might recognize familiarities between dispatch queues and tasks. Both allow dispatching work on a different thread with a specific priority. Yet, tasks are quite different and make our lives easier by taking away the verbosity from dispatch queues.

Architecting SwiftUI apps with MVC and MVVMAlthough you can create an app simply by throwing some code together, without best practices and a robust architecture, you’ll soon end up with unmanageable spaghetti code. Learn how to create solid and maintainable apps with fewer bugs using this free guide.

If you’re new to async/await, I recommend first reading my article Async await in Swift explained with code examples.

How to create and run a Task

Creating a basic task in Swift looks as follows:

let basicTask = Task {
    return "This is the result of the task"
}

As you can see, we’re keeping a reference to our basicTask which returns a string value. We can use the reference to read out the outcome value:

let basicTask = Task {
    return "This is the result of the task"
}
print(await basicTask.value)
// Prints: This is the result of the task

This example returns a string but could also have been throwing an error:

let basicTask = Task {
    // .. perform some work
    throw ExampleError.somethingIsWrong
}

do {
    print(try await basicTask.value)
} catch {
    print("Basic task failed with error: (error)")
}

// Prints: Basic task failed with error: somethingIsWrong

In other words, you can use a task to produce both a value and an error.

How do I run a task?

Well, the above examples already gave away the answer for this section. A task runs immediately after creation and does not require an explicit start. It’s important to understand that a job is executed directly after creation since it tells you only to create it when its work is allowed to start.

Performing async methods inside a task

Apart from returning a value or throwing an error synchronously, tasks also execute async methods. We need a task to perform any async methods within a function that does not support concurrency. The following error might be familiar to you already:

‘async’ call in a function that does not support concurrency

‘async’ call in a function that does not support concurrency is a common error in Swift.

In this example, the executeTask method is a simple wrapper around another task:

func executeTask() async {
    let basicTask = Task {
        return "This is the result of the task"
    }
    print(await basicTask.value)
}

We can solve the above error by calling the executeTask() method within a new Task:

var body: some View {
    Text("Hello, world!")
        .padding()
        .onAppear {
            Task {
                await executeTask()
            }
        }
}

func executeTask() async {
    let basicTask = Task {
        return "This is the result of the task"
    }
    print(await basicTask.value)
}

The task creates a concurrency supporting environment in which we can call the async method executeTask(). Interestingly, our code executes even though we didn’t keep a reference to the created task within the on appear method, bringing us to the following section: cancellation.

Handling cancellation

When looking at cancellation, you might be surprised to see your task executing even though you didn’t keep a reference to it. Publisher subscriptions in Combine require us to maintain a strong reference to ensure values get emitted. Compared to Combine, you might expect a task to cancel as well once all references are released.

However, tasks work differently since they run regardless of whether you keep a reference. The only reason to keep a reference is to give yourself the ability to wait for a result or cancel the task.

Cancelling a task

To explain to you how cancellation works, we’re going to work with a new code example which is loading an image:

struct ContentView: View {
    @State var image: UIImage?

    var body: some View {
        VStack {
            if let image = image {
                Image(uiImage: image)
            } else {
                Text("Loading...")
            }
        }.onAppear {
            Task {
                do {
                    image = try await fetchImage()
                } catch {
                    print("Image loading failed: (error)")
                }
            }
        }
    }

    func fetchImage() async throws -> UIImage? {
        let imageTask = Task { () -> UIImage? in
            let imageURL = URL(string: "https://source.unsplash.com/random")!
            print("Starting network request...")
            let (imageData, _) = try await URLSession.shared.data(from: imageURL)
            return UIImage(data: imageData)
        }
        return try await imageTask.value
    }
}

The above code example fetches a random image and displays it accordingly if the request succeeds.

For the sake of this demo, we could cancel the imageTask right after its creation:

func fetchImage() async throws -> UIImage? {
    let imageTask = Task { () -> UIImage? in
        let imageURL = URL(string: "https://source.unsplash.com/random")!
        print("Starting network request...")
        let (imageData, _) = try await URLSession.shared.data(from: imageURL)
        return UIImage(data: imageData)
    }
    // Cancel the image request right away:
    imageTask.cancel()
    return try await imageTask.value
}

The cancellation call above is enough to stop the request from succeeding since the URLSession implementation performs cancellation checks before execution. Therefore, the above code example is printing out the following:

Starting network request...
Image loading failed: Error Domain=NSURLErrorDomain Code=-999 "cancelled"

As you can see, our print statement still executes. This print statement is a great way to demonstrate how to implement cancellation checks using one of the two static cancellation check methods. The first one stops executing the current task by throwing an error when a cancellation is detected:

let imageTask = Task { () -> UIImage? in
    let imageURL = URL(string: "https://source.unsplash.com/random")!

    /// Throw an error if the task was already cancelled.
    try Task.checkCancellation()

    print("Starting network request...")
    let (imageData, _) = try await URLSession.shared.data(from: imageURL)
    return UIImage(data: imageData)
}
// Cancel the image request right away:
imageTask.cancel()

The above code results print:

Image loading failed: CancellationError()

As you can see, both our print statement and network requests don’t get called.

The second method we can use gives us a boolean cancellation status. By using this method, we allow ourselves to perform any additional cleanups on cancellation:

let imageTask = Task { () -> UIImage? in
    let imageURL = URL(string: "https://source.unsplash.com/random")!

    guard Task.isCancelled == false else {
        // Perform clean up
        print("Image request was cancelled")
        return nil
    }

    print("Starting network request...")
    let (imageData, _) = try await URLSession.shared.data(from: imageURL)
    return UIImage(data: imageData)
}
// Cancel the image request right away:
imageTask.cancel()

In this case, our code only prints out the cancellation statement.

Performing regular cancellation checks is essential to prevent your code from doing unnecessary work. Imagine an example in which we would transform the returned image; we should’ve probably added multiple checks throughout our code:

let imageTask = Task { () -> UIImage? in
    let imageURL = URL(string: "https://source.unsplash.com/random")!

    // Check for cancellation before the network request.
    try Task.checkCancellation()
    print("Starting network request...")
    let (imageData, _) = try await URLSession.shared.data(from: imageURL)

    // Check for cancellation after the network request
    // to prevent starting our heavy image operations.
    try Task.checkCancellation()

    let image = UIImage(data: imageData)

    // Perform image operations since the task is not cancelled.
    return image
}

We are in control in regards to cancellation, making it easy to make mistakes and perform unnecessary work. Keep an eye sharp when implementing tasks to ensure your code regularly checks for cancellation states.

Setting the priority

Each task can have its priority. The values we can apply are similar to the quality of service levels we can configure when using dispatch queues. The low, medium, high priorities look similar to priorities set with operations.

We can manage the order of tasks execution by setting the priority

Each priority has its purpose and can indicate that a job is more important than others. There is no guarantee your task indeed executes earlier. For example, a lower priority job could already be running.

Configuring a priority helps prevent a low-priority task from avoiding the execution of a higher priority task.

The thread used for execution

By default, a task executes on an automatically managed background thread. Through testing, I found out that the default priority is 25. Printing out the raw value of the high priority shows a match:

(lldb) p Task.currentPriority
(TaskPriority) $R0 = (rawValue = 25)
(lldb) po TaskPriority.high.rawValue
25

You can set a breakpoint to verify on which thread your method is running:

By using a breakpoint, you can check the thread on which the task is running.

Continuing your journey into Swift Concurrency

The concurrency changes are more than just async-await and include many new features that you can benefit from in your code. Now that you’ve learned about the basics of tasks, it’s time to dive into other new concurrency features:

Architecting SwiftUI apps with MVC and MVVMAlthough you can create an app simply by throwing some code together, without best practices and a robust architecture, you’ll soon end up with unmanageable spaghetti code. Learn how to create solid and maintainable apps with fewer bugs using this free guide.

Conclusion

Tasks in Swift allow us to create a concurrent environment to run async methods. Cancellation requires explicit checks to ensure we do not perform any unnecessary work. By configuring the priority of our tasks, we can manage the order of execution.

If you like to learn more tips on Swift, check out the Swift category page. Feel free to contact me or tweet me on Twitter if you have any additional suggestions or feedback.

Thanks!

<!– Disable cross link to Medium



Also published on Medium.

–>

 

Hope this code and post will helped you for implement Tasks in Swift explained with code examples – onlinecode. if you need any help or any feedback give it in comment section or you have good idea about this post you can give it comment section. Your comment will help us for help you more and improve us. we will give you this type of more interesting post in featured also so, For more interesting post and code Keep reading our blogs

For More Info See :: laravel And github

We're accepting well-written guest posts and this is a great opportunity to collaborate : Contact US