2013-10-11 15 views
11

Tôi đã tìm kiếm một cách cắt rõ ràng để làm điều này và đã không tìm thấy bất cứ nơi nào mà sẽ đưa ra một ví dụ và giải thích nó rất tốt. Tôi hi vọng bạn có thể giúp tôi vượt qua.Cách thích hợp để sử dụng NSCache với dispatch_async trong ô bảng tái sử dụng là gì?

Đây là mã của tôi mà tôi đang sử dụng:

lợi nhuận
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 

static NSString *CellIdentifier = @"NewsCell"; 
NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 

// Configure the cell... 

NewsItem *item = [newsItemsArray objectAtIndex:indexPath.row]; 

cell.newsTitle.text = item.title; 

NSCache *cache = [_cachedImages objectAtIndex:indexPath.row]; 

[cache setName:@"image"]; 
[cache setCountLimit:50]; 

UIImage *currentImage = [cache objectForKey:@"image"]; 

if (currentImage) { 
    NSLog(@"Cached Image Found"); 
    cell.imageView.image = currentImage; 
}else { 
    NSLog(@"No Cached Image"); 

    cell.newsImage.image = nil; 

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL), ^(void) 
        { 
         NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.image]]; 
         dispatch_async(dispatch_get_main_queue(), ^(void) 
         { 
          cell.newsImage.image = [UIImage imageWithData:imageData]; 
          [cache setValue:[UIImage imageWithData:imageData] forKey:@"image"]; 
          NSLog(@"Record String = %@",[cache objectForKey:@"image"]); 
         }); 
        }); 
} 

return cell; 
} 

Cache Nil cho tôi.

+1

Bạn có thể muốn xem dự án SDWebImage, điều này đã tìm ra tất cả. –

+0

Bạn có thể cung cấp liên kết không? – mcphersonjr

+2

https://github.com/rs/SDWebImage – Rob

Trả lời

15

Nitin đã trả lời câu hỏi về cách sử dụng một bộ nhớ cache khá tốt. Vấn đề là, cả câu hỏi ban đầu và câu trả lời của Nitin đều gặp phải vấn đề mà bạn đang sử dụng GCD, cái mà (a) không cung cấp quyền kiểm soát số lượng yêu cầu đồng thời; và (b) các khối được gửi đi không thể hủy bỏ được. Ngoài ra, bạn đang sử dụng dataWithContentsOfURL, không thể hủy.

Xem video WWDC 2012 Asynchronous Design Patterns with Blocks, GCD, and XPC, phần 7, "Điều khiển riêng biệt và lưu lượng dữ liệu", khoảng 48 phút vào video để thảo luận về lý do tại sao điều này có vấn đề, cụ thể là nếu người dùng nhanh chóng cuộn danh sách xuống mục thứ 100, tất cả 99 yêu cầu khác sẽ được xếp hàng đợi. Bạn có thể, trong trường hợp cực đoan, sử dụng hết tất cả các chuỗi công nhân có sẵn. Và iOS chỉ cho phép năm yêu cầu mạng đồng thời, vì vậy không có điểm nào trong việc sử dụng hết tất cả các chuỗi đó (và nếu một số khối gửi đi bắt đầu yêu cầu không thể khởi động được vì có hơn năm yêu cầu, một số yêu cầu mạng của bạn sẽ bắt đầu thất bại).

Vì vậy, ngoài việc tiếp cận hiện tại của bạn thực hiện các yêu cầu mạng không đồng bộ và sử dụng một bộ nhớ cache, bạn nên:

  1. Sử dụng hàng đợi hoạt động, cho phép bạn để (a) hạn chế số lượng yêu cầu đồng thời; và (b) mở ra khả năng hủy bỏ hoạt động;

  2. Sử dụng hủy có thể hủy NSURLSession. Bạn có thể tự làm điều này hoặc sử dụng thư viện như AFNetworking hoặc SDWebImage.

  3. Khi ô được sử dụng lại, hãy hủy mọi yêu cầu đang chờ xử lý (nếu có) cho ô trước đó.

Điều này có thể được thực hiện và chúng tôi có thể chỉ cho bạn cách thực hiện đúng cách nhưng có nhiều mã. Cách tiếp cận tốt nhất là sử dụng một trong nhiều loại UIImageView, làm bộ nhớ cache, nhưng cũng xử lý tất cả các mối quan tâm khác.Danh mục UIImageView của SDWebImage là khá tốt. Và nó đơn giản hóa mã của bạn:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
    static NSString *cellIdentifier = @"NewsCell"; // BTW, stay with your standard single cellIdentifier 

    NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier indexPath:indexPath]; 

    NewsItem *item = newsItemsArray[indexPath.row]; 

    cell.newsTitle.text = item.title; 

    [cell.imageView sd_setImageWithURL:[NSURL URLWithString:item.image] 
         placeholderImage:[UIImage imageNamed:@"placeholder.png"]]; 

    return cell; 
} 
+1

