How to prevent deallocated objects in GCD?

460 views Asked by At

This function is supposed to re-schedule the execution of a work item:

class MyClass {

    var id: String?
    var workItem: DispatchWorkItem?
    var isDoing = false

    func myFunction() {

        guard let id = self.id else { return }

        isDoing = true
        NotificationCenter.default.post(name: MyNotification, object: nil, userInfo: ["id": id])
        workItem?.cancel()

        workItem = DispatchWorkItem {
            self.isDoing = false
            NotificationCenter.default.post(name: MyNotification, object: nil, userInfo: ["id": id])
        }

        if let workItem = workItem { 
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(10), execute: workItem)
        }
    }
}

It works fine in development, but the design seems suspicious, so I ask some basic questions:

  1. Could workItem be nil, if workItem?.cancel() is called just before the queue tries to execute the workItem?
  2. Could id inside workItem ever be nil when workItem is executed or is it retained by the scoped let id = self.id?
  3. Could isDoing inside workItem be already deallocated when the workItem is executed if the MyClass object has been deallocated? In other words, what happens to the scheduled workItem when the MyClass object is deallocated?
1

There are 1 answers

2
mag_zbc On BEST ANSWER
  1. Not sure what you mean. You don't nil out workItem anywhere.

  2. No, it can't be nil since you're working with a local variable - a copy of self.id. By using guard you make sure that local variable id is not nil, and closure keeps a strong reference (by default) to captured values, so it won't be deallocated.

  3. isDoing is an instance variable of MyClass so it can't be deallocated before MyClass instance is deallocated. The problem is, in your case MyClass can't be deallocated because you're looking at a strong reference cycle. The closure, by default, keeps a strong reference to the value captured, and you are capturing self. And since self keeps a strong reference to workItem, which in turn keeps a strong reference to closure that captures self, hence the reference cycle.

In general, when capturing self you use a capture list to work with a weak reference to self and do a check whether it hasn't been deallocated

workItem = DispatchWorkItem { [weak self] in
    guard let strongSelf = self else { return }
    // do stuff with strongSelf
}