lazy懶加載(延遲加載)UITableView

舉個例子,當我們在用網易新聞App時,看着那麼多的新聞,並不是所有的都是我們感興趣的,有的時候我們只是很快的滑過,想要快速的略過不喜歡的內容,但是隻要滑動經過了,圖片就開始加載了,這樣用戶體驗就不太好,而且浪費內存.

             這個時候,我們就可以利用lazy加載技術,當界面滑動或者滑動減速的時候,都不進行圖片加載,只有當用戶不再滑動並且減速效果停止的時候,才進行加載.

              剛開始我異步加載圖片利用SDWebImage來做,最後試驗的時候出現了重用bug,因爲雖然SDWebImage實現了異步加載緩存,當加載完圖片後再請求會直接加載緩存中的圖片,注意注意注意,關鍵的來了,如果是lazy加載,滑動過程中是不進行網絡請求的,cell上的圖片就會發生重用,當你停下來能進行網絡請求的時候,纔會變回到當前Cell應有的圖片,大概1-2秒的延遲吧(不算延遲,就是沒有進行請求,也不是沒有緩存的問題).怎麼解決呢?這個時候我們就要在Model對象中定義個一個UIImage的屬性,異步下載圖片後,用已經緩存在沙盒中的圖片路徑給它賦值,這樣,才cellForRowAtIndexPath方法中,判斷這個UIImage對象是否爲空,若爲空,就進行網絡請求,不爲空,就直接將它賦值給cell的imageView對象,這樣就能很好的解決圖片短暫重用問題.

              @下面我的代碼用的是自己寫的異步加載緩存類,SDWebImage的加載圖片的懶加載,會在後面的章節給出.(爲什麼不同呢,因爲SDWebImage我以前使用重來不關心它將圖片存儲在沙盒中的名字和路徑,但是要實現懶加載的話,一定要得到圖片路徑,所以在找SDWebImage如何存儲圖片路徑上花了點時間)

