Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly deal with a deallocated delegate of a queued nsoperation

In my current project, several view controllers (like vc) spawn NSOperation objects (like operation) that are executed on a static NSOperationQueue. While the operation is waiting or running, it will report back to the view controller via delegation (operation.delegate = vc, assigned not retained).

These operations can take a while though, and in the mean time the app can dealloc the view controller (by popping them of a navigation controller's stack).

So far everything is intentional. The class containing the static NSOperationQueue has a way to get back at the operations, therefore the view controllers do not retain them. They're just alloc/init/autoreleased and put on the queue.

Now this also causes the problem. After the view controller deallocates, any calls to the NSOperation's spirited delegate will cause a bad access violation. From what I understand, it is not possible to check whether an object at a pointer has been deallocated, as stated in this question.

One fix I can think of is retaining the operation and setting the operation.delegate to nil on dealloc. But that'd be my least popular fix, for it would introduce a lot of extra ivars/properties to keep track of.

My question therefore is, are there other ways to work around this problem and if so, could you sketch one here?

Cheers,
EP.

SOLUTION: The approach that worked out best for me was a slight variation to Guiliano's answer:

  • Implementing every delegate protocol in the queue manager is not feasible (20+ different protocols with 50+ methods), so I kept the direct delegate assignments. What I did change was the class making the assign call. This used to be the class (and delegate) that created the request, but now it is offloaded to the queue manager.

  • The queue manager, next to assigning the delegate to the operation, also holds a secondary mutable dictionary to keep track of the delegate/operation pairs.

  • Every delegate instance calls a [QueueManager invalidateDelegate:self] method on deallocation, which then looks up the request that belonged to the delegate and nils it. The dictionary operation/delegate pair is then also deleted to allow proper deallocation of the operation.

  • Lastly with KVO observing the isFinished property of each operation, the mutable dict is kept clean, to make sure that all operation retain counts actually deallocate after they're finished.

Thanks Guiliano for providing the hint to using KVO for cracking this!

like image 324
epologee Avatar asked May 06 '11 15:05

epologee


1 Answers

I would suggest to review your architecture and move the delegate to the class (assume QueueManager) that manages the queue instead of having a delegate in each operation:

  • Create a QueueManagerDelegate that implements the method(s) you need to notify the viewControllers

  • In QueueManager add a KVO observer for the isFinished property of each NSOperation (do this before adding the operation to the queue ;))

  • In the callback of the KVO call the delegate method(s) you need only if delegate is != nil

  • Add an invalidate method to QueueManager and call this method in the dealloc method of your UIViewController(s)

    -(void)invalidate { self->delegate = nil; }

in case you need a refresh on KVO: Kvo programming guide

like image 156
Giuliano Galea Avatar answered Nov 15 '22 19:11

Giuliano Galea