Reputation: 53
deinit {
print("deinit")
}
DispatchQueue.global().async { [weak self] in
while(true){
print(self)
print("sleep")
sleep(1)
}
}
Although deinit
in class is called,
infinite loop in DispatchQueue.global()
is still alive.
In that case, for example,
Optional(<OutOfKiosk.DialogFlowPopUpController: 0x150116e00>)
sleep
Optional(<OutOfKiosk.DialogFlowPopUpController: 0x150116e00>)
sleep
deinit (after deinit)
nil
sleep
nil
sleep
...(repeat)
Upvotes: 1
Views: 1154
Reputation: 437432
GCD manages the queues (and tasks dispatched to those queues) independent of your object’s life cycle.
FWIW, the fact that you’re using a global queue is not relevant to the question at hand. Using a custom dispatch queue (or an operation queue) results in the exact same behavior:
let queue = DispatchQueue(label: "private_queue")
queue.async { [weak self] in
while true {
print(self)
print("sleep")
sleep(1)
}
}
Dispatched tasks, regardless of queue, with while
loop will continue to execute unless you explicitly exit the loop (either with a self == nil
test, or, if you want to manually cancel, with isCancelled
).
GCD does not perform preemptive cancellations.
Note, it’s not even relevant that this dispatch task is currently running. It only matters whether the dispatched task has finished. The while
loop is irrelevant. Consider:
let queue = DispatchQueue(label: "private_queue")
for i in 0 ..< 100 {
queue.async { [weak self] in
print("iteration \(i) queued by \(self)")
Thread.sleep(forTimeInterval: 1)
}
}
print("done dispatching")
This enqueues 100 tasks on that custom serial queue. But if self
is deallocated before all of those tasks finish, this queue will continue to process these 100 dispatched tasks.
Bottom line, while we can talk about GCD’s management of the global queues, that’s not the salient issue. The key observations are:
A task added to a dispatch queue (or an operation added to an operation queue) effectively keeps a strong reference to its respective queue;
A dispatched work item (or operation) will continue to execute regardless of fact that the object that enqueued it may have been deallocated (unless, of course, you test whether self == nil
); and
Even if a dispatched task (or operation) has not yet started, it will remain in that queue until it finishes execution (or you manually cancel it) ... again, it is irrelevant whether the object that enqueued it has been deallocated or not.
Upvotes: 2
Reputation: 2632
DispatchQueue.global() returns the global system queue. https://developer.apple.com/documentation/dispatch/dispatchqueue/2300077-global
GCD manages a shared thread pool, decides and adds blocks of code to global dispatch queue to execute it.
In Debug Memory Graph, You can figure out many dispatch queues alive
your execution executed on dispatch queue is not related to DialogFlowPopUpController instance's deinit
// your execution should not be completed because there are no break statement
{ [weak self] in
while(true){
print(self)
print("sleep")
sleep(1)
}
}
How about change your execution to break infinity loop
DispatchQueue.global().async { [weak self] in
while(self != nil){
print(self)
print("sleep")
sleep(1)
}
}
Upvotes: 0
Reputation: 534950
The global dispatch queue is not "in" your app's class. It is "in" the system. You've launched an independent thread and it just keeps running until its operation is over, which in this case is never. That has nothing at all do with whether your class gets a deinit
and the instance ceases to exist.
Indeed, a common mistake is launching an independent thread which, some time later, refers to your instance. If your instance has meanwhile had its deinit
called, all sorts of terrible things can happen, ranging from a crash to keeping your instance alive in an indeterminate state after deinit
.
However, that's not happening here; you have correctly used weak self
so your instance has indeed gone out of existence in good order, as the nil
tells you. So what you are seeing is the expected behavior, though obviously it's not a very good thing to do in real life.
Upvotes: 2