iOS---tableview加载图片的时候的优化之lazy(懒加载)模式and异步加载模式

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

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

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

@下面我的代码用的是自己写的异步加载缓存类,SDWebImage的加载图片的懒加载,原理差不多.

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@model
# import <foundation foundation.h= "" >
 
@interface NewsItem : NSObject
 
@property (nonatomic,copy) NSString * newsTitle;
@property (nonatomic,copy) NSString * newsPicUrl;
@property (nonatomic,retain) UIImage * newsPic; //  存储每个新闻自己的image对象
 
- (id)initWithDictionary:(NSDictionary *)dic;
 
//  处理解析
+ (NSMutableArray *)handleData:(NSData *)data;
@end
 
 
# import "NewsItem.h"
# import "ImageDownloader.h"
 
@implementation NewsItem
 
- ( void )dealloc
{
     self.newsTitle = nil;
     self.newsPicUrl = nil;
     self.newsPic = nil;
     [ super dealloc];
}
 
- (id)initWithDictionary:(NSDictionary *)dic
{
     self = [ super init];
     if (self) {
 
 
         self.newsTitle = [dic objectForKey:@ "title" ];
         self.newsPicUrl = [dic objectForKey:@ "picUrl" ];
         
         //从本地沙盒加载图像
         ImageDownloader * downloader = [[[ImageDownloader alloc] init] autorelease];
         self.newsPic = [downloader loadLocalImage:_newsPicUrl];
 
     }
 
     return self;
}
 
+ (NSMutableArray *)handleData:(NSData *)data;
{
 
         //解析数据
         NSError * error = nil;
         NSDictionary * dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
         NSMutableArray * originalArray = [dic objectForKey:@ "news" ];
 
         //封装数据对象
         NSMutableArray * resultArray = [NSMutableArray array];
     
         for ( int i= 0 ;i<[originalArray count]; i++) {
             NSDictionary * newsDic = [originalArray objectAtIndex:i];
             NewsItem * item = [[NewsItem alloc] initWithDictionary:newsDic];
             [resultArray addObject:item];
             [item release];
         }
 
         return resultArray;
 
}
 
@end
</foundation>

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
@图片下载类
# import <foundation foundation.h= "" >
 
 
@class NewsItem;
 
 
@interface ImageDownloader : NSObject
 
 
@property (nonatomic,copy) NSString * imageUrl;
@property (nonatomic,retain) NewsItem * newsItem; //下载图像所属的新闻
 
 
//图像下载完成后,使用block实现回调
@property (nonatomic,copy) void (^completionHandler)( void );
 
 
//开始下载图像
- ( void )startDownloadImage:(NSString *)imageUrl;
 
 
//从本地加载图像
- (UIImage *)loadLocalImage:(NSString *)imageUrl;
 
 
@end
 
 
 
 
# import "ImageDownloader.h"
# import "NewsItem.h"
 
 
@implementation ImageDownloader
 
 
- ( void )dealloc
{
     self.imageUrl = nil;
     Block_release(_completionHandler);
     [ super dealloc];
}
 
 
 
 
#pragma mark - 异步加载
- ( void )startDownloadImage:(NSString *)imageUrl
{
 
 
     self.imageUrl = imageUrl;
 
 
     // 先判断本地沙盒是否已经存在图像,存在直接获取,不存在再下载,下载后保存
     // 存在沙盒的Caches的子文件夹DownloadImages中
     UIImage * image = [self loadLocalImage:imageUrl];
 
 
     if (image == nil) {
 
 
         // 沙盒中没有,下载
         // 异步下载,分配在程序进程缺省产生的并发队列
         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
 
 
             // 多线程中下载图像
             NSData * imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];
 
 
             // 缓存图片
             [imageData writeToFile:[self imageFilePath:imageUrl] atomically:YES];
 
 
             // 回到主线程完成UI设置
             dispatch_async(dispatch_get_main_queue(), ^{
 
 
                 //将下载的图像,存入newsItem对象中
                 UIImage * image = [UIImage imageWithData:imageData];
                 self.newsItem.newsPic = image;
 
 
                 //使用block实现回调,通知图像下载完成
                 if (_completionHandler) {
                     _completionHandler();
                 }
                 
             });
             
         });
     }
     
}
 
#pragma mark - 加载本地图像
- (UIImage *)loadLocalImage:(NSString *)imageUrl
{
 
     self.imageUrl = imageUrl;
 
 
     // 获取图像路径
     NSString * filePath = [self imageFilePath:self.imageUrl];
 
 
     UIImage * image = [UIImage imageWithContentsOfFile:filePath];
 
 
     if (image != nil) {
         return image;
     }
 
     return nil;
}
 
