Young Min Sim
Young Min Sim

Reputation: 53

why dispatchqueue.global() still alive after deinit

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)


I will really appreciate it if someone teach me why

Upvotes: 1

Views: 1154

Answers (3)

Rob
Rob

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

Cruz
Cruz

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 enter image description here

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

matt
matt

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

Related Questions