[objc] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. @model類  
  2. #import <Foundation/Foundation.h>  
  3.   
  4. @interface NewsItem : NSObject  
  5.   
  6. @property (nonatomic,copyNSString * newsTitle;  
  7. @property (nonatomic,copyNSString * newsPicUrl;  
  8. @property (nonatomic,retainUIImage * newsPic; //  存儲每個新聞自己的image對象  
  9.   
  10. - (id)initWithDictionary:(NSDictionary *)dic;  
  11.   
  12. //  處理解析  
  13. + (NSMutableArray *)handleData:(NSData *)data;  
  14. @end  
  15.   
  16.   
  17. #import "NewsItem.h"  
  18. #import "ImageDownloader.h"  
  19.   
  20. @implementation NewsItem  
  21.   
  22. - (void)dealloc  
  23. {  
  24.     self.newsTitle = nil;  
  25.     self.newsPicUrl = nil;  
  26.     self.newsPic = nil;  
  27.     [super dealloc];  
  28. }  
  29.   
  30. - (id)initWithDictionary:(NSDictionary *)dic  
  31. {  
  32.     self = [super init];  
  33.     if (self) {  
  34.   
  35.   
  36.         self.newsTitle = [dic objectForKey:@"title"];  
  37.         self.newsPicUrl = [dic objectForKey:@"picUrl"];  
  38.           
  39.         //從本地沙盒加載圖像  
  40.         ImageDownloader * downloader = [[[ImageDownloader alloc] init] autorelease];  
  41.         self.newsPic = [downloader loadLocalImage:_newsPicUrl];  
  42.   
  43.     }  
  44.   
  45.     return self;  
  46. }  
  47.   
  48. + (NSMutableArray *)handleData:(NSData *)data;  
  49. {  
  50.   
  51.         //解析數據  
  52.         NSError * error = nil;  
  53.         NSDictionary * dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];  
  54.         NSMutableArray * originalArray = [dic objectForKey:@"news"];  
  55.   
  56.         //封裝數據對象  
  57.         NSMutableArray * resultArray = [NSMutableArray array];  
  58.       
  59.         for (int i=0 ;i<[originalArray count]; i++) {  
  60.             NSDictionary * newsDic = [originalArray objectAtIndex:i];  
  61.             NewsItem * item = [[NewsItem alloc] initWithDictionary:newsDic];  
  62.             [resultArray addObject:item];  
  63.             [item release];  
  64.         }  
  65.   
  66.         return resultArray;  
  67.   
  68. }  
  69.   
  70. @end  

[objc] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. @圖片下載類  
  2. #import <Foundation/Foundation.h>  
  3.   
  4.   
  5. @class NewsItem;  
  6.   
  7.   
  8. @interface ImageDownloader : NSObject  
  9.   
  10.   
  11. @property (nonatomic,copyNSString * imageUrl;  
  12. @property (nonatomic,retainNewsItem * newsItem; //下載圖像所屬的新聞  
  13.   
  14.   
  15. //圖像下載完成後,使用block實現回調  
  16. @property (nonatomic,copyvoid (^completionHandler)(void);  
  17.   
  18.   
  19. //開始下載圖像  
  20. - (void)startDownloadImage:(NSString *)imageUrl;  
  21.   
  22.   
  23. //從本地加載圖像  
  24. - (UIImage *)loadLocalImage:(NSString *)imageUrl;  
  25.   
  26.   
  27. @end  
  28.   
  29.   
  30.   
  31.   
  32. #import "ImageDownloader.h"  
  33. #import "NewsItem.h"  
  34.   
  35.   
  36. @implementation ImageDownloader  
  37.   
  38.   
  39. - (void)dealloc  
  40. {  
  41.     self.imageUrl = nil;  
  42.     Block_release(_completionHandler);  
  43.     [super dealloc];  
  44. }  
  45.   
  46.   
  47.   
  48.   
  49. #pragma mark - 異步加載  
  50. - (void)startDownloadImage:(NSString *)imageUrl  
  51. {  
  52.   
  53.   
  54.     self.imageUrl = imageUrl;  
  55.   
  56.   
  57.     // 先判斷本地沙盒是否已經存在圖像,存在直接獲取,不存在再下載,下載後保存  
  58.     // 存在沙盒的Caches的子文件夾DownloadImages中  
  59.     UIImage * image = [self loadLocalImage:imageUrl];  
  60.   
  61.   
  62.     if (image == nil) {  
  63.   
  64.   
  65.         // 沙盒中沒有,下載  
  66.         // 異步下載,分配在程序進程缺省產生的併發隊列  
  67.         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  68.   
  69.   
  70.             // 多線程中下載圖像  
  71.             NSData * imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];  
  72.   
  73.   
  74.             // 緩存圖片  
  75.             [imageData writeToFile:[self imageFilePath:imageUrl] atomically:YES];  
  76.   
  77.   
  78.             // 回到主線程完成UI設置  
  79.             dispatch_async(dispatch_get_main_queue(), ^{  
  80.   
  81.   
  82.                 //將下載的圖像,存入newsItem對象中  
  83.                 UIImage * image = [UIImage imageWithData:imageData];  
  84.                 self.newsItem.newsPic = image;  
  85.   
  86.   
  87.                 //使用block實現回調,通知圖像下載完成  
  88.                 if (_completionHandler) {  
  89.                     _completionHandler();  
  90.                 }  
  91.                   
  92.             });  
  93.               
  94.         });  
  95.     }  
  96.       
  97. }  
  98.   
  99. #pragma mark - 加載本地圖像  
  100. - (UIImage *)loadLocalImage:(NSString *)imageUrl  
  101. {  
  102.   
  103.     self.imageUrl = imageUrl;  
  104.   
  105.   
  106.     // 獲取圖像路徑  
  107.     NSString * filePath = [self imageFilePath:self.imageUrl];  
  108.   
  109.   
  110.     UIImage * image = [UIImage imageWithContentsOfFile:filePath];  
  111.   
  112.   
  113.     if (image != nil) {  
  114.         return image;  
  115.     }  
  116.   
  117.     return nil;  
  118. }  
  119.   
  120. #pragma mark - 獲取圖像路徑  
  121. - (NSString *)imageFilePath:(NSString *)imageUrl  
  122. {  
  123.     // 獲取caches文件夾路徑  
  124.     NSString * cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];  
  125.   
  126.   
  127.     // 創建DownloadImages文件夾  
  128.     NSString * downloadImagesPath = [cachesPath stringByAppendingPathComponent:@"DownloadImages"];  
  129.     NSFileManager * fileManager = [NSFileManager defaultManager];  
  130.     if (![fileManager fileExistsAtPath:downloadImagesPath]) {  
  131.   
  132.   
  133.         [fileManager createDirectoryAtPath:downloadImagesPath withIntermediateDirectories:YES attributes:nil error:nil];  
  134.     }  
  135.   
  136.   
  137. #pragma mark 拼接圖像文件在沙盒中的路徑,因爲圖像URL有"/",要在存入前替換掉,隨意用"_"代替  
  138.     NSString * imageName = [imageUrl stringByReplacingOccurrencesOfString:@"/" withString:@"_"];  
  139.     NSString * imageFilePath = [downloadImagesPath stringByAppendingPathComponent:imageName];  
  140.   
  141.   
  142.     return imageFilePath;  
  143. }  
  144.   
  145. @end  


[objc] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. @這裏只給出關鍵代碼,網絡請求,數據處理,自定義cell自行解決  
  2.   
  3. #pragma mark - Table view data source  
  4.   
  5. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView  
  6. {  
  7.     // Return the number of sections.  
  8.     return 1;  
  9. }  
  10.   
  11. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section  
  12. {  
  13.     // Return the number of rows in the section.  
  14.     if (_dataArray.count == 0) {  
  15.         return 10;  
  16.     }  
  17.     return [_dataArray count];  
  18. }  
  19.   
  20. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  
  21. {  
  22.     static NSString *cellIdentifier = @"Cell";  
  23.     NewsListCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier ];  
  24.     if (!cell) {  
  25.         cell = [[[NewsListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];  
  26.     }  
  27.   
  28.     NewsItem * item = [_dataArray objectAtIndex:indexPath.row];  
  29.   
  30.     cell.titleLabel.text = item.newsTitle;  
  31.   
  32.     //判斷將要展示的新聞有無圖像  
  33.   
  34.     if (item.newsPic == nil) {  
  35.         //沒有圖像下載  
  36.         cell.picImageView.image = nil;  
  37.           
  38.         NSLog(@"dragging = %d,decelerating = %d",self.tableView.dragging,self.tableView.decelerating);  
  39.         // ??執行的時機與次數問題  
  40.         if (self.tableView.dragging == NO && self.tableView.decelerating == NO) {  
  41.             [self startPicDownload:item forIndexPath:indexPath];  
  42.         }  
  43.   
  44.     }else{  
  45.         //有圖像直接展示  
  46.         NSLog(@"1111");  
  47.         cell.picImageView.image = item.newsPic;  
  48.   
  49.     }  
  50.       
  51.     cell.titleLabel.text = [NSString stringWithFormat:@"indexPath.row = %ld",indexPath.row];  
  52.   
  53.     return cell;  
  54. }  
  55.   
  56. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath  
  57. {  
  58.     return [NewsListCell cellHeight];  
  59. }  
  60.   
  61. //開始下載圖像  
  62. - (void)startPicDownload:(NewsItem *)item forIndexPath:(NSIndexPath *)indexPath  
  63. {  
  64.     //創建圖像下載器  
  65.     ImageDownloader * downloader = [[ImageDownloader alloc] init];  
  66.   
  67.     //下載器要下載哪個新聞的圖像,下載完成後,新聞保存圖像  
  68.     downloader.newsItem = item;  
  69.   
  70.     //傳入下載完成後的回調函數  
  71.     [downloader setCompletionHandler:^{  
  72.   
  73.         //下載完成後要執行的回調部分,block的實現  
  74.         //根據indexPath獲取cell對象,並加載圖像  
  75. #pragma mark cellForRowAtIndexPath-->沒看到過  
  76.         NewsListCell * cell = (NewsListCell *)[self.tableView cellForRowAtIndexPath:indexPath];  
  77.         cell.picImageView.image = downloader.newsItem.newsPic;  
  78.   
  79.     }];  
  80.   
  81.     //開始下載  
  82.     [downloader startDownloadImage:item.newsPicUrl];  
  83.   
  84.     [downloader release];  
  85. }  
  86.   
  87.   
  88. - (void)loadImagesForOnscreenRows  
  89. {  
  90. #pragma mark indexPathsForVisibleRows-->沒看到過  
  91.     //獲取tableview正在window上顯示的cell,加載這些cell上圖像。通過indexPath可以獲取該行上需要展示的cell對象  
  92.     NSArray * visibleCells = [self.tableView indexPathsForVisibleRows];  
  93.     for (NSIndexPath * indexPath in visibleCells) {  
  94.         NewsItem * item = [_dataArray objectAtIndex:indexPath.row];  
  95.         if (item.newsPic == nil) {  
  96.             //如果新聞還沒有下載圖像,開始下載  
  97.             [self startPicDownload:item forIndexPath:indexPath];  
  98.         }  
  99.     }  
  100. }  
  101.   
  102. #pragma mark - 延遲加載關鍵  
  103. //tableView停止拖拽,停止滾動  
  104. - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate  
  105. {  
  106.     //如果tableview停止滾動,開始加載圖像  
  107.     if (!decelerate) {  
  108.   
  109.         [self loadImagesForOnscreenRows];  
  110.     }  
  111.      NSLog(@"%s__%d__|%d",__FUNCTION__,__LINE__,decelerate);  
  112. }  
  113.   
  114. - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView  
  115. {  
  116.     //如果tableview停止滾動,開始加載圖像  
  117.     [self loadImagesForOnscreenRows];  
  118.   
  119. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章