9

I've got a serious doubt. Suppose the following scenario:

  1. You have a UIViewController onscreen.
  2. The app initiates, say, a backend call using a block as a callback
  3. You use a 'self' surrogate, to prevent retain cycles.
  4. The user hits 'Back', and the UIViewController gets dealloc'ed.
  5. Sooner or later, the callback block gets executed >> BAD ACCESS

Before iOS 4, we dealt with this kind of situation by setting to nil the delegate property of... i don't know, whatever class you were using.

But nowadays... how do you cancel a block??. What if the block was sent to a static method, and you have no way of wiping out that callback reference??.

In that case, should we avoid using the 'self' surrogate?

BTW, by 'self' surrogate, i mean to say:

__block typeof(self) bself = self;

Thanks!!

atxe
  • 5,029
  • 2
  • 36
  • 50

2 Answers2

5

Well, first off: If (and only if) your reason for avoiding the use of self or direct access of ivars inside of a block really are retain-cycles, then you should be in a situation like

client => objectA => blockWithWeakBackReference

(where => means 'has a strong reference to').

In this case, blockWithWeakBackReference should only ever be invoked by objectA, so there is no danger of a BAD ACCESS.

If I understand your question correctly, what you really mean is a different scenario:

  • objectA wants some application-wide service to execute a block on its behalf, if some precondition is met.
  • You avoid using self inside of the block because you want to be able to dispose of objectA before the block is executed.

One example for this might be a shared network-queue that executes a block when the request finished loading for one reason or another.

In that case, I would suggest to simply copy the design of NSNotificationCenter's addObserverForName:object:queue:usingBlock: and make your service implement a pair of methods like -(SomeTokenObjectType)addWorkerBlock:(void(^)(whatever-signature-makes-sense-for-you)) and -(void)cancelWorkerBlockWithToken:(SomeTokenObjectType) in order to enqueue and cancel your callback-blocks.

Then, all objects that use this service can simply have an ivar of type NSMutableSet to store the token for every enqueued block and — in their dealloc — enumerate the remaining tokens, canceling them with the service.

danyowdee
  • 4,658
  • 2
  • 20
  • 35
  • That's exactly the scenario i'm dealing with Dany. Thanks *a lot* for your comments, i really appreciate it. I'm gonna implement that mechanism, i really like the way it looks!. – Jorge Leandro Perez Aug 02 '11 at 00:01
  • It's been a while, but I wanted to add a maybe interesting bit of information for anyone stumbling over this to tackle related problems: In his formidable article _[Let's build NSNotificationCenter](http://mikeash.com/pyblog/friday-qa-2011-07-08-lets-build-nsnotificationcenter.html)_, Mike Ash implements this mechanism in a very clean and elegant way—so be a great artist and steal his approach ;-) – danyowdee Nov 26 '11 at 21:47
0

"to prevent retain cycles."

But do you really have a retain cycle to prevent? Think about this. The block retains self (your view controller). The backend call retains the block. But where does self retain the block?

newacct
  • 119,665
  • 29
  • 163
  • 224
  • If you don't use a self surrogate in (3), the current UIViewController will get retained, till you get a response from the backend. – Jorge Leandro Perez Oct 24 '12 at 16:46
  • Which is not cool at all. If the user hits 'back', the current viewController should get released. The 'retain cycle' part depends on your communications layer... theoretically, you're right. You shouldn't get a retain cycle, if the code is well written. Yet, you'll be using more resources than what you actually need. – Jorge Leandro Perez Oct 24 '12 at 16:48
  • @JorgeLeandroPerez: Yes, it will get retained until the backend task finishes. But c'mon, this is not a big deal. It's not true that you know a view controller will be deallocated when it is popped; other things in the OS could still be retaining for various reasons. – newacct Oct 24 '12 at 18:52
  • Anyway, unless there's a way to cancel the backend task, the backend task must retain the thing it calls back to. So you must either retain this UIViewController, or, if you are really strict about the memory usage of this view controller, you can have the block instead retain a (small) dummy proxy object, and your view controller can be the delegate to this dummy object, and it will nil the delegate when it deallocates. – newacct Oct 24 '12 at 18:55
  • This is quite an old question. What i've been doing, nowadays, is simply what you say. Unless few specific cases, in which memory usage IS big deal. In those cases, i just store the active requestId's, and cancel them before going back. Thanks for your help! – Jorge Leandro Perez Oct 24 '12 at 19:43