#pragma mark - 获取图像路径
- (NSString *)imageFilePath:(NSString *)imageUrl
{
     // 获取caches文件夹路径
     NSString * cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
 
 
     // 创建DownloadImages文件夹
     NSString * downloadImagesPath = [cachesPath stringByAppendingPathComponent:@ "DownloadImages" ];
     NSFileManager * fileManager = [NSFileManager defaultManager];
     if (![fileManager fileExistsAtPath:downloadImagesPath]) {
 
 
         [fileManager createDirectoryAtPath:downloadImagesPath withIntermediateDirectories:YES attributes:nil error:nil];
     }
 
 
#pragma mark 拼接图像文件在沙盒中的路径,因为图像URL有 "/" ,要在存入前替换掉,随意用 "_" 代替
     NSString * imageName = [imageUrl stringByReplacingOccurrencesOfString:@ "/" withString:@ "_" ];
     NSString * imageFilePath = [downloadImagesPath stringByAppendingPathComponent:imageName];
 
 
     return imageFilePath;
}
 
@end </foundation>

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
@这里只给出关键代码,网络请求,数据处理,自定义cell自行解决
 
#pragma mark - Table view data source
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
     // Return the number of sections.
     return 1 ;
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
     // Return the number of rows in the section.
     if (_dataArray.count == 0 ) {
         return 10 ;
     }
     return [_dataArray count];
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
     static NSString *cellIdentifier = @ "Cell" ;
     NewsListCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier ];
     if (!cell) {
         cell = [[[NewsListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
     }
 
     NewsItem * item = [_dataArray objectAtIndex:indexPath.row];
 
     cell.titleLabel.text = item.newsTitle;
 
     //判断将要展示的新闻有无图像
 
     if (item.newsPic == nil) {
         //没有图像下载
         cell.picImageView.image = nil;
         
         NSLog(@ "dragging = %d,decelerating = %d" ,self.tableView.dragging,self.tableView.decelerating);
         // ??执行的时机与次数问题
         if (self.tableView.dragging == NO && self.tableView.decelerating == NO) {
             [self startPicDownload:item forIndexPath:indexPath];
         }
 
     } else {
         //有图像直接展示
         NSLog(@ "1111" );
         cell.picImageView.image = item.newsPic;
 
     }
     
     cell.titleLabel.text = [NSString stringWithFormat:@ "indexPath.row = %ld" ,indexPath.row];
 
     return cell;
}
 
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
     return [NewsListCell cellHeight];
}
 
//开始下载图像
- ( void )startPicDownload:(NewsItem *)item forIndexPath:(NSIndexPath *)indexPath
{
     //创建图像下载器
     ImageDownloader * downloader = [[ImageDownloader alloc] init];
 
     //下载器要下载哪个新闻的图像,下载完成后,新闻保存图像
     downloader.newsItem = item;
 
     //传入下载完成后的回调函数
     [downloader setCompletionHandler:^{
 
         //下载完成后要执行的回调部分,block的实现
         //根据indexPath获取cell对象,并加载图像
#pragma mark cellForRowAtIndexPath-->没看到过
         NewsListCell * cell = (NewsListCell *)[self.tableView cellForRowAtIndexPath:indexPath];
         cell.picImageView.image = downloader.newsItem.newsPic;
 
     }];
 
     //开始下载
     [downloader startDownloadImage:item.newsPicUrl];
 
     [downloader release];
}
 
 
- ( void )loadImagesForOnscreenRows
{
#pragma mark indexPathsForVisibleRows-->没看到过
     //获取tableview正在window上显示的cell,加载这些cell上图像。通过indexPath可以获取该行上需要展示的cell对象
     NSArray * visibleCells = [self.tableView indexPathsForVisibleRows];
     for (NSIndexPath * indexPath in visibleCells) {
         NewsItem * item = [_dataArray objectAtIndex:indexPath.row];
         if (item.newsPic == nil) {
             //如果新闻还没有下载图像,开始下载
             [self startPicDownload:item forIndexPath:indexPath];
         }
     }
}
 
#pragma mark - 延迟加载关键
//tableView停止拖拽,停止滚动
- ( void )scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
     //如果tableview停止滚动,开始加载图像
     if (!decelerate) {
 
         [self loadImagesForOnscreenRows];
     }
      NSLog(@ "%s__%d__|%d" ,__FUNCTION__,__LINE__,decelerate);
}
 
- ( void )scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
     //如果tableview停止滚动,开始加载图像
     [self loadImagesForOnscreenRows];
 
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值