iOS 崩溃 - 异步 UIImageView setImage EXC_BAD_ACCESS

Posted

技术标签:

【中文标题】iOS 崩溃 - 异步 UIImageView setImage EXC_BAD_ACCESS【英文标题】:iOS Crash - Asynchronous UIImageView setImage EXC_BAD_ACCESS 【发布时间】:2013-09-14 11:39:47 【问题描述】:

我在测试中没有遇到任何崩溃,但我从 iTunesConnect 收到了一些如下所示的崩溃报告:

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x45d319f8
Crashed Thread:  0

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libobjc.A.dylib                 0x3a6595be objc_msgSend + 30
1   UIKit                           0x34796e30 -[UIImageView setImage:] + 116
2   My App                      0x000c40b2 -[AsyncImageView setImage:] (AsyncImageView.m:224)
3   My App                      0x000c3950 __47-[AsyncImageView loadImageWithURL:animated:]_block_invoke_2 (AsyncImageView.m:147)

AsyncImageView 是一个典型的 UIImageView 子类,它从 URL 异步加载图像。

这里是带有违规行号的资产加载代码:

- (void)loadImageWithURL:(NSURL *)url animated:(BOOL)animated 

    if (url == nil) 
        [self setImage:nil];
        return;
    

    self.imageAsset = [[Asset alloc] init];
    self.imageAsset.assetURL = url;

    AssetRequest *request = [[AssetRequest alloc] init];
    request.assetURL = url;

    __weak AsyncImageView *weakSelf = self;
    self.assetLoader = [AssetLoader AssetLoaderWithRequest:request
                                                 completion:^(Asset *asset)
                                                    dispatch_async(dispatch_get_main_queue(), ^

                                                    if (weakSelf.imageAsset.assetURL == asset.assetURL) 

                                                             weakSelf.imageAsset = asset;

                                                             if (animated) 
                                                                 CATransition *transition = [CATransition animation];
                                                                 transition.type = kCATransitionFade;
                                                                 transition.duration = 0.20;
                                                                 [weakSelf.layer addAnimation:transition forKey:nil];
                                                             

                                                            [weakSelf setImage:weakSelf.imageAsset.assetImage]; //THIS IS LINE 147


                                                             [weakSelf setDisplayLoadingIndicator:NO];
                                                             [weakSelf stopAnimating];
                                                    

                                                    );
                                                 
                                                     error:^(NSError *err)

                                                         if (weakSelf.failedToLoad)
                                                             weakSelf.failedToLoad(url);

                                                     ];

    [self.assetLoader load];

这里是设置图像的位置,指出了违规的行号:

- (void)setImage:(UIImage *)image 

        if (image) 
            [super setImage:image]; //THIS IS LINE 224
            [self hidePlaceholderView];

            if (self.imageLoadedBlock)
                self.imageLoadedBlock();
        
        else 
            [self showPlaceholderView];
        

    

崩溃报告表明设置图像时发生崩溃。是否有任何明显的原因导致这种情况发生?或者我可以做的任何进一步的错误检查(我已经在检查图像不为空)?再说一次,这种情况不会一直发生,只会偶尔发生一次。

【问题讨论】:

但看起来图像实际上并不是图像 - 可能是 null/nil 或已损坏。 更好的是,试试 SDWebImage。 它是否会在 没有 nil 检查 setImage 的情况下崩溃?这对我来说看起来很可疑,尤其是当它禁用 nil'ing(即:删除)图像时。 【参考方案1】:

我查看了 SDWebImage 源代码,当资产加载器完成时,它们会执行以下操作:

if (!weakSelf) 
   return;


dispatch_async(dispatch_get_main_queue(), ^
   if (weakSelf.imageAsset.assetURL == asset.assetURL) 
      weakSelf.imageAsset = asset;
      [weakSelf setImage:weakSelf.imageAsset.assetImage];
       ...
                                                             
 );

所以基本上如果weakSelf 不再存在,就什么也不做。问自己是否仍然存在似乎有点奇怪,但这会解决问题吗?

【讨论】:

如果在释放“weakSelf”时出现问题,它不会帮助你——它是一个指针,无论如何它都会指向相同的内存——只是对象不存在。它只会在 if(!weakSelf) 而不是稍后崩溃。【参考方案2】:

问题是您正在使用遍历来进行保留循环:

    __weak AsyncImageView *weakSelf = self;

使用这种方法,它不会被块保留 - 因此,如果有人在块执行之前足够快地离开 - selfBlock 会被释放。

最好的办法是取消对 dealloc 的所有操作,或者至少将完成块设置为“nil”。

- (void) dealloc

    [self.assetLoader cancelAllOperations]; //of course you need to implement that
    [super dealloc];

我认为,如果您对应用进行压力测试并在边缘连接上使用它并在响应到来之前非常迅速地离开视图,那么您将能够重现这一点。

另一个更简单的解决方案是不将“AssetLoader”保留为属性并远程此 __block 变量让块保留“self”。但是当视图不在屏幕上并且会被更新时,它可能会导致意外的行为。

【讨论】:

我尝试使用 Edge 连接,但无法使其崩溃。请查看我发布的答案,看看您是否有任何想法。 我可以在 dealloc 方法中设置 self.assetLoader = nil 吗?

以上是关于iOS 崩溃 - 异步 UIImageView setImage EXC_BAD_ACCESS的主要内容,如果未能解决你的问题,请参考以下文章

iOS-UIImageView载入网络下载的图片(异步+多线程)

自Xcode 10以来,UIImageView setImage在后台线程上崩溃[重复]

如何将图像设置为在另一个视图控制器中异步获取的 UIImageView?

iOS UIImageView 内存没有在 ARC 上被释放

添加 UITapGestureRecognizer 时 UIImageView 崩溃

尝试设置框架时 UIImageView 崩溃