I am trying to learn ARC and I'm having a hard time with a weakly captured self. My project is using MVVM with SwiftUI. I'm presenting a sheet (AuthenticationLoginView) that has a @StateObject var viewModel = AuthenticationLoginViewModel()
property. On dismiss of the presented sheet, I expect that the viewModel will have it's deinit
called and so it does until I run an asynchronous function within a Task
block.
class AuthenticationLoginViewModel: ObservableObject {
@Published var isLoggingIn: Bool = false
private var authenticationService: AuthenticationService
private var cancellables: Set<AnyCancellable> = Set()
private var onLoginTask: Task<Void, Never>?
init(authenticationService: AuthenticationService) {
self.authenticationService = authenticationService
}
deinit {
onLoginTask?.cancel()
LoggerService.log("deallocated")
}
public func onLogin() {
guard !isLoggingIn else { return }
isLoggingIn = true
onLoginTask = Task { [weak self] in
await self?.login()
}
}
private func login() async {
LoggerService.log("Logging in...")
sleep(2)
//
// self is still allocated here <<<---- ???
//
let authResponse = try? await self.authenticationService.authenticate(username: username, password: password)
LoggerService.log(self.isLoggingIn) // <<--- prints `true`
handleLoginResponse(authResponse: authResponse)
}
}
So I have my two cases here:
Case #1
- I present the sheet.
- I dismiss the sheet.
- The
deinit
function is getting called (app logs: "deallocated")
Case #2
- I present the sheet.
- I press the login button so the
onLogin
function is getting called. - I dismiss the sheet before the
sleep(2)
ends. - ---- I EXPECT the "deallocated" message to be printed from
deinit
and the logging atLoggerService.log(self.isLoggingIn)
to printnil
and theself.authenticationService.authenticate(...
to never be called as self doesn't exist anymore. - Not expected but happening: the app prints "Logging in", sleeps for 2 seconds, calls the service, prints true, and then deallocates the view model (the view was dismissed 2 seconds ago)
What am I doing wrong?
I'm still learning and I'm pretty much unsure if this is normal or I miss something. Anyway, I expect the view model to be deallocated as the view referencing it was dismissed.
CodePudding user response:
At the time you call onLogin
the reference to self
is valid and so the Task commences.
After that, the reference to self
in login
keeps self
alive. The Task has a life of its own, and you did not cancel it.
Moreover the use of sleep
is wrong, as it is not cancellable in any case, so neither is your Task. Use Task.sleep
.