views:

452

answers:

2

I have a background task that updates a view. That task calls -setNeedsDisplay to have the view drawn.

This works:

- (void) drawChangesTask;
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    if (pixels) {
     drawChanges((UInt32 *) origPixels, (UInt32 *) pixels, CGBitmapContextGetBytesPerRow(ctx)/4, CGBitmapContextGetHeight(ctx), count--);

     if (count < 0) {
      count = 150;
     }
     else
      [self performSelectorInBackground:@selector(drawChangesTask) withObject:nil ];

     [self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO ];

    }
    [pool release];
}

This does not work:

- (void) drawChangesTask;
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    if (pixels) {
     drawChanges((UInt32 *) origPixels, (UInt32 *) pixels, CGBitmapContextGetBytesPerRow(ctx)/4, CGBitmapContextGetHeight(ctx), count--);

     if (count < 0) {
      count = 150;
     }
     else
      [self performSelectorInBackground:@selector(drawChangesTask) withObject:nil ];

     [self setNeedsDisplay];

    }
    [pool release];
}

Anyone know why? When I say it doesn't work, I mean that it runs tens of iterations, sometimes I see portions of my image shifted up or down, or entirely blank, and then the deugger give me an “EXC_BAD_ACCESS” somewhere in CoreGraphics.

Also, if I don't handle the autorelease pool myself, then I get leaking error messages. Don't understand why that is either. My drawChanges() doesn't create any new objects. Here's the error:

2009-08-17 11:41:42.358 BlurApp[23974:1b30f] *** _NSAutoreleaseNoPool(): Object 0xd78270 of class NSThread autoreleased with no pool in place - just leaking
+4  A: 

UIKit simply isn't thread-safe — you need to call methods that update UIKit controls on the main thread.

I think that this line:

[self performSelectorInBackground:@selector(drawChangesTask) withObject:nil];

Is causing trouble. Have you tried simply calling it again on the current thread? If you need the runloop to execute between the calls, use:

[self performSelector:@selector(drawChangesTask) withObject:nil afterDelay:0.0];

This will call the method on the current thread after the method you're in has finished and the runloop has gone round once.

iKenndac
Yes, if I call it directly, without performSelectorInBackground, then the runloop never gets a chance to run and call drawRect. If I do what you say to call it with afterDelay, it works without any strange crashes.
mahboudz
This also explains why it works when using [self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO ]; to call setNeedsDisplay. Note that the performSelectorInBackground calls the same function/method that it is in: drawChangesTask, and the drawChanges() function does not call UIKit. Thus the only thing that looks like it might be thread sensitive is setNeedsDisplay, and if we perform that in the main thread, then I have no issues.
mahboudz
I have two solutions now: performSelector after delay of 0 or performSelectorInBackground, with setNeedDisplay called by performSelectorOnMainThread.
mahboudz
+2  A: 

Problem here is that UIKit is not thread safe, if you tell your UI to do something from a background thread nothign is guaranteed, what you want to do is use the performSelectorOnMainThread method to do updates t o your UI elements

Daniel