Search code examples
iosobjective-csdwebimage

Sometimes app crash in SDWebImage(SDWebImageDownloaderOperation#cancelInternal)


We have used SDWebImage Lib on Our App.(SDWebImage Version 3.7.2) Recently, the app terminate abnormally sometimes.(In SDWebImageDownloaderOperation#cancelInternal.)

the following is stacktrace.

Crashed: com.apple.main-thread
EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0xxx

Thread : Crashed: com.apple.main-thread
0 libobjc.A.dylib 0x180765bd0 objc_msgSend + 16
1 HOGE 0x1002a94d8 -SDWebImageDownloaderOperation cancelInternal
2 HOGE 0x1002a93ec -SDWebImageDownloaderOperation cancel
3 HOGE 0x1002ad63c __69-[SDWebImageManager downloadImageWithURL:options:progress:completed:]_block_invoke131 (SDWebImageManager.m:245)
4 HOGE 0x1002ae0f0 -SDWebImageCombinedOperation cancel
5 HOGE 0x1002b5380 -UIView(WebCacheOperation) sd_cancelImageLoadOperationWithKey:
6 HOGE 0x1002b0c74 -UIButton(WebCache) sd_cancelImageLoadForState:
7 HOGE 0x1002af578 -UIButton(WebCache) sd_setImageWithURL:forState:placeholderImage:options:completed:
8 HOGE 0x1002af3f8 -UIButton(WebCache) sd_setImageWithURL:forState:placeholderImage:options:

Abort point is following.

[imageButton sd_setImageWithURL:[NSURL URLWithString:url] forState:UIControlStateNormal placeholderImage:nil options:SDWebImageRetryFailed]; 

(It's called Collection view cell 's layoutSubviews)

CollectionView Cell's View Source code. Header

#import <UIKit/UIKit.h>
#import "Photo.h"

@interface PhotoCollectionViewCell : UICollectionViewCell
@property (weak, nonatomic) IBOutlet UIButton *imageButton;
@property (strong, nonatomic) UIButton *likeButton;
@property (strong, nonatomic) Photo *photo;

@end

Implementation

@implementation PhotoCollectionViewCell

- (void)awakeFromNib
{
    self.selectedBackgroundView = [[UIView alloc] initWithFrame:self.bounds];
    self.selectedBackgroundView.backgroundColor = [BaseColorSet getColor1];

    [_imageButton setBackgroundColor:[BaseColorSet getPhotoBackgroundColor]];
    _imageButton.layer.cornerRadius = 2;
    _imageButton.clipsToBounds = YES;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    NSString *url = [PhotoImgURL get_5:_photo];
    if (!url) {
        url = [NSString stringWithFormat:@"%@/%@/%@",S3_STORAGE_URL,[PhotoDirectory get_5],_photo.filename];
    }

    [_imageButton sd_setImageWithURL:[NSURL URLWithString:url] forState:UIControlStateNormal placeholderImage:nil options:SDWebImageRetryFailed];
    _imageButton.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
    _imageButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
    _imageButton.imageView.contentMode = UIViewContentModeScaleToFill;
}

@end

MyCollectionViewController

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    PhotoCollectionViewCell *cell = (PhotoCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
    cell.photo = [_photos objectAtIndex:indexPath.row];
    [cell.imageButton addTarget:self action:@selector(imageButtonPushed:) forControlEvents:UIControlEventTouchUpInside];
    [cell setNeedsLayout];

    // add Load processing
    if (indexPath.row == _photos.count-1 && !_isLast) {
        Photo *photo = [_photos objectAtIndex:indexPath.row];
        [self pagingMyView:photo];
    }

    return cell;
}

Why terminate abnormally?

How to avoid this error should I do?

(Is it Bad Access Memory?)

Edit

thanks. It has not been released yet, and modify the code as follows

PhotoCollectionViewCell(fixed.)

@implementation PhotoCollectionViewCell

- (void)awakeFromNib
{
    self.selectedBackgroundView = [[UIView alloc] initWithFrame:self.bounds];
    self.selectedBackgroundView.backgroundColor = [BaseColorSet getColor1];

    [_imageButton setBackgroundColor:[BaseColorSet getPhotoBackgroundColor]];
    _imageButton.layer.cornerRadius = 2;
    _imageButton.clipsToBounds = YES;
    _imageButton.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
    _imageButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
    _imageButton.imageView.contentMode = UIViewContentModeScaleToFill;
}

-(void)prepareForReuse
{
    [super prepareForReuse];
    [_imageButton sd_cancelImageLoadForState:UIControlStateNormal];
}

@end

MyCollectionViewController(fixed)

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    PhotoCollectionViewCell *cell = (PhotoCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
    cell.photo = [_photos objectAtIndex:indexPath.row];
    cell.imageButton.tag = cell.photo.photoId;
    NSString *url = [PhotoImgURL get_5:cell.photo];
    if (!url) {
        url = [NSString stringWithFormat:@"%@/%@/%@",S3_STORAGE_URL,[PhotoDirectory get_5],cell.photo.filename];
    }
    [cell.imageButton sd_setImageWithURL:[NSURL URLWithString:url] forState:UIControlStateNormal placeholderImage:nil options:SDWebImageRetryFailed];
    [cell.imageButton addTarget:self action:@selector(imageButtonPushed:) forControlEvents:UIControlEventTouchUpInside];
    [cell setNeedsLayout];

    // add Load processing
    if (indexPath.row == _photos.count-1 && !_isLast) {
        Photo *photo = [_photos objectAtIndex:indexPath.row];
        [self pagingMyView:photo];
    }

    return cell;
}

Solution

  • When you use SDWebImageView or SDWebImageButton in UITableViewCell and UICollectionViewCell, you have to care about 'cell's reuse timing' and 'asynchronous download and set image timing'.

    This happens when you scroll fast, so when the asynchronous download completed, the ui is already reused by another cell.

    To avoid this error (I guess),

    use SDWebImage api in

    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    

    or

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    

    and grab the UI's pointer with '__block'

    If you show more code about 'cellForItemAtIndexPath', I can help you.