当 MaxConcurrentOperationCount > 时,我得到 EXC_BAD_ACCESS 1

发布于 2024-08-27 10:09:18 字数 2905 浏览 2 评论 0原文

您好,我正在使用 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];
}

这就是我处理下载图像的方式。如果我评论掉 <代码> [委托 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];
        }

    }
}


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

    }
}


如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

凉宸 2024-09-03 10:09:18

根据此邮件列表帖子,UIGraphicsBeginImageContext 不是线程安全的。该帖子暗示 CGBitmapContextCreate 和相关函数是执行此操作的安全方法。

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

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

明月夜 2024-09-03 10:09:18

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

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

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

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

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

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.

陪你搞怪i 2024-09-03 10:09:18

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

@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; 
}

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; 
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文