簡體   English   中英

當我向下滾動很多時,帶圖像的UITableView會崩潰應用程序

[英]UITableView with images crashes app when I scroll down a lot

我有這個簡單的UITableView,每個單元格都有一個與之對應的圖像。 我正在做的就是在每個單元格中顯示圖像的標題和圖像本身。 這是我的cellForRowAtIndexPath:

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

    // this is where the data for each cell is
    NSDictionary *dataForThisCell = cachedData.posts[indexPath.row][@"data"];

    // this creates a new cell or grabs a recycled one, I put NSLogs in the if statement to make sure they are being recycled, they are.
    post *cell = (post *) [self.tableView dequeueReusableCellWithIdentifier:@"postWithImage"];
    if (cell == nil) {
        cell = [[[NSBundle mainBundle]loadNibNamed:@"postWithImage" owner:self options:nil]objectAtIndex:0];
        [cell styleCell];
    }

    // if this cell has an image we need to stick it in the cell
    NSString *lowerCaseURL = [dataForThisCell[@"url"] lowercaseString];
    if([lowerCaseURL hasSuffix: @"gif"] || [lowerCaseURL hasSuffix: @"bmp"] || [lowerCaseURL hasSuffix: @"jpg"] || [lowerCaseURL hasSuffix: @"png"] || [lowerCaseURL hasSuffix: @"jpeg"]) {

        // if this cell doesnt have an UIImageView, add one to it. Cells are recycled so this only runs several times
        if(cell.preview == nil) {
            cell.preview = [[UIImageView alloc] init];
            [cell.contentView addSubview: cell.preview];
        }

        // self.images is an NSMutableDictionary that stores the width and height of images corresponding to cells.
        // if we dont know the width and height for this cell's image yet then we need to know now to store it
        // once the image downloads, and then cause our table to reload so that heightForRowAtIndexPath
        // resizes this cell correctly
        Boolean shouldReloadData = self.images[dataForThisCell[@"name"]] == nil ? YES : NO;

        // download image
        [cell.preview cancelImageRequestOperation];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString: dataForThisCell[@"url"]]];
        [request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
        [cell.preview setImageWithURLRequest: request
                              placeholderImage: [UIImage imageNamed:@"thumbnailLoading.png"]
                                       success: ^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {

                                           // if we indicated earlier that we didnt know the dimensions of this image until
                                           // just now after its been downloaded, then store the image dimensions in self.images
                                           // and tell the table to reload so that heightForRowAtIndexPath
                                           // resizes this cell correctly
                                           if(shouldReloadData) {
                                               NSInteger imageWidth = image.size.width;
                                               NSInteger imageHeight = image.size.height;
                                               if(imageWidth > [ColumnController columnWidth]) {
                                                   float ratio = [ColumnController columnWidth] / imageWidth;
                                                   imageWidth = ratio * imageWidth;
                                                   imageHeight = ratio* imageHeight;
                                               }
                                               if(imageHeight > 1024) {
                                                   float ratio = 1024 / imageHeight;
                                                   imageHeight = ratio * imageHeight;
                                                   imageWidth = ratio* imageWidth;
                                               }
                                               self.images[dataForThisCell[@"name"]] = @{ @"width": @(imageWidth), @"height": @(imageHeight), @"titleHeight": @([post heightOfGivenText: dataForThisCell[@"title"]]) };
                                               [self.tableView reloadData];

                                           // otherwise we alreaady knew the dimensions of this image so we can assume
                                           // that heightForRowAtIndexPath has already calculated the correct height
                                           // for this cell
                                           }else{

                                               // assign the image we downloaded to the UIImageView within the cell
                                               cell.preview.image = image;

                                               // position the image
                                               NSInteger width = [self.images[dataForThisCell[@"name"]][@"width"] integerValue];
                                               NSInteger height = [self.images[dataForThisCell[@"name"]][@"height"] integerValue];
                                               cell.preview.frame = CGRectMake( ([ColumnController columnWidth] - width)/2 , [self.images[dataForThisCell[@"name"]][@"titleHeight"] integerValue] + 10, width, height);

                                           }

                                       }
                                       failure: ^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {}];


    }

    // set title of the cell
    cell.title.text = [NSString stringWithFormat: @"%@\n\n\n\n\n", dataForThisCell[@"title"]];

    `enter code here`// ask for a restyle
    [cell setNeedsLayout];

    // returns my customized cell
    return cell;

}

會發生什么事情都是我想要的一切,但是一旦我向下滾動大約100個單元格左右,我的應用程序的背景變黑了幾秒鍾,然后我看到我的主屏幕(我看到有些人稱之為HSOD - 死亡的主屏幕)。 有時在xcode的控制台中,我會在崩潰前看到內存警告,有時候我沒有。

我知道無論問題是什么,它都與將圖像放入細胞有關。 如果我注釋掉這一行:

cell.preview.image = image;

然后一切正常,它不再崩潰(但當然,圖像不會在單元格中顯示)。

這些單元格正在被重用,我知道它正在工作,為了更好的衡量,我將UIImageView的image屬性設置為nil:

- (void) prepareForReuse {
    [super prepareForReuse];
    if(self.preview != nil)
        self.preview.image = nil;
}

在我的appDelegate中我也定義了這個:

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {

    [UIImageView clearAFImageCache];

}

這會刪除圖像緩存,但也不能解決問題(無論如何iOS都應該自動清除圖像緩存中的內存警告)。

我對我的項目進行了分析,它報告沒有內存泄漏,這里是分析器顯示,以及顯示崩潰時的分配:

在此輸入圖像描述

除了控制台中偶爾發生的內存警告(大約是應用程序崩潰時間的2/3),控制台中沒有出現其他錯誤,我也沒有遇到任何斷點或異常。

所有這些分配都是您在每次請求時創建新的表格視圖單元格,而不是重用現有的單元格視圖單元格。 如果不為從UINib創建的單元格設置reuseIdentifier ,則dequeueReusableCellWithIdentifier:將始終返回`nil。

要解決此問題,請添加以下代碼( 在此問題中引用):

[self.tableView registerNib:[UINib nibWithNibName:@"nibname" bundle:nil] forCellReuseIdentifier:@"cellIdentifier"];

我找到了一個解決方案,雖然不是一個完美的解決方案:

AFNetworking庫很棒,我認為我的問題的原因在於我自己的代碼或者我對NSCache如何工作缺乏了解。

AFNetworking使用Apple的NSCache緩存圖像。 NSCache類似於NSMutableDictionary,但在內存稀疏時釋放對象(請參閱此處 )。

在UIImageView + AFNetworking.m中,我找到了定義

+ (AFImageCache *)af_sharedImageCache

並將其改為類似於:

+ (AFImageCache *)af_sharedImageCache {
    static AFImageCache *_af_imageCache = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _af_imageCache = [[AFImageCache alloc] init];
        _af_imageCache.countLimit = 35;
    });

    return _af_imageCache;
}

這里的重要一點是

_af_imageCache.countLimit = 35;

這告訴AFNetworking中使用的NSCache對象緩存圖像,它必須只緩存最多35個東西。

由於我不知道的原因,iOS不會自動從緩存中清除對象,並且在緩存上調用removeAllObjects也不起作用。 這種解決方案並不理想,因為它可能不會充分利用緩存或者可能過度使用緩存,但同時它至少會阻止緩存嘗試存儲無限數量的對象。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM