I've found a zombie in my code, now what do I do?

283 Views Asked by At

I'm working on a timeline based app that displays posts from App.net. When I tap a cell I load a TableViewController that loads a data controller object. This controller object then loads a data object. The controller object goes onto a background thread, fetching the posts in the thread. When it gets them it sends them to the data object which also goes onto a background thread to apply formatting to the posts and pre-calculate the heights needed to display the post text.

Then, from the background thread I call dispatch_async(dispatch_get_main_queue(), ^{... and post a notification:

[[NSNotificationCenter defaultCenter] postNotificationName:PostStreamDataDidUpdateNotification object:self];

self in this case is the data object that the TableViewController uses to populate it's cells. The program throws a tantrum when this notification is posted because technically self (the data object), the data controller object, and the TableViewController could be released by the navigation controller. This happens when I load a thread, therefore entering the background thread, and then popping the view from the navigation controller. The TableViewController released, as is everything it owned: the data controller, the data object etc.

I'm at a loss of what to do. From searches on the internet I can deduce that I'm running into Zombie problem. That sounded manageable, until I couldn't find anyone talking about how to avoid or fix them. Instruments makes them easy to find but how to avoid them...

I'm thinking that something is fishy with how I'm declaring properties and that setting something as a strong reference might be a bad idea.

Currently the TableViewController declares the data controller (PRSPostStreamDataController) as such:

@property (nonatomic, strong) PRSPostStreamDataController *postStreamDataController;

And that data controller object declared the data object as such:

@property (nonatomic, strong, readonly) PRSPostStreamData *data;

Some Questions bouncing around in my head:

  • Should I call newSelf = [self copy]; before I enter the background thread and then return newSelf as the notification object? (I've attempted this but at this point I'm just throwing paint at a canvas)

  • Should I cancel the background thread in my dealloc method? If so, how is that done? This question has a very similar premise to mine, except that the request completes and my problem lies in the background thread. a simple delegate = nil; doesn't exist for me.

  • Testing for self = nil seems futile because the Xcode debugger is telling me self is not nil.

  • Should I give up on my hopes of background threads entirely?


I'm new to stack overflow (and iOS Development too) but I do know that questions are supposed to be singular and concise. So the question:

It's great that I can use Instruments to track down Zombies in my app and it's great that I can see the methods responsible but how do I avoid creating zombie objects on a background thread?

This question is broad but I hope the information I provided can narrow it down. Let me know if I can improve the question

2

There are 2 best solutions below

1
On

One possible solution would be to separate your data access layer, ie. the model, to an independent service (usually implemented as a singleton). This way the data is owned and managed by this service and not by the view controller that displays the data. This pattern is sometimes called Model-View-Controller-Store. It sometimes goes by other names, too — look it up.

This SO question also deals with similar issue: Pattern for preloading data for subsequent view to be displayed

0
On

I mentioned in a comment to Palimondo's answer that I was attempting to use KVO to solve my problem. It turns out that KVO is a bit trickier than I thought, or at least for this particular problem. I wanted to use KVO because it meant I could monitor from the ViewController object instead of of from the object that might go into "zombie mode" on the background thread. However, it didn't allow me to send updates when I wanted. I tried monitoring a BOOL in the data object but that didn't work with the same ease as sending notifications with NSNotificationCenter.

So, in the end I decided to add a BOOL property to my data object that would be checked before posting the notification. Here is the simplified code from one method:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{

    // Calculate the cell heights, text colors, etc.

    dispatch_async(dispatch_get_main_queue(), ^{
        if (self.hasPopped == NO) {
            [[NSNotificationCenter defaultCenter] postNotificationName:PostStreamDataDidUpdateNotification object:self];
        }
    });
});

By default self.hasPopped will equal NO so this code will always run. When a view is popped of the navigation stack it is deallocated and so in the dealloc method of the TableViewController that owns this data object I send a notification:

[[NSNotificationCenter defaultCenter] postNotificationName:PostStreamHasBeenPopped object:self.postStreamDataController.data];

The data object has registered to receive this notification and so when it does it sets self.hasPopped to YES. Now if any background processes return to the main thread the notification that caused the crash will be skipped (and hopefully the data object is finally deallocated). I can also verify which view controller sent the notification by testing if the notification.object == self in the data object. This can be useful if I have multiple TableViewControllers operating at the same time, say a Mentions and a Global timeline.

If you've noticed that I haven't really answered the broad question, it's because I'm not sure how. I have solved the specific problem I was having with zombies so I thought I should elaborate for others like me out there.