I know that you have to balance out the calls with DispatchGroup
for both .enter()
and .leave()
.
The question is if I started a dispatchGroup.enter()
and I dismissed/popped
the vc before the calls were balanced, would the app crash? Basically, would deinit()
change the outcome because the vc is no longer in memory?
eg. This starts, but before the 3rd background task is completed, the vc is dismissed or popped. .leave()
only ran twice but it's supposed to run 3 times. This is just a simple example to get the idea across:
func viewDidLoad()
let group = DispatchGroup()
// there are 3 background tasks
for task in backgroundTasks {
group.enter()
someBackgroundTask(... completion: { (_) in
group.leave()
})
}
group.notify...
}
No, failing to call leave
will not, itself, cause a crash. (It likely is not relevant here, but calling leave
too many times will crash.)
What will happen, though, is that until all of the enter
calls are offset by leave
calls, the dispatch group will not be released. Worse, anything captured strongly by the completion handler and the notify
closures will not get released. So you really do want to ensure that all enter
calls are eventually offset by leave
calls.
But I would first suggest confirming that leave
is not getting called enough times. Typically the exact opposite problem occurs, that leave
is called the correct number of times, but that one or more of these background tasks finish after the view controller was dismissed, and does not handle this scenario gracefully.
For what it is worth, if someBackgroundTask
is doing something that is no longer needed after the view controller is dismissed, we would:
make someBackgroundTask
a cancelable task, if possible;
make sure the various closures do not maintain a strong reference back to the view controller (by using [weak self]
pattern), eliminating strong reference cycles;
make sure the closure gracefully handles if the view controller is deallocated by the time the closure runs, e.g.
group.notify(queue: .main) { [weak self] in
guard let self = self else { return }
...
}
and
in deinit
of the view controller (or somewhere equivalent) cancel the tasks that are no longer needed (again, assuming it is a cancelable task).