Task.detached in Swift


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.

  1. Tasks can inherit task local values, and detached tasks can’t do that.
  2. The task will inherit its parent task priority.
  3. 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!