2013-05-22 100 views
5

我需要調整大量本地存儲的圖像(包含在self.optionArray),然後顯示它在的CollectionView。如果我只是展示它,iOS會嘗試調整圖像的大小,因爲我快速滾動導致與內存相關的崩潰。UICollectionView細胞圖像改變,因爲它進入視野與GCD

在下面的代碼,所述的CollectionView將平穩滾動,但有時,如果我滾動速度極快,會有不正確的圖像顯示,然後改變到正確的一個作爲滾動減速。爲什麼不將cell.cellImage.image設置爲nil修復此問題?

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

    CustomTabBarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CustomTabBarCell" forIndexPath:indexPath]; 
    cell.cellImage.image = nil; 
      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 

      dispatch_async(queue, ^{ 
       cell.cellImage.image = nil; 
       UIImage *test = [self.optionArray objectAtIndex:indexPath.row]; 
       UIImage *localImage2 = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)]; 

       dispatch_sync(dispatch_get_main_queue(), ^{ 

        cell.cellImage.image = localImage2 
        cell.cellTextLabel.text = @""; 
        [cell setNeedsLayout]; 
       }); 

      }); 

     } 

    return cell; 
    } 

- (UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize { 
    UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0); 
    [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)]; 
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 
    UIGraphicsEndImageContext(); 
    return newImage; 
} 

編輯: 我又增加了異步緩存第一和零和初始化cell.image。我在初始快速向下滾動時遇到了同樣的問題。然而,在翻卷的時候,它現在完美無瑕。

我加了這一點:

-(void)createDictionary 
{ 
    for (UIImage *test in self.optionArray) { 
     UIImage *shownImage = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)]; 
     [localImageDict setObject:shownImage forKey:[NSNumber numberWithInt:[self.optionArray indexOfObject:test]]]; 
    } 
} 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 
    if (!localImageDict) { 
     localImageDict = [[NSMutableDictionary alloc]initWithCapacity:self.optionArray.count]; 
    } 
    else { 
     [localImageDict removeAllObjects]; 
    } 
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 

    dispatch_async(queue, ^{ 
     [self createDictionary]; 
    }); 

} 
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 
{ 
    CustomTabBarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CustomTabBarCell" forIndexPath:indexPath]; 
    cell.cellImage.image = nil; 
    cell.cellImage.image = [[UIImage alloc]init]; 

     if ([localImageDict objectForKey:[NSNumber numberWithInt:indexPath.row]]) { 
      cell.cellImage.image = [localImageDict objectForKey:[NSNumber numberWithInt:indexPath.row]]; 
      cell.cellTextLabel.text = @""; 
     } 
    else { 

     cell.cellImage.image = nil; 
     cell.cellImage.image = [[UIImage alloc]init]; 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 

     dispatch_async(queue, ^{ 
      UIImage *test = [self.optionArray objectAtIndex:indexPath.row]; 
      UIImage *shownImage = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)]; 
      [localImageDict setObject:shownImage forKey:[NSNumber numberWithInt:indexPath.row]]; 

      dispatch_sync(dispatch_get_main_queue(), ^{ 

       cell.cellImage.image = shownImage; 

       cell.cellTextLabel.text = @""; 
       [cell setNeedsLayout]; 
      }); 

     }); 
    } 

} 
return cell; 

回答

6

以在您的代碼示例仔細一看,我可以看到你的記憶問題的根源。跳出的最重要的問題是,您似乎將所有圖像都放在數組中。這需要非常多的內存(並且我從您需要調整大小的圖像推斷它們必須很大)。

爲了減少您的應用程序的足跡,你不應該保持UIImage對象的數組。相反,只需維護一組URL或指向圖像的路徑,然後僅根據UI需要(即稱爲延遲加載的進程),隨時創建對象即時創建UIImage對象。一旦圖像離開屏幕,您可以釋放它(UICollectionView,就像UITableView一樣,只要您不保留對圖像的強烈引用,就會爲您做很多清理工作)。

的應用程序通常只應維持當前可見的圖像UIImage對象。出於性能原因,您可能會緩存這些調整大小的圖像(例如,使用NSCache),但緩存將在內存不足時自動清除。

