2012-08-13 35 views
5

我使用Grand Central Dispatch異步加載UITableViewCell的圖像。除了在單元格被重用的一些邊界情況下,以及之前的塊加載錯誤的圖像,這種方法效果很好。與GCD重複使用UITableViewCell

我當前的代碼如下所示:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
    static NSString *CellIdentifier = @"Cell"; 

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 
    if (!cell) { 
     cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; 
    } 

    NSString *imagePath = [self imagePathForIndexPath:indexPath];   
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 

    dispatch_async(queue, ^{ 
     UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; 

     dispatch_sync(dispatch_get_main_queue(), ^{ 
      cell.imageView.image = image; 
      [cell setNeedsLayout]; 
     }); 
    }); 

    return cell; 
} 

據我所知GCD隊列不能停止。那麼如何防止這個邊界案件呢?還是應該使用別的東西而不是GCD來解決這個問題?

+0

我正在使用GMGridView來顯示非常大量的圖像縮略圖,並具有完全相同的問題。我還使用GCD從我的web服務器上即時加載縮略圖。 – Humayun 2012-08-13 09:22:22

回答

3

我所做的是在細胞中添加一個NSOperation伊娃。該操作負責獲取,加載和創建圖像。完成後,它會對電池進行排氣。當/如果單元格出隊時,操作被取消並且如果尚未完成則被銷燬。當在-main時取消操作測試。在圖像傳遞到單元格後,單元格將會丟棄該操作並使用該圖像。

+1

NSOperation肯定比GCD更好,通常使用它,並將它放入單元格內肯定是最好的(也是最優雅的)方式...... +1 – meronix 2012-08-13 12:36:19

+1

WWDC 2012會話211對此也有很好的演練 – jrturton 2012-08-15 07:40:25

1

在開始異步請求之前,您可能會嘗試強制重置圖像。

當用戶滾動表時,他可能會看到舊有形象的異步方法與正確的改變之前一個

剛加入這一行:

cell.imageView.image = nil; // or a placeHolder image 
dispatch_async(queue, ^{ 
    UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; 

    dispatch_sync(dispatch_get_main_queue(), ^{ 
     cell.imageView.image = image; 
     [cell setNeedsLayout]; 
    }); 
}); 

編輯:

@hgpc:

我不認爲這解決了問題。之前的塊仍然可以在新塊之前設置 錯誤的圖像。 - HGPC 9分鐘前

你說得對,如果用戶滾動快,這可能是一個問題......

我看到的唯一方法是使自定義單元格與反財產在何處存儲(在同步方式)operationn的每個小區中的隊列的數目,然後在異步方法檢查該計數器是== 1來改變圖像之前:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
     static NSString *CellIdentifier = @"Cell"; 

     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 
     if (!cell) { 
    // MyCustomUITableViewCell has a property counter 
      cell = [[[MyCustomUITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; 
      cell.counter = 0; 
     } 

     NSString *imagePath = [self imagePathForIndexPath:indexPath];   
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 

     cell.imageView.image = nil; // or a placeHolder image 
     cell.counter = cell.counter + 1; 
     dispatch_async(queue, ^{ 
      UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; 

      dispatch_sync(dispatch_get_main_queue(), ^{ 
       if (cell.counter == 1){ 
        cell.imageView.image = image; 
        [cell setNeedsLayout]; 
       } 
       cell.counter = cell.counter - 1; 

      }); 
     }); 

    return cell; 
} 
+0

我不認爲這解決了問題。之前的塊仍然可以在新塊之前設置錯誤的圖像。 – hpique 2012-08-13 09:06:28

+0

right ... se我的回答中的新編輯 – meronix 2012-08-13 09:35:00

+0

現在,計數器確保只設置一個圖像,但不一定是正確的。基於@ willy的建議,我認爲你可以簡單地使用indexPath設置視圖標籤,並在更改圖像之前檢查它是否相同。 – hpique 2012-08-13 09:39:29

0

您可以設置之前的小區標識符啓動異步任務,並在更新UI之前檢查它是否相同。

+0

爲什麼downvote?這似乎是一個好主意。 – hpique 2012-08-13 09:40:28

0

這裏有幾種可能性。

a)使用NSOperationQueue而不是直接的GDC。
NSOperation基於GDC並啓用了許多管理,如停止線程。
看看蘋果公司的concurrency guide

b)使用現成的異步圖像加載器。
internetz上有足夠的免費和開源項目可用。
我個人建議AFNetworking(特別是AFNetworking+UIImageView類別)。

如果你想自己做(研究,知識等))你應該留在a),但更方便的方法是b)

更新
我剛纔注意到,您從文件加載圖像,而不是從網絡。
在這種情況下,你應該進行不同的處理,並照顧在以下幾點:

  • 避免imageWithContentsOfFile:和而使用imageNamed:。這是因爲imageNamed:會在加載後緩存圖像,而imageWithContentsOfFile:則不會。如果您需要使用imageWithContentsOfFile:預加載NSCache中的所有圖像,並使用此緩存中的圖像填充表格。

  • TableView圖像的尺寸不應該很大(尺寸爲文件大小)。即使100x100像素的視網膜圖像也沒有超過幾個kb,這肯定不會花費那麼長時間來加載,你需要一個異步加載。

+0

它應該需要注意的是圖像是從文件加載的,這就是我想要做異步的延遲。沒有下載涉及。 – hpique 2012-08-13 09:42:20

+0

啊我明白了,是的。但在這種情況下,你還有其他問題。我很快就會回答我的答案。 – yinkou 2012-08-13 09:51:42

+0

所以我更新了我的答案。 – yinkou 2012-08-13 10:02:19

0

只有在表格沒有滾動時才加載圖像,怎麼辦? cellForRowWithIndexPath:的問題在於,您正在爲正在滾動的單元格執行大量工作。

您可以在viewDidLoad(或您的數據模型初始化時)以及scrollViewDidEndDecelerating:上執行,而不是在單元出列時加載圖像。

-(void)viewDidLoad { 
    [super viewDidLoad]; 
    [self initializeData]; 
    [self loadVisibleImages]; 
} 

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 
    if (scrollView == self.tableView) { 
     [self loadVisibleImages]; 
    } 
} 

-(void)loadVisibleImages { 
    for (NSIndexPath *indexPath in [self.tableview indexPathsForVisibleRows]) { 
     dispatch_async(queue, ^{ 
      NSString *imagePath = [self imagePathForIndexPath:indexPath]; 
            UIImage *image = [UIImage imageWithContentsOfFile:imagePath];   
            dispatch_sync(dispatch_get_main_queue(), ^{ 
       UITableViewCell *cell = [self tableView:self.tableView cellForRowAtIndexPath:indexPath]; 
                cell.imageView.image = image; 
                [cell setNeedsLayout]; 
            }); 
        }); 
    } 
}