Greetings, traveler!
We continue to study our course on Swift Concurrency. This article will examine the difference between a task and a detached task. The answer to this question is straightforward. Detached tasks don’t inherit anything from the caller. But what can a task inherit from a caller? Let’s take a closer look.
- Tasks can inherit task local values, and detached tasks can’t do that.
- The task will inherit its parent task priority.
- Tasks will inherit the actor’s context, while detached tasks won’t.
Let’s explore the last point more deeply. We can use a UIViewController as an example. Since this class is marked with the @MainActor attribute, all its asynchronous code will be executed on the main thread.
final class ViewController: UIViewController {
private let imageView = UIImageView()
private func configureImageView() {
Task {
do {
let image = try await loadImage(url: URL(string: "https://picsum.photos/200/300")!)
imageView.image = image
}
}
}
private func loadImage(url: URL) async throws -> UIImage {
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: url))
return UIImage(data: data) ?? .init()
}
}
What will happen if we try to create a task via Task.detached? First, the compiler will ask us to reference ‘self.’ explicitly or capture ‘self’ explicitly to enable implicit ‘self’ in this closure.
final class ViewController: UIViewController {
private let imageView = UIImageView()
private func configureImageView() {
Task.detached {
do {
let image = try await loadImage(url: URL(string: "https://picsum.photos/200/300")!) // ❌ Compile error: Reference to property 'imageView' in closure requires explicit use of 'self' to make capture semantics explicit
imageView.image = image // ❌ Compile error: Reference to property 'imageView' in closure requires explicit use of 'self' to make capture semantics explicit
}
}
}
private func loadImage(url: URL) async throws -> UIImage {
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: url))
return UIImage(data: data) ?? .init()
}
}
Alright, we will do it. But the compiler will then notify us about a new error:
private func configureImageView() {
Task.detached { [self] in
do {
let image = try await loadImage(url: URL(string: "https://picsum.photos/200/300")!)
imageView.image = image // ❌ Compile error: Main actor-isolated property 'image' can not be mutated from a Sendable closure
}
}
}
This is all because of thread mismatching. And this is pretty convenient, so we are much less likely to make a mistake.
Alright then! Since we have touched on the topic of actors, let’s continue discussing them in more detail in the next article. See you there!