This is my pattern:
1) SpecialView creates a MessageView and holds a strong reference to it.
2) User taps a button in MessageView which causes it to fade out. MessageView then tells it's delegate, SpecialView, that it faded out completely.
3) SpecialView releases MessageView.
The problem is this:
- (void)fadedOut:(NSString*)animationID finished:(NSNumber*)finished context:(void*)context {
[self.delegate messageViewFadedOut:self]; // delegate releases us...
// self maybe got deallocated... BOOM!
// now what? method of a zombie returns? stack freaks out?
} // does it even return?
In the last line I'm calling the delegate, which in turn immediately releases MessageView. -fadedOut:finished:context: is called by a core animation didStopSelector callback.
My fear is that the MessageView instance is going to be deallocated immediately before the -fadedOut:finished:context: fully returned, causing very nasty random bugs
Once upon a time, a old veteran programmer told me: "Never cut the branch on which you're sitting on."
So in order to make sure the instance survives until this method returns, I made a retain-autorlease-dance before calling the delegate:
- (void)fadedOut:(NSString*)animationID finished:(NSNumber*)finished context:(void*)context {
//[[self retain] autorelease];
[self.delegate messageViewFadedOut:self]; // delegate releases us...
}
However, under ARC, retain-autorelease dances are not allowed anymore (the migration tool won't allow it) and there seems to be no way to force the ARC system to do something like this.
So I came up with this strategy instead:
- (void)fadedOut:(NSString*)animationID finished:(NSNumber*)finished context:(void*)context {
[self.delegate performSelector:@selector(messageViewFadedOut:) withObject:self afterDelay:0];
}
The delayed performSelector hopefully lets the method fully return. As far as I know a delay of 0 still guarantees that the selector is performed in the next run loop iteration rather than immediately.
There's a good chance this is bogus as well.
How can I correctly resolve this problem of one object asking another to destroy the last reference to it with the chance that the object get's deallocated before the method that made the call to the other object has a chance to fully return? Can there be something like a stack trace zombie?
And how must I solve something like this under ARC?
To be honest, I think that rather than trying to emulate a retain-autorelease, you should make sure that by the time the delegate method
messageViewFadedOut:
is called you don't care if the owning reference to your message view is released. The contract for awillFadeOut:
method may assume it won't be deallocated, but afadedOut:
ordidFadeOut:
method should be okay with the object being deallocated.An ideal solution would be for your
dealloc
method to specifically avoid the nasty random bugs you are dreading, by cancelling any active fadeout animation that is occurring (hold a reference to it somewhere). That way you don't care how or when it gets todealloc
, becausedealloc
knows to do X to prevent leaving any object state or visual anomalies behind when it dies.So just use your solution (
performSelector:withObject:afterDelay:
) or maybe a GCD block:Besides, ARC might just
autorelease
immediately, why don't you test that code section a bunch of times and see what happens? Put a breakpoint indealloc
and see if it's called before the method returns.