HepaKKes
HepaKKes

Reputation: 1549

Asynchronous methods called inside `-dealloc` could generate unwanted zombie objects

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

Answers (4)

Ken Thomases
Ken Thomases

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

quellish
quellish

Reputation: 21244

  1. Don't call asynchronous cleanup methods from dealloc. It's just not a good idea. Your -deregister should be synchronous.
  2. NSHashTable stores pointers - it's the equivalent of __unsafe_unretained or assign - UNLESS it was created using +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

gnasher729
gnasher729

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

Bryan Chen
Bryan Chen

Reputation: 46598

Using ARC doesn't means all your memory problem magically disappeared.

What happened is

  • [obj release] called by ARC

    • [obj dealloc]

      • [obj.postOfficeService deregister:obj]
        • [obj retain] - sorry you can't cancel the deallocation process
        • dispatch_async
    • free(obj) - from this point, obj is a zombie

  • GCD scheduling tasks

  • dispatch_async execute task
    • use obj - crash

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

Related Questions