的好處是,你明明在異步處理已經精通。總之,實施看起來可能是這樣:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 
{ 
    CustomTabBarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CustomTabBarCell" forIndexPath:indexPath]; 

    NSString *filename = [self.filenameArray objectAtIndex:indexPath.row]; // I always use indexPath.item, but if row works, that's great 

    UIImage *image = [self.thumbnailCache objectForKey:filename];   // you can key this on whatever you want, but the filename works 

    cell.cellImage.image = image;           // this will load cached image if found, or `nil` it if not found 

    if (image == nil)              // we only need to retrieve image if not found in our cache 
    { 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 

     dispatch_async(queue, ^{ 
      UIImage *test = [UIImage imageWithContentsOfFile:filename]; // load the image here, now that we know we need it 
      if (!test) 
      { 
       NSLog(@"%s: unable to load image", __FUNCTION__); 
       return; 
      } 

      UIImage *localImage2 = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)]; 
      if (!localImage2) 
      { 
       NSLog(@"%s: unable to convert image", __FUNCTION__); 
       return; 
      } 

      [self.thumbnailCache setObject:localImage2 forKey:filename]; // save the image to the cache 

      dispatch_async(dispatch_get_main_queue(), ^{     // async is fine; no need to keep this background operation alive, waiting for the main queue to respond 
       // see if the cell for this indexPath is still onscreen; probably is, but just in case 

       CustomTabBarCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath]; 
       if (updateCell) 
       { 
        updateCell.cellImage.image = localImage2 
        updateCell.cellTextLabel.text = @""; 
        [updateCell setNeedsLayout]; 
       } 
      }); 

     }); 
    } 

    return cell; 
} 

這是假設你定義的thumbnailCache類屬性,它是一個強大的參照NSCache,你會在viewDidLoad,或任何初始化。高速緩存是一種獲得兩全其美的方式,可以將內存中的圖像加載到內存中以獲得最佳性能,但是當您遇到內存壓力時它會被釋放。很明顯,我很樂意假設「哦,只是用一組圖像文件名替換你的圖像數組」,並且我知道你可能必須進入你的代碼的一些不同部分才能做出工作,但這無疑是你內存消耗的來源。顯然,你總是可能有其他內存問題(保留週期等),但是在你發佈的代碼片段中沒有這樣的內容。

+0

所有的圖像都是本地的,沒有一個是從服務器上下載的。這是否會改變您的任何建議? – Eric

+0

@Eric啊,對不起,我沒有注意到。在這種情況下,我的第二點不可能表現出來(儘管理論上可以)。第四點也不是什麼問題。但是,第一點和第三點絕對相關,如果您使用緩存,則可能會看到第四點的性能略有改善。我鼓勵你在最慢的設備上測試你的代碼,因爲你不會在模擬器或更新的設備上看到這些問題。 – Rob

+0

你可以看看我的編輯? – Eric

1

我有一個類似的問題,但以一種不同的方式去了。

我也有「卡扣式」問題作爲加載異步閃蒸,因爲他們進來,直到最後正確的結果顯示圖像。

發生這種情況的一個原因是,最初出隊的單元格的當前索引路徑與您放入圖像的索引不匹配。

基本上,如果從0-19快速滾動,並且要更新的單元格是#20,並且希望它顯示圖像20,但它仍然異步加載圖像3,7,14。

爲了防止這種情況,我所做的是追蹤兩個指標; #1)反映單元實際位置的最新索引路徑和#2)實際加載異步的圖像對應的索引(在這種情況下實際上應該是您傳遞到cellforitematindexpath的索引路徑,它會保留爲異步進程通過隊列工作,所以對於某些圖像加載實際上會是「舊」數據)。

獲取最近的索引路徑的一種方法可能是創建一個簡單的方法,該方法僅爲單元的當前位置返回一個NSInteger。將其存儲爲currentIndex。

然後,如果在實際填寫圖像之前檢查了兩者是否相等,則我會放入一對夫婦。

so if(currentIndex == imageIndex)then load image。

如果你在這些if語句之前放置NSLog(@「CURRENT ...%d ... IMAGE ...%d」,currentIndex,imageIndex),你可以在兩者不匹配的時候看得很清楚,異步調用應該退出。

希望這會有所幫助。

1

我發現了chuey101說的話,令人困惑。我想出了一個辦法,然後意識到chuey101的意思是一樣的。

如果要幫助任何人,圖像會因運行的不同線程而閃爍併發生更改。所以,當你爲圖像操作產生線程時,它會產生一個特定的單元格號,例如c1。但是,最後當你真的把你的圖像加載到單元格中時,它將成爲你正在查看的當前單元格,即你滾動的那個單元格 - 比如說c2。所以,當你滾動到c2時,當你滾動時,會產生c2個線程,每個單元格前一個。據我所知,所有這些線程都會嘗試將它們的圖像加載到當前單元格c2中。所以,你有閃光的圖像。

爲了避免這種情況,您需要實際檢查您是否將所需的圖像加載到要加載的單元格中。因此,在加載圖像之前獲取collectionviewcell indexpath.row(loading_image_into_cell)。另外,在產生線程之前,也就是在主線程(image_num_to_load)中,獲取您產生線程的單元格。現在,在加載之前,檢查這兩個數字是否相等。

問題解決:)