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
    评论
iOS 开发中,TableView 是一个非常重要的视图控件,用于展示大量数据。而 TableView 中的 HeaderView 和 FooterView 也是非常重要的组件,可以用于展示一些额外的信息或者操作按钮。在 TableView 中,我们可以通过注册重用标识符来复用 Cell,但是对于 HeaderView 和 FooterView 却没有提供类似的注册方法。本文将介绍如何在 TableView 中循环利用 HeaderView,并且还会介绍如何自定义 HeaderView。 ## 循环利用 TableView 中的 HeaderView 在 TableView 中,我们可以通过以下方法来设置 HeaderView: ``` - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.bounds.size.width, 44)]; headerView.backgroundColor = [UIColor grayColor]; return headerView; } - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { return 44; } ``` 上面的代码中,我们通过实现 `tableView:viewForHeaderInSection:` 方法来设置 HeaderView 的内容和样式,通过实现 `tableView:heightForHeaderInSection:` 方法来设置 HeaderView 的高度。但是如果我们在 TableView 中有很多组数据,每次都创建一个新的 HeaderView 会非常消耗性能,因此我们需要对 HeaderView 进行循环利用。 循环利用 HeaderView 的实现方法非常简单,我们只需要在 TableView 的代理方法中通过 `dequeueReusableHeaderFooterViewWithIdentifier:` 方法来获取 HeaderView,如果获取到的 HeaderView 为 nil,就创建一个新的 HeaderView,否则就返回重用的 HeaderView。 ``` - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { static NSString *headerIdentifier = @"headerIdentifier"; UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerIdentifier]; if (!headerView) { headerView = [[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:headerIdentifier]; headerView.contentView.backgroundColor = [UIColor grayColor]; } return headerView; } ``` 上面的代码中,我们首先定义一个静态的重用标识符 `headerIdentifier`,然后通过 `dequeueReusableHeaderFooterViewWithIdentifier:` 方法获取重用的 HeaderView。如果获取到的 HeaderView 为 nil,我们就创建一个新的 HeaderView,并且设置它的重用标识符为 `headerIdentifier`。最后,我们设置 HeaderView 的背景颜色,并且返回 HeaderView。 ## 自定义 TableView 中的 HeaderView 除了循环利用 HeaderView,我们还可以自定义 HeaderView。自定义 HeaderView 的方法与自定义 Cell 的方法类似,我们需要在 XIB 或者代码中创建一个自定义的 HeaderView,然后在 TableView 的代理方法中返回它。 ### 在 XIB 中创建自定义 HeaderView 在 XIB 中创建自定义 HeaderView 的方法非常简单,我们只需要创建一个新的 XIB 文件,然后在 XIB 中添加一个 UIView,将它的 Class 设置为 UITableViewHeaderFooterView,接着就可以在 XIB 中自定义 HeaderView 的内容和样式了。 创建完成后,我们需要在代码中注册这个 XIB 文件,并且设置它的重用标识符。在 TableView 的初始化方法中,我们可以通过以下方法来注册 XIB 文件: ``` UINib *headerNib = [UINib nibWithNibName:@"HeaderView" bundle:nil]; [tableView registerNib:headerNib forHeaderFooterViewReuseIdentifier:@"headerIdentifier"]; ``` 上面的代码中,我们首先通过 `nibWithNibName:bundle:` 方法加载 XIB 文件,然后通过 `registerNib:forHeaderFooterViewReuseIdentifier:` 方法注册 XIB 文件,并且设置它的重用标识符为 `headerIdentifier`。 最后,在 TableView 的代理方法中,我们可以通过以下方法来获取自定义的 HeaderView: ``` - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { static NSString *headerIdentifier = @"headerIdentifier"; UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerIdentifier]; return headerView; } ``` ### 在代码中创建自定义 HeaderView 在代码中创建自定义 HeaderView 的方法也非常简单,我们只需要创建一个继承自 UITableViewHeaderFooterView 的类,然后在这个类中实现自定义 HeaderView 的内容和样式。 ``` @interface CustomHeaderView : UITableViewHeaderFooterView @property (nonatomic, strong) UILabel *titleLabel; @end @implementation CustomHeaderView - (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier { if (self = [super initWithReuseIdentifier:reuseIdentifier]) { self.contentView.backgroundColor = [UIColor grayColor]; self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, self.contentView.bounds.size.width - 20, self.contentView.bounds.size.height)]; self.titleLabel.font = [UIFont systemFontOfSize:16]; self.titleLabel.textColor = [UIColor whiteColor]; [self.contentView addSubview:self.titleLabel]; } return self; } @end ``` 上面的代码中,我们创建了一个名为 `CustomHeaderView` 的类,继承自 UITableViewHeaderFooterView。在这个类的初始化方法中,我们设置了 HeaderView 的背景颜色,并且创建了一个 UILabel 来显示标题,最后将它添加到 HeaderView 的 contentView 上。 创建完成后,我们需要在代码中注册这个自定义 HeaderView,并且设置它的重用标识符。在 TableView 的初始化方法中,我们可以通过以下方法来注册自定义 HeaderView: ``` [tableView registerClass:[CustomHeaderView class] forHeaderFooterViewReuseIdentifier:@"headerIdentifier"]; ``` 上面的代码中,我们通过 `registerClass:forHeaderFooterViewReuseIdentifier:` 方法注册自定义 HeaderView,并且设置它的重用标识符为 `headerIdentifier`。 最后,在 TableView 的代理方法中,我们可以通过以下方法来获取自定义的 HeaderView: ``` - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { static NSString *headerIdentifier = @"headerIdentifier"; CustomHeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerIdentifier]; headerView.titleLabel.text = [NSString stringWithFormat:@"Header %ld", section]; return headerView; } ``` 上面的代码中,我们首先定义一个静态的重用标识符 `headerIdentifier`,然后通过 `dequeueReusableHeaderFooterViewWithIdentifier:` 方法获取自定义的 HeaderView,并且设置它的标题为 `Header section`。最后,我们返回自定义的 HeaderView。 总结 在本文中,我们介绍了如何在 TableView 中循环利用 HeaderView,并且还介绍了如何自定义 HeaderView。循环利用 HeaderView 可以提高 TableView 的性能,自定义 HeaderView 可以让我们更加灵活地控制 HeaderView 的样式和内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值