Reputation: 1549
As I was walking through some line of codes I stumbled upon this problem a couple of days ago,
- (void)dealloc {
...
[self.postOfficeService deregister:self];
...
}
Where the de-registration from the Post Office Service is an asynchronous operation, even if it's not self evident from the interface as there's no block or function passed to the postOfficeService
.
The internal implementation of postOfficeService's -deregister
method is something like that
// -deregister:(id)formerSubscriber implementation
//some trivial checks here
// deregister former subscriber
dispatch_asynch(_serialQueue, ^{
[self.subcribers removeObject:formerSubscriber];
});
...
The container, self.subscribers
, does perfectly its job and contains only weak references. I.e. it is a NSHashTable
.
As long as the deregistration method got called within the dealloc method, I keep on getting a crash while postOfficeService
is trying to remove the former subscribers from its list inside that asynch block, which is used for thread safety purposes I guess.
Adding a breakpoint on [self.subscribers removeObject:formerSubscriber]
, it's possible to notice that the formerSubscriber
object is always a NSZombieObject
. That's the reason for crashes.
I know that it's possible to get thread safety for deregister
method without incurring in this problem - I figure it should be enough use the dispatch_synch in lieu of the dispatch_asynch version
I think this is one of the reason why asynchronous methods shouldn't be called within dealloc
methods.
But the question is how's possible to constantly get NSZombie objects even if we are in an ARC environment and the container objects is a NSHashTable
(so it should be working I guess)?
Upvotes: 4
Views: 515
Reputation: 90551
I agree with the others that you should probably avoid asynchronous cleanup in your -dealloc
method. However, it may be possible to fix this by making the parameter to -deregister:
__unsafe_unretained
. That method would then have to treat the pointer purely as a opaque value. It must not dereference it or message it. Unfortunately, you don't control the implementation of NSHashTable
and can't guarantee that. Even if NSHashTable
could be relied upon, the interface of -removeObject:
takes an implicitly strong object pointer, so ARC might retain the pointer when it's copied from the unsafe unretained pointer.
You might use the C function API for hash tables (e.g. NSHashRemove()
) as suggested in the overview for the NSHashTable
class.
Upvotes: 0
Reputation: 21244
-deregister
should be synchronous.+weakObjectsHashTable
or the equivalent set of options (NSHashTableZeroingWeakMemory
and NSPointerFunctionsObjectPersonality
). If it was not created that way, it is quite likely you will have values pointing to zombie objects.The question of "why am I getting zombies" is best answered by profiling your application with the Zombies template in Instruments and stimulating the required behavior.
Upvotes: 0
Reputation: 52538
The rule is: When dealloc is called, the object will be gone once dealloc returns to its caller (whoever called release when the reference count was 0), and nothing is going to prevent this.
Before ARC, you might have tried to retain an object inside dealloc - doesn't help; once dealloc is called the object will go (and dealloc will be called only once for it, in case you do a retain / release inside dealloc). ARC does the same, just automatically.
Upvotes: 2
Reputation: 46598
Using ARC doesn't means all your memory problem magically disappeared.
What happened is
[obj release] called by ARC
[obj dealloc]
free(obj) - from this point, obj is a zombie
GCD scheduling tasks
The correct solution is use dispatch_sync to make sure you not trying to use object after it is deallocated. (be careful about dead lock)
Upvotes: 0