首先先了解一下我们多图片下载的一般解决方案
注:以下模拟AppStore浏览购买项目场景
主体思路
场景还原:
进入app,当图片还没显示完成时,我们不停的拖拽tableview,就会不停的调用 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath. 导致以下俩个问题.
出现问题:
- 重复下载操作
- cell重用导致图片错位
第一次进入app我们会进行下载图片,由于下载图片是一个耗时操作,此时用户进行拖拽,由于cell的重用机制移出屏幕的cell会被重用,之前的cell会被重用到下方,恰巧图片又刚好下载完成,这时候如果我们任然进行显示
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
就会出现 --- 植物大战僵尸2 捕鱼 都出现了错乱
解决办法:
1.为了防止图片下载操作重复,我们将对应的操作对象做记录,无论是成功还是失败最后我们都移除该对象,保证操作的唯一性.
/** 内存缓存的图片 */
@property (nonatomic, strong) NSMutableDictionary *images;
/** 所有的操作对象 */
@property (nonatomic, strong) NSMutableDictionary *operations;
2.下载完成我们去刷新对应的cell
// 回到主线程显示图片
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}];
整体实现代码
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"app";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
XMGApp *app = self.apps[indexPath.row];
cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;
// 先从内存缓存中取出图片
UIImage *image = self.images[app.icon];
if (image) { // 内存中有图片,显示图片
cell.imageView.image = image;
} else { // 内存中没有图片,检查沙盒中是否有图片
// 获得Library/Caches文件夹
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
// 获得文件名
NSString *filename = [app.icon lastPathComponent];
// 计算出文件的全路径
NSString *file = [cachesPath stringByAppendingPathComponent:filename];
// 加载沙盒的文件数据
NSData *data = [NSData dataWithContentsOfFile:file];
if (data) { // 沙盒中有图片,直接利用沙盒中图片
UIImage *image = [UIImage imageWithData:data];
cell.imageView.image = image;
// 存到字典中
self.images[app.icon] = image;
} else { // 沙盒中没有图片,下载图片
cell.imageView.image = [UIImage imageNamed:@"placeholder"];
//查询是否有下载任务
NSOperation *operation = self.operations[app.icon];
if (operation == nil) { // 这张图片暂时没有下载任务
operation = [NSBlockOperation blockOperationWithBlock:^{
// 下载图片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]];
// 数据加载失败
if (data == nil) {
// 移除操作
[self.operations removeObjectForKey:app.icon];
return;
}
UIImage *image = [UIImage imageWithData:data];
// 存到字典中
self.images[app.icon] = image;
// 回到主线程显示图片
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}];
// 将图片文件数据写入沙盒中
[data writeToFile:file atomically:YES];
// 移除操作
[self.operations removeObjectForKey:app.icon];
}];
// 添加到队列中
[self.queue addOperation:operation];
// 存放到字典中
self.operations[app.icon] = operation;
}
}
}
return cell;
}
小结:由于自己去完成多图片的下载 细节比较多,需要考虑cell重用错乱,重复下任务,网络错误data为空等等一系列的细节问题,为了提高开发效率我们通常会使用第三方框架
最终解决方案
第三方框架 SDWebImage
- 导入头文件
#import "UIImageView+WebCache.h"
- 加载图片设置占位图
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"placeholder"]];
对照
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"app";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
XMGApp *app = self.apps[indexPath.row];
cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;
//只需要一句话
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"placeholder"]];
return cell;
}
而且它也帮我们完成了缓存的处理
让我们简单的了解一下如何使用这个强大的第三方框架
1.下面我们以参数较多的方法举例:
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"placeholder"] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// expectedSize: 图片的总字节数
// receivedSize: 已经接收的图片字节数
NSLog(@"下载进度:%f", (double)receivedSize / expectedSize);
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(@"下载完图片");
}];
1,sd_setImageWithURL获取网络图片
2,placeholderImage占位图片
3,progress 下载进度 用法: NSLog(@"下载进步:%f",(double)receivedSize / expectedSize);
4, *image *error *imageURL分别完成后返回 的图片,错误和下载地址
5,SDImageCacheType cacheType 是枚举类型,图片存储位置在内存、磁盘或无
6,SDWebImageOptions 枚举类型
用法:SDWebImageOptions options = SDWebImageRetryFailed | SDWebImageLowPriority
SDWebImageRetryFailed 下载失败重复下载 常用
SDWebImageLowPriority 当UI交互的时候暂停下载 常用
SDWebImageCacheMemoryOnly 只存图片在内存
SDWebImageProgressiveDownload 可以像浏览器那样从上往下下载刷新图片
SDWebImageRefreshCached 刷新缓存
SDWebImageHighPriority 高优先级
SDWebImageDelayPlaceholder 不加载占位图
options参数图片
2.内存处理
因为SDWebImgae是属于整个项目,不是属于某个控制器,所以不要在控制器里的didReceiveMemoryWarning处理内存问题,而且在AppDelegate.m添加applicationDidReceiveMemoryWarning方法
- AppDelegate中 (√)
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{
// 取消所有下载
[[SDWebImageManager sharedManager] cancelAll];
// 清除内存缓存
[[SDWebImageManager sharedManager].imageCache clearMemory];
}
- 当前控制器中 出现内存警告(×)
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
self.images = nil;
self.operations = nil;
[self.queue cancelAllOperations];
}
3.其他功能
- SDWebImage的图片缓存周期是多长:1个星期
//设置100天,默认是7天
[SDWebImageManager sharedManager].imageCache.maxCacheAge = 100 * 24 * 60 * 60
- SDWebImage的图片最大尺寸(字节)
//无默认值,单位字节
[SDWebImageManager sharedManager].imageCache.maxCacheSize = ;
拓展:只下载图片不设置 --- 给Button设置图片时可以使用
[[SDWebImageManager sharedManager] downloadImageWithURL:<#(NSURL *)#> options:<#(SDWebImageOptions)#> progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
}]