Lazy懒加载(延迟加载)UITableView

来源:互联网 发布:会计核算软件说明书 编辑:程序博客网 时间:2024/05/01 15:27

           举个例子,当我们在用网易新闻App时,看着那么多的新闻,并不是所有的都是我们感兴趣的,有的时候我们只是很快的滑过,想要快速的略过不喜欢的内容,但是只要滑动经过了,图片就开始加载了,这样用户体验就不太好,而且浪费内存.

             这个时候,我们就可以利用lazy加载技术,当界面滑动或者滑动减速的时候,都不进行图片加载,只有当用户不再滑动并且减速效果停止的时候,才进行加载.

              刚开始我异步加载图片利用SDWebImage来做,最后试验的时候出现了重用bug,因为虽然SDWebImage实现了异步加载缓存,当加载完图片后再请求会直接加载缓存中的图片,注意注意注意,关键的来了,如果是lazy加载,滑动过程中是不进行网络请求的,cell上的图片就会发生重用,当你停下来能进行网络请求的时候,才会变回到当前Cell应有的图片,大概1-2秒的延迟吧(不算延迟,就是没有进行请求,也不是没有缓存的问题).怎么解决呢?这个时候我们就要在Model对象中定义个一个UIImage的属性,异步下载图片后,用已经缓存在沙盒中的图片路径给它赋值,这样,才cellForRowAtIndexPath方法中,判断这个UIImage对象是否为空,若为空,就进行网络请求,不为空,就直接将它赋值给cell的imageView对象,这样就能很好的解决图片短暂重用问题.

              @下面我的代码用的是自己写的异步加载缓存类,SDWebImage的加载图片的懒加载,会在后面的章节给出.(为什么不同呢,因为SDWebImage我以前使用重来不关心它将图片存储在沙盒中的名字和路径,但是要实现懒加载的话,一定要得到图片路径,所以在找SDWebImage如何存储图片路径上花了点时间)

[objc] view plaincopy在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 plaincopy在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 plaincopy在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. }  
0 0