← All topics/Concurrency

Practical interview questions

Scenario-style prompts with sample answer outlines. Focus is on how you would design and reason in real codebases.

Question 6

Task lifecycles in UIKit / SwiftUI

How do you tie the lifecycle of async tasks to a view (e.g. SwiftUI view or UIViewController) so you don't leak work or update deallocated UI?

Answer outline

SwiftUI: prefer .task { } on the view. This creates structured work that is cancelled when the view leaves the hierarchy (and can restart when id changes).

Avoid orphan Task.detached from views.

UIKit: store Task? (or a small coordinator) on the view controller, cancel() in viewWillDisappear / deinit, and [weak self] (or Task.checkCancellation) before touching UI after await.

Leaks happen when Task.detached or unowned self outlives the VC; structured children tied to a stored Task you cancel fix most cases. After await, assume self may be nilguard weak self.

Principles

  • SwiftUI: use .task, not onAppear { Task { } }.task auto-cancels when the view leaves the hierarchy; onAppear does not.
  • .task(id:) re-runs and cancels the previous task whenever id changes — the right tool for selection-driven loads.
  • UIKit: store the handle, cancel on disappear — viewWillDisappear cancels in-flight work; deinit is your safety net.
  • Always [weak self] after await — the task may still be running when the VC is gone.
SwiftUI — structured .task
struct ProfileView: View {
    @StateObject private var model = ProfileModel()
    let userId: String

    var body: some View {
        VStack {
            // ...
        }
        .task(id: userId) {
            await model.load(userId: userId)
        }
    }
}
UIKit — store handle, cancel on disappear
final class FeedViewController: UIViewController {
    private var loadTask: Task<Void, Never>?

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        loadTask?.cancel()
        loadTask = Task { [weak self] in
            guard let self else { return }
            await self.viewModel.load()
        }
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        loadTask?.cancel()
        loadTask = nil
    }
}