How to properly deal with a deallocated delegate of a queued nsoperation
Asked Answered
H

3

11

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!

Haldi answered 6/5, 2011 at 15:46 Comment(0)
M
7

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

Mcbryde answered 6/5, 2011 at 16:40 Comment(7)
+1 Second that. It makes "reconnecting" to the vc easier too, in case it will appear again and needs to respond to changes in the queue (and its contents).Horgan
Very interesting approach. It will make the QueueManager (that indeed exists) a fair bit more complex, but it will also make the delegation more robust. I'll let it marinate a bit but it sounds like the way to go.Haldi
If you need some sample code look in the FetchedImageLinker sample code from Apple documentation. There is a class named WatchedOperationQueue (or something like that, dont have the doc here) that implements the described behaviour.Mcbryde
Couldn't find the sources, but I get the gist. One thing is new to me though, why would you use self->delegate = nil instead of just assigning nil via self.delegate = nil?Haldi
the difference between the two syntaxes is that with self->delegate you are directly accessing the ivar, self.delegate is just syntactic sugar that will call the setter setDelegate: if you declare the delegate as assign and use the default accessor's implementation the two are the sameMcbryde
I added the final solution to the question, it was 90% of what you suggested @Guilano, thanks!Haldi
Hey Giuliano, thanks for the wonderful answer! I was having trouble finding the sample code package that you mentioned. Is there perhaps another name for that package?Hazen
M
0

The best advice here is to review the architecture of the app to avoid such situations. However, if there current code can't be changed some-why, you can use NSNotificationCenter. Every time your view controller is deallocated you can post a notification, this notifications must be caught by the NSOperationQueue holder, simple foreach cycle in the notification handler to nil the delegate for a deallocated view controller. Should do the trick.

Mckibben answered 6/5, 2011 at 16:6 Comment(0)
E
0

You should also be checking to ensure that any delegates, if non-nil, are also able to respond to a message from the operation completion. You do this using the respondsToSelector function that all NSObject subclasses posess.

In my projects, I've abstracted this checking into a category on NSObject that lets me safely call delegates with an arbitrary number of object arguments:

- (void) dispatchSelector:(SEL)selector target:(id)target objects:(NSArray*)objects onMainThread:(BOOL)onMainThread {

if(target && [target respondsToSelector:selector]) { // Do your delegate calls here as you please } }

You can see the full example here: https://github.com/chaione/ChaiOneUtils/blob/master/Categories/NSObject-Dispatch.m

Embrey answered 6/5, 2011 at 16:40 Comment(2)
If everything is done properly, a protocol should be in place anyway to which the delegate conforms...Horgan
Not an answer to my question, though I'll just let you know that all the delegate protocols are in place and have required methods only. Thus no checking needed.Haldi

© 2022 - 2024 — McMap. All rights reserved.