1

consider the following method snippet.

- (void) closeSocket {
...
   dispatch_async(dispatch_get_main_queue(), ^{
       // last message before actual disconnection:
       [self.connectionListenerDelegate connectionDisconnected:self];
   }
   self.connectionListenerDelegate = nil;
...
}

This method of my "socket" implementation class can be called by external object, in some arbitrary thread (main, or other). I wish to only notify my delegate once, on the main thread, and remove the delegate so that other background events and possible incoming data won't reach it.

In other words, I want to make sure connectionDisconnected: is the last call from the socket to the delegate.

I know code blocks capture their environment's variables etc. But will the block capture and retain the self.connectionListenerDelegate when created?

If closeSocket is being called on some background thread, and dispatches the connectionDisconnected: asynchronously on the main thread, and I nullify my weak reference to my delegate right away - maybe the block will have a nil object and won't send its message?

What is the right way to go about this?

I guess I could use the old

[self.connectionListenerDelegate performSelectorOnMainThread:@selector(connectionDisconnected:) withObject:self waitUntilDone:NO];

which retains both the receiver and parameter object (self), but I prefer GCD dispatch_async and I'd like to better understand blocks.

Motti Shneor
  • 2,095
  • 1
  • 18
  • 24
  • To clarify: you need the reference in the `dispatch_async` block to remain valid until the block executes, but you are worried that something else might release the delegate, causing `self.connectionListenerDelegate` to be set to nil? – Avi Nov 26 '15 at 13:03
  • Show the declaration of `connectionListenerDelegate`. Normally delegates are weak or unretained for a reason. – trojanfoe Nov 26 '15 at 13:11
  • If you place self.connectionListenerDelegate = nil; in the block, then even if closeConnection: is called asynchronously, the order of execution will be maintained and the connectionListenerDelegate wlll go nil only after connectionDisconnected has been called. – Kunal Shrivastava Nov 26 '15 at 13:17
  • @MottiShneor did the answer work for you? – ShahiM Dec 18 '15 at 09:57
  • I haven't yet had the time to change my implementation and test thoroughly - but I already had this kind of "trick" In my code before (taking the delegate into a stack variable, nullifying it, and then dispatching asynchronously using the (captured) stack variable. First, I'm not sure this ensures one-time-call, and that this is thread safe, Next - I still don't have a clear answer about dispatch_async either capturing/not-capturing my self.delegate object. B.T.W - my delegates are weakly referenced. – Motti Shneor Dec 19 '15 at 15:08

1 Answers1

2

If closeSocket is being called on some background thread, and dispatches the connectionDisconnected: asynchronously on the main thread, and I nullify my weak reference to my delegate right away - maybe the block will have a nil object and won't send its message?

I think that after self.connectionListenerDelegate = nil; runs, all methods in the dispatch method will get the nil reference when accessing the connectionListenerDelegate.

So, the best way to go about this would be to transfer the delegate reference to a temporary object for use inside the block:

id<ConnectionListenerDelegate> *tempDelegateRef = self.connectionListenerDelegate;

dispatch_async(dispatch_get_main_queue(), ^{
       // last message before actual disconnection:
       [tempDelegateRef connectionDisconnected:self];
   }

self.connectionListenerDelegate = nil;

I'm not sure if you need strong/weak references or something like that.

ShahiM
  • 3,179
  • 1
  • 33
  • 58
  • tempDelegateRef is just a reference to self. connectionListenerDelegate. When connectionListenerDelegate goes nil, tempDelegateRef will point to the same nil as well. How is the problem solved? – Kunal Shrivastava Nov 26 '15 at 13:14
  • Doesn't `tempDelegateRef` hold on to the old object? – ShahiM Nov 26 '15 at 13:15
  • The `tempDelegateRef` is a strong reference, and will not be nil'd until the last owner goes away. In this case, that's the block. – Avi Nov 26 '15 at 13:19
  • @KunalShrivastava. check [this answer](http://stackoverflow.com/a/20627255/3894781). Even though `self.connectionListenerDelegate` is set to nil, `tempDelegateRef` will still point to the old object. – ShahiM Nov 26 '15 at 13:23
  • @Avi: Yes, it is a strong "reference". But the object it is referring to is connectionListenerDelegate, which is being manually set to nil. The order of execution may not always be the same as tempDelegateRef is used in an async block. There may be a case when connectionListenerDelegate is set to nil first and then the async block get executed. – Kunal Shrivastava Nov 26 '15 at 13:25
  • @KunalShrivastava: the temp reference is not pointing to `self.connectionListenerDelegate`; it's pointing to the same object. – Avi Nov 26 '15 at 13:29
  • @Avi: Yes, I got it. Thanks. – Kunal Shrivastava Nov 26 '15 at 13:31
  • First, my delegate is weakly referenced. Second - I was NOT caring about my delegate becoming nil because it died -- that is completely good and healthy for me. I am concerned about my method being called concurrently - (e.g. once because of a socket error, on some thread, and again from external object calling the same method at the same time). I want to ensure my (alive!) delegate only receives the call ONCE, and that I understand how blocks work... – Motti Shneor Dec 19 '15 at 15:12
  • @ShahiM Thanks, it does provide an answer, and a way to go. I understand that even if "self" is retained for the code-block, self.connectionListenerDelegate is not - and will be nullified before the block is executed. Indeed copying the reference into a stack variable seems puts us on known domain of "captured variables". – Motti Shneor Jan 16 '19 at 15:35
  • I marked this answer as accepted, although I still have one doubt, that in the short time between tempDelegateRef = xxx; and self.connectionListenerDelegate = nil; some other thread may be able to inject more calls on the delegate ALTHOUGH the "closeSocket" method was called, and it promises the delegate will not receive any more data or calls... I wonder how to make this more robust. Maybe I should have managed all this via a private serial dispatch_queue or NSOperationQueue – Motti Shneor Jun 21 '22 at 17:19