WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

How to properly cancel Tasks in a ViewModel when a ViewController is deinitialized

Forums > Swift

Hello there,

I write this question because I can't really find an answer. As a lot of developers, since the release of Xcode 13.2, I'd like to use the Swift Concurrency in my company projects (at last :D).

However, I'm facing an architecture problem when it comes to cancel all tasks currently running (not really cancel them, but I'd want to get the isCancel boolean to true to respect the "Cooperative Cancellation" process). The problem I'm trying to figure out is : how can I cancel my tasks running in a ViewModel when the ViewController is deinitialized ?

I can summarize the question with the code below :

// ViewController
class SomeViewController: UIViewController {

  var viewModel: SomeViewModel?

  deinit {
    print("SomeViewController instance deinit")
  }

  override func viewDidLoad() {
    super.viewDidLoad()
    viewModel?.doAsyncStuff()
  }
}

// ViewModel
class SomeViewModel {

  deinit {
    print("SomeViewModel instance deinit")
  }

  func doAsyncStuff() {
    Task {
      print("Begin async stuff")
      try await Task.sleep(nanoseconds: 1_000_000_000 * 5) // 5 seconds
      simulateAPICall()
      print("Async stuff has been done successfully")
    }
  }

  func simulateAPICall() {
    // API Call and update the ViewModel properties
  }
}

Console result :

Begin async stuff  // Async code is called
SomeViewController instance deinit  // SomeViewController instance is not shown anymore
Async stuff has been done successfully  // SomeViewModel instance is retained in memory until the task has finished
SomeViewModel instance deinit  // Now that the task has finished, the ViewModel can be automatically deinit

In this example, when the SomeViewController instance is not shown anymore (dismissed, pop, etc.) it is well deinitialized and does not retain anything in memory.

However, the SomeViewModel is implicitly retained in memory because of the Task (it's normal because the Tasks behave like this, they need to retain anything they use to perform their functions until they've finished). And the problem is here. For example, if I use a router at the end of the Task to show another ViewController, then the router stuff will be executed even if the SecondViewController that have asked the initial async stuff doesn't exist anymore.

I don't know how to inform the Task that it needs to be cancelled without doing a boilerplate? I'd have liked to make a system like RxSwift / Combine and their Disposable / Cancellable, so that I don't need to call the cancel() Task function manually. An explicit code can be easily forgotten or broken in a project life.

The problem might also exists with SwiftUI when we don't use a modifier that implicitly cancel tasks when the view is not shown anymore, like the task modifier.

1      

This is very good question and recently i came across this scenario, as of now i can think one solution is to keep reference to your task as handle then cancel when Viewcontroller about to dismiss for example

....
var task: Task<(), Never>?

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        task?.cancel()
    }
    ....

   

I am curious, if a view controller has some Task properties, do these tasks not get cancelled automatically on the view controller's deinit?

   

When a View is dismissed the tasks are automatically requested to cancel, although they may not do so.

Here is an explanation of how to cancel tasks, and the effects and other actions.

This makes sense. Consider a View that requests some data from a URL, using Task Async Await. If the View is dismissed, then the data is no longer needed, and the task cancellation requested. The Task, however, may ignore the request if there would be some negative ramifications for your app's operation.

   

Hacking with Swift is sponsored by Emerge

SPONSORED Why are Swift reference types bad for app startup time, and what’s the performance cost of protocol conformances? That’s just a couple of the topics you can learn about on the Emerge blog — written by the app performance experts behind Emerge’s advanced app optimization and monitoring tools, based on their experience of working at companies like Apple, Airbnb, Snap, and Spotify.

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.