1

您好,我正在使用 NSOperationQueue 在后台下载图像。我创建了一个自定义 NSOperation 来下载图像。我将图像放在表格单元格中。问题是如果我执行 [operationQueue setMaxConcurrentOperationCount: 10] 并向下滚动几个单元格,程序会因 EXC_BAD_ACCESS 而崩溃。每次它在桌子的同一个地方崩溃。一个接一个地有 3 个单元,它们是同一家公司的,并且具有相同的徽标,因此基本上它应该下载图像 3 次。每隔一段时间它工作正常。


- (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];
}

这就是我处理下载图像的方式。如果我在上面的函数中注释掉 [delegate didFinishDownloadingImage: self]; 它不会崩溃,但它当然没用。


-(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];
        }

    }
}


4

3 回答 3

4

根据这个邮件列表帖子,UIGraphicsBeginImageContext不是线程安全的。该帖子暗示CGBitmapContextCreate相关功能是安全的方法。

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

于 2010-03-30T13:17:55.070 回答
1

我认为您正在崩溃,因为您试图访问 tableview 中不存在的单元格。

不管逻辑表有多长,可视化表视图只包含足够的单元格来显示当前屏幕上的逻辑表部分。在默认行大小下,表格显示并因此仅包含 9 到 10 个单元格对象。例如,如果您有一个 100 行长的逻辑表,并且您的视图显示第 11-20 行,则该表只有 9 个单元格对象。如果显示第 89-98 行,它只有完全相同的9 个单元格对象。无论您显示哪一行,您都会一遍又一遍地看到相同的 9 个单元格对象。唯一改变的是它们显示的数据。

如果您尝试访问单元格以获取屏幕外的逻辑行,您将不会得到任何回报。在您的情况下,您尝试访问第 11 个逻辑行,但没有第 11 个单元格,并且永远不会有。

我认为你有一些概念上的混乱,因为你试图通过设置单元格内容将数据存储在 tableview 本身中。这是行不通的,因为 tableviews 不会存储任何超出立即显示的数据。当表格视图需要在滚动时显示更多行时,它会重用现有单元格,并且其 DataSource 委托会更改现有单元格显示的数据。

您需要创建一个数据模型并将图像存储在那里,而不是将图像存储在单元格中。然后当 tableview 滚动时,它将发送tableview:cellForRowAtIndexPath:到它的数据源委托。然后,数据源委托将向数据模型询问逻辑行的数据,填充重用的单元格并将其返回给表格视图。

于 2010-03-30T14:46:48.873 回答
0

好的,由于codewarrior,它似乎已解决。这是改变的部分。

@同步(委托)
{
    UIImage *resizedImage;
    UIGraphicsBeginImageContext(itemSize);
    CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
    [图像 drawInRect:imageRect];
    resizedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    self.theImage = resizedImage;
}
于 2010-03-31T06:56:05.687 回答