views:

465

answers:

1

Hi

I have got a memory bug that seems to boil down to something happening in a thread. I am having difficulties troubleshooting this.

I have a UIViewController, that when active, i.e. the user is using its view, retrieves updates from a web service in an NSThread.

This is done every 3 minutes and this delay is controlled by a:

[self performSelector:@selector(timerDone) withObject:nil afterDelay:180.0];

The timerDone method now starts the NSThread that retrieves the web service data and also it sends the performSelector message again. This is a little "check for updates, populate views, shut everything down, repeat" routine that works just fine.

Now, the user can of course suddenly tap a button an load up a second UIViewController. When this happens I call:

[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timerDone) object:nil];

And do my cleaning up in the dealloc method.

My question is now: What happens if the NSThread was running while the user changed the view and set in motion the deconstruction of this object that is the starting point of the NSThread?

Should I keep a BOOL around that tells me if the NSThread is still active, and if so, what to do with the NSThread if this is the case.

The threading is done like this:

- (void) runTimer {

    [self performSelector:@selector(timerDone) withObject:nil afterDelay:180];
}

- (void) timerDone {

    [self performSelector:@selector(runTimer) withObject:nil afterDelay:2];
    [NSThread detachNewThreadSelector:@selector(updateAllVisibleElements) toTarget:self withObject:nil]; 

    }

- (void) updateAllVisibleElements  {

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    //call approiate web service
    [pool release];
}
+4  A: 

You have two problems here: first, you're using performSelector:withObject:afterDelay: to do what an NSTimer does best (periodic callback). cancelPreviousPerformRequestsWithTarget:selector:object: can be quite expensive, and because of your threading is likely creating race conditions.

Second problem: each thread has its own run loop, and both mechanisms (performSelector:... and NSTimer) and are tied to the current thread's run loop.

Here's what I recommend: Create a single, long-lived NSThread with its own explicit run loop for all your update needs. Look at the Threading Programming Guide for some good example code of this. On that thread, set up a 3-minute repeating NSTimer. Every 3 minutes, update.

If you need to schedule an update outside the three-minute cycle, then you use performSelector:onThread:withObject:waitUntilDone: to call your updateAllVisibileElements. The way I generally do this is to encapsulate all of the thread logic into a single object (WebServiceController or whatever). It creates it own NSThread and saves it in an ivar. Then I use code like this:

- (void)requestUpdate
{
    if ([NSThread currentThread] != self.thread)
    {
        [self performSelector:@selector(update) onThread:self.thread withObject:nil waitUntilDone:NO];
        return;
    }
    else
    {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        //call approiate web service
        [pool drain];
    }
}

One more note: you mention that the background thread "populates views." A background thread should never call into UIKit. UIKit is not thread safe and should only be called on the main thread. I typically achieve this by posting notifications onto the main thread which the view controllers observe. The "updating" object should not know anything about the UI. That breaks the Model-View-Controller paradigm of Cocoa.

Rob Napier
And, just to repeat the last paragraph: UIKit is *not* thread-safe, and should *only* be updated from the main thread. See `performSelectorOnMainThread:withObject:waitUntilDone:` for the way to send information back from the background. http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/occ/instm/NSObject/performSelectorOnMainThread:withObject:waitUntilDone:
Sixten Otto
Hi and thank you both.I really don't hope anything I wrote could be interpreted as "a NSThread updating the views" The background thread sets the data on the model through a delegate. The model then tells all controllers that new data is available.I need to understand this correctly: should I set up an NSThread in my appDelegate, that lives through the entire run of my app. This thread would then run an NSTimer every 3 minutes and update my data?As it is now I sort all my data objects, those older than 3 minutes are updated but this only happens when a view needing these data is loaded.
RickiG
I would use an NSThread that lives through the app. If you want to only update when someone needs an update, then have the view controllers call something like setNeedsUpdate on the model (just like setNeedsDisplay for views). Alternately, you could create addObserver and removeObserver on the model objects, and the view objects would set themselves this way. Every 3 minutes, if observers>0, update the object. I'd only do this if there's a real benefit to avoiding unneeded updates. Often it's connecting to a server that's expensive. Downloading 500 bytes or 5000 bytes is often about the same.
Rob Napier
Thank you for elaborating, Rob.It is a web service that charges by the call, so it is very important to select the correct approach:) I like the addObserver/removeObserver idea. I could run a thread, that runs an X min. timer. Only if the number of observers > 0 I need to do the update. This seems to decouple my viewControllers from dealing with timers and garbage collection of threads. As a bonus, this also gives me a way of gathering several data objects and batch update them.Thank you again!
RickiG