views:

64

answers:

3

Hi all,

I have a small iPhone app which has a button on the first view. When I select this button I load up my new view which has an image on it. I'm currently using the following code to load the image from an online source on a separate thread, whilst allowing the user to continue controlling the application:

- (void) loadImageTest
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSURL *url = [[NSURL alloc] init];
    url = [NSURL URLWithString:logoPath];

    NSData *data = [[NSData alloc] init];
    data = [NSData dataWithContentsOfURL:url];

    loadingImage = [UIImage imageWithData:data];
    titleLogoImage.image = loadingImage;

    //[pool drain];
    [pool release];
}

This is called from this line of code in the new view's init:

[NSThread detachNewThreadSelector:@selector(loadImageTest) toTarget:self withObject:nil];

Now this works fine (ish), but if I close the new view and then load a new one up again in quick succession (or just after-wards in some cases) it will bomb out with the classic EXC_BAD_ACCESS. I'm sure that this is due to the code within the thread pool, but can anyone see why this is happening?

Thanks

A: 

You're doing weird stuff.

NSURL *url = [[NSURL alloc] init];

means you create an NSURL object, which you own.

url = [NSURL URLWithString:logoPath];

This means you create another NSURL object, which is autoreleased. The NSURL you just created, just leaks. The same goes for the NSData here.

The loadingImage must be retained by titleLogoImage, or it will be deallocated on the drain of your NSAutoReleasePool. What is titleLogoImage and does it retain image?

edit ps: what also disturbes me, is that loadingImage is not confined to the scope of the function. To cut things short:

NSURL *url = [NSURL URLWithString:logoPath];
NSData *data = [NSData dataWithContentsOfURL:url];
titleLogoImage.image = [UIImage imageWithData:data];

may save some headaches, at least.

mvds
This is what I had, but when reading up about NSAutoReleasePool's I read somewhere to do this... Anyway, anyway even with your method, which I had earlier, I get the same issue. Thanks anyway.
ing0
A: 

There is nothing in the posted code that would trigger a crash. Depending on how titleLogoImage is defined, it might be affects by the autorelease.

However, beyond the problems outlined by mvds, you have no pressing needs for a localized autorelease pool. It will do nothing here but cause you trouble.

Autorelease pools are dangerous and not for beginners. They will kill any autoreleased objects in them. You generally only create your own pool when you are rapidly creating a large number of memory intensive objects. That does not appear to be the case here.

Despite the attention given them, custom pools are seldom used. After over a decade Apple API work, I can probably count on my fingers the number of times I have used my own custom pool.

TechZen
+1  A: 

Yours:

// This is ok, you might try using NSURLConnections asynchronous methods instead of making // your own thread. [NSThread detachNewThreadSelector:@selector(loadImageTest) toTarget:self withObject:nil];

- (void)loadImageTest
{
    // This is fine
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    // you're making and then abandoning this url object so it will leak
    NSURL *url = [[NSURL alloc] init];
    // this is fine
    url = [NSURL URLWithString:logoPath];
    // again making and abandoning an object
    NSData *data = [[NSData alloc] init];
    data = [NSData dataWithContentsOfURL:url];
    // this works, but is not thread safe,
    // can't operate on UIKit objects off the
    // main thread
    loadingImage = [UIImage imageWithData:data];
    titleLogoImage.image = loadingImage;
    // this is fine
    //[pool drain];
    [pool release];
}

Cleaned up to make things thread safe, etc. Should help:

// I'm assuming you have a iVar for logoPath but we don't want to
// make threaded calls to an iVar (it's not mutable, so you could do it, but it's just bad form)
// If i'm wrong about logoPath being an iVar don't sweat copying it.
- (void)whateverMethodYouWant
{
    NSString *aLogoPath = [[logoPath copy] autorelease];
    [NSThread detachNewThreadSelector:@selector(loadImageForPath:) toTarget:self withObject:aLogoPath];
}
- (void)loadImageForPath:(NSString *)aLogoPath
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:aLogoPath]];
    // the performSelector will retain the data until the method can be performed
    [self performSelectorOnMainThread:@selector(setImageForTitleLogo:) withObject:imgData waitUntilDone:NO];

    [pool release];
}
- (void)setImageForTitleLogo:(NSData *)imgData
{
    // This part is not strictly necessary
    // but it's a nice way to wrap a method that needs to happen on the main thread.
    if ([NSThread isMainThread])
    {
        // make the image (i'm assuming you meant loadingImage to be a local scope variable)
        UIImage *loadingImage = [UIImage imageWithData:imgData];
        // make sure the button still exists 
        // also make sure you're setting any references to this button to nil when you're releasing and making new views
        // so that you're not addressing a button that's been released
        // (assigning the image to the button will cause the button to retain it for you)
        if (titleLogoImage != nil)
            titleLogoImage.image = loadingImage;
    }
    else
    {
        [self performSelectorOnMainThread:@selector(setImageForTitleLogo:) withObject:imgData waitUntilDone:NO];
    }
}
Doug
Hey thanks for that, you new to SO?
ing0
Excellent anyways :) I think it was not performing the setting of the image on the main thread which was the issue. Thanks so much :)
ing0
I pop in now and again. Glad it helped.
Doug