I’m puzzling over how multithreading can work with delegates.
The main thread has an object “A”, which has created an object “B”. Object “A” is the delegate for object “B”. Object “B” uses a thread to run the code.
When object “B” wants to notify the delegate, it does:
[[self delegate] performSelectorOnMainThread:@selector(didFinish:) withObject:self waitUntilDone:[NSThread isMainThread]];
The “delegate” property is an assign, atomic @property. Hence it would appear that the generated getter will do [[delegate retain] autorelease], according to objective c manual.
The dealloc method for “A” is:
- (void)dealloc
{
[b setDelegate:nil];
[b release];
[super dealloc];
}
This would appear to lead to the possible situation where the threads run like this:
- Main thread: call [A dealloc] (due to a call to [a release])
- Other thread: b calls [A retain] (due to the call to [self delegate])
- Main thread: calls [b setDelegate:nil]
- Other thread: calls performSelectorOnMainThread
At step 2, it would seem that retain cannot succeed, as dealloc is already committed to – is this race condition? What happens if you call retain on an object that is in the process of being dealloced? Can it actually occur?
If it is a race condition, how do multi-threaded objects with delegates usually avoid it?
(This arose from a slightly similar but simpler question/answer I previously asked, how to handle setDelegate with multiple threads.)
Update
It is a race conditon, as the accepted answer proves.
The solution to my original problem is to avoid this case all together, I’ve updated How to handle setDelegate: when using multipe threads to show this.
I don’t think there’s a lock on
deallocversusretain/release. The following example has adeallocmethod with asleep()in it (does anyone know ifsleep()breaks locks? I don’t think it does, but you never know). A better example might be to repeatedly instantiate/destroy instances of A and B until you get a situation like the one mentioned here, without thesleep().View controller, in my case, but could be anything:
A:
B (.h):
B (.m):
The program ends up crashing with
EXC_BAD_ACCESSat B’s releaseDelegate method. The following is the output from the NSLogs:Once
-deallocis called, retain counts are no longer of import. The object will be destroyed (this is probably obvious, though I wonder what would happen if you checked self’s retainCount and DID NOT call [super dealloc] if the object had retains… insane idea). Now if we modify the-deallocfor A to set B’s delegate tonilfirst, the program works but only because we’re nil’ingdelegatein B inreleaseDelegate.I don’t know if that answers your question, really, but presuming sleep()’s are not somehow breaking thread locks, the exact same behavior should happen when
deallocis called right before aretain.