Deinit does not support async / @MainActor

I am using SwiftData without using an app and it does not save automatically when it quits (.autosaveEnabled==true), so I thought I could call .mainContext.save() from the deinit of the swiftdata-owner, but it has to run on the MainActor and deinit does not allow async.

Is there a way to set up a Task on the MainActor that is somehow blocked until the deinit of the swiftdata-owner runs?

So basically instead of running the save-operation from the deinit I want to unblock a Task that does the saving on the MainActor. The unblocking should be done from the deinit of the swiftdata-owner.

You could maybe do something like this?

deinit {
  let mainContext = self./* wherever it is */.mainContext
  Task { @MainActor in
    mainContext.save()
  }
}

The compiler denies access to container.mainContext in a deinit because is is MainActor isolated.

All you have to do is get something out of self so that you don't capture self in the task. If you can say let container = self.container, and then access container.mainContext in the task, that works too.

Also, wasn't isolated deinit discussed at one point? Did that go anywhere?

Ok, thanks, I tried

    deinit {
        let thecontainer = container
        Task { @MainActor [thecontainer] in
            try! thecontainer.mainContext.save()
            print("Did it…")
        }
    }

But it does not run the task... :-/ Seems like the Task in the deinit doesn't make it before the program quits? Or is there a bug in my code? (I am new to Swift concurrency).

Adding sleep(…) does not help either. The deinit runs, but the Task does not run even if it has several seconds to catch up before the program quits.

Is it legal to create new Tasks from deinit?

Turns out that the Task does not run regardless of whether it appears in deinit or not, but it does run if it is placed before MainActor.run has been executed like this:

Task {
    print("hello")
}
try await MainActor.run { 
   …
}

I am sure there is a good explanation for this?

Solved:

I had to add a dependency on the Task that was created in the deinit and wait on it at the end of the program.

Like this:

// global
public var pending:Task<(), Never>? = nil
…
    deinit {
        let thecontainer = container
        pending = Task { @MainActor [thecontainer] in
                …
        }
    }
…
// at the end of the main function of the program
_ = await pending?.result
1 Like