views:

120

answers:

1

Hi!

I have a UIViewController and in that controller, i am fetching an image from a URL source. The image is fetched in a separate thread after which the user-interface is updated on the main thread. This controller is displayed as a page in a UIScrollView parent which is implemented to release controllers that are not in view anymore.

When the thread finishes fetching content before the UIViewController is released, everything works fine - but when the user scrolls to another page before the thread finishes, the controller is released and the only handle to the controller is owned by the thread making releaseCount of the controller equals to 1. Now, as soon as the thread drains NSAutoreleasePool, the controller gets releases because the releaseCount becomes 0. At this point, my application crashes and i get the following error message:

bool _WebTryThreadLock(bool), 0x4d99c60: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...

The backtrace reveals that the application crashed on the call to [super dealloc] and it makes total sense because the dealloc function must have been triggered by the thread when the pool was drained. My question is, how i can overcome this error and release the controller without leaking memory?

One solution that i tried was to call [self retain] before the pool is drained so that retainCount doesn't fall to zero and then using the following code to release controller in the main thread:

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

Unfortunately, this did not work out. Below is the function that is executed on a thread:

- (void)thread_fetchContent {

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSURL *imgURL = [NSURL URLWithString:@"http://www.domain.com/image.png"];

    // UIImage *imgHotspot is declared as private - The image is retained 
    // here and released as soon as it is assigned to UIImageView

    imgHotspot = [[[UIImage alloc] initWithData:
         [NSData dataWithContentsOfURL: imgURL]] retain];

    if ([self retainCount] == 1) {

        [self retain]; // increment retain count ~ workaround
        [pool drain]; // drain pool

        // this doesn't work - i get the same error

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

    else {

        // show fetched image on the main thread - this works fine!

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

        [pool drain];
    }
}

Please help! Thank you in advance.

A: 

Hi Yeah it can be really daunting to try and keep threaded stuff in sync. The use case you describe sounds perfect for an NSOperation. By using this approach you can have an NSOperationQueue as an ivar in the controller and release this in your controllers dealloc method.

The benefits are many, when the controllers view is visible in the scrollView it (viewWillAppear or loadView) starts retrieving the image using an NSOperation added to an NSOperationQueue, if the user then scrolls away before the operation is done and the NSOperationQueue is released, it will take care of sending a cancel message to all operations and in general close everything down in an orderly fashion.

If this is a central component in your app, which I guess it is since you put thought into releasing things that are "of screen", I would recommend having your controller display a "dummy image" in the loadVIew method and then start a fetch operation in the viewDidLoad. You could subclass NSOperation so that you just send it the URL and let it do its thing.

I did something similar some weeks ago where I had to continuously start threaded operations, but with a large chance the the user would do something that caused these to get canceled. That functionality is "build" into the NSOperation. NSOperation question

RickiG
Thank you! The NSOperation approach was great help - i looked at the link you provided and the tutorial at http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/ - it solved my problem :)
bart-simpson