Greetings, traveler!
We often use DispatchQueue to perform tasks on a specific thread serially or concurrently. Expecting different code bases, we can notice that some developers capture weak references inside the DispatchQueue closure, and some developers don’t do that. Let’s figure out who is right.
We can ask someone who didn’t capture weak references directly. The most popular answers will be:
- I forgot to do this
DispatchQueuedoesn’t cause retain cycles.
Since the first answer is absolutely clear, let’s inspect the second one a bit deeper.
It’s true that DispatchQueue doesn’t cause retain cycles itself. But it can be caused via another closure that can be used inside of it. Check out this example.
class Service {
func getData(_ completion: @escaping () -> Void) {
// do something...
}
}
class Client {
let service = Service()
func firstAction() {
DispatchQueue.main.async {
self.service.getData {
self.secondAction()
}
}
}
private func secondAction() {
// do something...
}
}In this example, self will be retained by the DispatchQueue.main.async closure and the services method getData closure. We can fix it like this:
func firstAction() {
DispatchQueue.main.async { [weak self] in
self?.service.getData {
self?.secondAction()
}
}
}Or like this:
func firstAction() {
DispatchQueue.main.async {
self.service.getData { [weak self] in
self?.secondAction()
}
}
}
Delayed allocation
While using DispatchQueue can free us of worrying about retain cycles, there is something that we should keep in mind. If we don’t use weak reference capture inside this closure, we can cause a delayed allocation. That means the parent object will be released from memory only after executing this task. So, if we don’t want such behavior, we should use weak reference capture.
Conclusion
When we write code, it’s essential to understand the reasoning behind the decision to write it in a certain way.