giải thích rất hay và tôi cũng học được điều đúng cho câu trả lời rất hay của nó cho u. thực sự tôi chỉ đưa ra câu trả lời theo câu hỏi của NSCache. nhưng có Sdwebimage cũng như AFnetworking cũng rất tốt cho việc thực hiện nhiệm vụ này –

+0

@Rob là có một cách tốt hơn để đặt câu hỏi hơn cách tôi đã làm điều đó sẽ dẫn đến một sự hiểu biết tốt hơn về câu trả lời của bạn. Cảm ơn câu trả lời bằng cách này, tôi thấy nó rất hữu ích. – mcphersonjr

+0

@Cedric Không, tôi nghĩ câu hỏi của bạn là tốt. Bạn đã hỏi "làm cách nào để lưu vào bộ nhớ cache" và bạn đã cung cấp đủ ngữ cảnh để ngăn chúng tôi la hét "cung cấp mã!" hoặc "bạn đã thử cái gì ?!" lol. Nó chỉ là giả định rằng người ta nên sử dụng GCD hóa ra, trong trường hợp này, để không được tối ưu. Đây là một trong những trường hợp mà Apple khuyên dùng hàng đợi hoạt động. Và danh mục 'UIImageView' đó chỉ cần tiến thêm một bước nữa, giúp bạn thoát khỏi những thứ cỏ dại của mã dựa trên' NSOperation' đó. Nhưng, nếu có điều gì đó mà bạn không hiểu trong câu trả lời của tôi (đặc biệt sau khi xem video đó), hãy cho chúng tôi biết. – Rob

10

Có thể bạn đã làm một số sai lầm mà bạn thiết lập cùng chính cho mỗi hình ảnh của NSCache

[cache setValue:[UIImage imageWithData:imageData] forKey:@"image"]; 

Sử dụng này Thay vì bộ forKey trên như một ImagePath item.image và sử dụng setObject thay vì setVlaue: -

[self.imageCache setObject:image forKey:item.image]; 

thử với ví dụ Bộ luật này: -

trong lớp .h: -

@property (nonatomic, strong) NSCache *imageCache; 

trong lớp .m: -

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    self.imageCache = [[NSCache alloc] init]; 

    // the rest of your viewDidLoad 
} 

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

    static NSString *cellIdentifier = @"cell"; 
    NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 

    NewsItem *item = [newsItemsArray objectAtIndex:indexPath.row]; 
    cell.newsTitle.text = item.title; 

    UIImage *cachedImage = [self.imageCache objectForKey:item.image];; 
    if (cachedImage) 
    { 
     cell.imageView.image = cachedImage; 
    } 
    else 
    { 
     cell.imageView.image = [UIImage imageNamed:@"blankthumbnail.png"]; 

     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 

       NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.image]]; 
       UIImage *image = nil; 
       if (imageData) 
        image = [UIImage imageWithData:imageData]; 

       if (image) 
       { 

        [self.imageCache setObject:image forKey:item.image]; 
       } 
       dispatch_async(dispatch_get_main_queue(), ^{ 
         UITableViewCell *updateCell = [tableView cellForRowAtIndexPath:indexPath]; 
         if (updateCell) 
          cell.imageView.image = [UIImage imageWithData:imageData]; 
          NSLog(@"Record String = %@",[cache objectForKey:@"image"]); 
        }); 
      });    
    } 
    return cell; 
} 
+0

Điều gì sẽ xảy ra nếu ô được sử dụng lại trong khi hình ảnh đang tải? Có vẻ như bạn sẽ đặt sai hình ảnh trong ô. –

+0

Có, tôi cũng hy vọng sẽ lưu cache đối tượngAtIndex: indexPath.row để hình ảnh thuộc về vị trí tương ứng trong bộ nhớ cache như hàng bảng. Ngay bây giờ nó sẽ tải sai hình ảnh vào hàng sai khi bạn di chuyển trong khi nó đang tải. Tôi đã hy vọng rằng các câu trả lời có thể phản ánh rằng kể từ khi tôi đang sử dụng các tế bào tái sử dụng. Cảm ơn câu trả lời của bạn bằng cách này. – mcphersonjr

+0

@KendallHelmstetterGelner Vì Nitin khéo léo gọi 'cellForRowAtIndexPath' bên trong' dispatch_async' cuối cùng, nó đảm bảo rằng khi bạn sử dụng lại ô cho một hàng khác của bảng, nhiệm vụ được gửi đi cũ sẽ không cập nhật hàng sai một cách không thích hợp. Vì vậy, việc triển khai này thực hiện một cách duyên dáng cho phép yêu cầu cũ hoàn thành và gửi nhiệm vụ mới đến nền cho ô tiếp theo. Khá tốt việc thực hiện (mặc dù trên các kết nối chậm hoặc hình ảnh lớn, một giải pháp hơi duyên dáng hơn sẽ là hủy bỏ các yêu cầu cũ hoặc bằng cách nào đó trì hoãn chúng). – Rob

Các vấn đề liên quan