Question

Hello i am using NSOperationQueue to download images in the background. I have created a custom NSOperation to download the images. I put the images in table cells. The problem is if I do [operationQueue setMaxConcurrentOperationCount: 10] and i scroll down several cells the program crashes with EXC_BAD_ACCESS. Every time it crashes at the same place in the table. There are 3 cells one after the other which are for the same company and have the same logo so basically it should download the images 3 times. Every other time it works fine.


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

    NSURL *url = [[NSURL alloc] initWithString:self.imageURL];
    debugLog(@"downloading image: %@", self.imageURL);
    //NSError *error = nil;
    NSData *data = [[NSData alloc] initWithContentsOfURL:url];
    [url release];

    UIImage *image = [[UIImage alloc] initWithData:data];
    [data release];
    if (image)
    {
        if (image.size.width != ICONWIDTH && image.size.height != ICONHEIGHT)
        {
            UIImage *resizedImage;
            CGSize itemSize = CGSizeMake(ICONWIDTH, ICONHEIGHT);

                        //!!! UIGraphicsBeginImageContext NOT THREAD SAFE
            UIGraphicsBeginImageContext(itemSize);
            CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
            [image drawInRect:imageRect];
            resizedImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();

            self.theImage = resizedImage;
        }
        else
        {
            self.theImage = image;
        }
        [image release];
    }
    [delegate didFinishDownloadingImage: self];
    [pool release];
}

This is how i handle downloading the images. If i comment out [delegate didFinishDownloadingImage: self]; in the function above it doesn't crash but of course it is useless.


-(void) didFinishDownloadingImage:(ImageDownloadOperation *) imageDownloader
{
    [self performSelectorOnMainThread: @selector(handleDidFinishDownloadingImage:) withObject: imageDownloader waitUntilDone: FALSE];
}

-(void) handleDidFinishDownloadingImage:(ImageDownloadOperation *)imageDownloadOperation
{
    NSArray *visiblePaths = [self.myTableView indexPathsForVisibleRows];
    CompanyImgDownloaderState *stateObject = (CompanyImgDownloaderState *)[imageDownloadOperation stateObject];

    if ([visiblePaths containsObject: stateObject.indexPath])
    {
        //debugLog(@"didFinishDownloadingImage %@ %@", imageDownloader.theImage);

        UITableViewCell *cell = [self.myTableView cellForRowAtIndexPath: stateObject.indexPath];
        UIImageView *imageView = (UIImageView *)[cell viewWithTag: 1];
        if (imageDownloadOperation.theImage)
        {
            imageView.image = imageDownloadOperation.theImage;
            stateObject.company.icon = imageDownloadOperation.theImage;
        }
        else
        {
            imageView.image = [(TestWebServiceAppDelegate *)[[UIApplication sharedApplication] delegate] getCylexIcon];
            stateObject.company.icon = [(TestWebServiceAppDelegate *)[[UIApplication sharedApplication] delegate] getCylexIcon];
        }

    }
}


Was it helpful?

Solution

According to this mailing list post, UIGraphicsBeginImageContext isn't thread safe. The post hints that CGBitmapContextCreate and related functions are the safe way to do it.

http://osdir.com/ml/cocoa-dev/2009-10/msg00035.html

OTHER TIPS

I think you're crashing because you're trying to access cells in the tableview that are not there.

Regardless of how long the logical table is, the visual tableview only holds enough cells to display the section of the logical table currently on screen. At the default row size, a table shows and therefore contains, only 9 to 10 cell objects. For example, if you have a logical table that is 100 rows long and your view is showing rows 11-20 the table only has 9 cell objects. If you show rows 89-98, it only has the exact same 9 cell objects. No matter which rows you display, you see the same 9 cell objects over and over again. The only thing that changes is the data they display.

If you try to access the cell for a logical row off screen, you won't get anything back. In your case, you try to access the 11th logical row but there is no 11th cell and there never will be.

I think you have some conceptual confusion because you're trying to store data in the tableview itself by setting the cell contents. This will not work because tableviews do not store any data beyond what is immediately displayed. When a tableview needs to display more rows as you scroll, it reuses the existing cell and its DataSource delegate changes the data that the existing cells display.

Instead of storing the images in the cells, you need to create a data model and store the images there. Then when the tableview scrolls, it will send tableview:cellForRowAtIndexPath: to its datasource delegate. The datasource delegate will then ask the data model for the data for the logical row, populate the reused cell and return it to the tableview.

Ok it seems it is solved thanks to codewarrior. This is the part that changed.

@synchronized(delegate)
{
    UIImage *resizedImage;
    UIGraphicsBeginImageContext(itemSize);
    CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
    [image drawInRect:imageRect];
    resizedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    self.theImage = resizedImage; 
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top