有这样的需求:

image.png
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>name</key>
<string>植物大战僵尸</string>
<key>icon</key>
<string>http://p16.qhimg.com/dr/48_48_/t0125e8d438ae9d2fbb.png</string>
<key>download</key>
<string>10311万</string>
</dict>
<dict>
<key>name</key>
<string>捕鱼达人2</string>
<key>icon</key>
<string>http://p19.qhimg.com/dr/48_48_/t0101e2931181bb540d.png</string>
<key>download</key>
<string>9982万</string>
</dict>
<dict>
<key>name</key>
<string>保卫萝卜</string>
<key>icon</key>
<string>http://p17.qhimg.com/dr/48_48_/t012d281e8ec8e27c06.png</string>
<key>download</key>
<string>8582万</string>
</dict>
<dict>
<key>name</key>
<string>找你妹</string>
<key>icon</key>
<string>http://p18.qhimg.com/dr/48_48_/t0184f949337481f071.png</string>
<key>download</key>
<string>5910万</string>
</dict>
<dict>
<key>name</key>
<string>水果忍者</string>
<key>icon</key>
<string>http://p17.qhimg.com/dr/48_48_/t015f10076f95e27e74.png</string>
<key>download</key>
<string>5082万</string>
</dict>
<dict>
<key>name</key>
<string>鳄鱼小顽皮</string>
<key>icon</key>
<string>http://p16.qhimg.com/dr/48_48_/t01885f5596e1d30172.png</string>
<key>download</key>
<string>3918万</string>
</dict>
<dict>
<key>name</key>
<string>神偷奶爸</string>
<key>icon</key>
<string>http://p19.qhimg.com/dr/48_48_/t0164ad383c622aabef.png</string>
<key>download</key>
<string>3681万</string>
</dict>
<dict>
<key>name</key>
<string>时空猎人</string>
<key>icon</key>
<string>http://p17.qhimg.com/dr/48_48_/t017bc3cfcf3981b197.png</string>
<key>download</key>
<string>3645万</string>
</dict>
<dict>
<key>name</key>
<string>愤怒的小鸟</string>
<key>icon</key>
<string>http://p18.qhimg.com/dr/48_48_/t012fea7312194537c2.png</string>
<key>download</key>
<string>3552万</string>
</dict>
<dict>
<key>name</key>
<string>滑雪大冒险</string>
<key>icon</key>
<string>http://p18.qhimg.com/dr/48_48_/t01e61cbba53fb9eb82.png</string>
<key>download</key>
<string>3487万</string>
</dict>
<dict>
<key>name</key>
<string>爸爸去哪儿</string>
<key>icon</key>
<string>http://p18.qhimg.com/dr/48_48_/t0108c33d3321352682.png</string>
<key>download</key>
<string>3117万</string>
</dict>
<dict>
<key>name</key>
<string>我叫MT </string>
<key>icon</key>
<string>http://p17.qhimg.com/dr/48_48_/t01077fd80ffb5c8740.png</string>
<key>download</key>
<string>2386万</string>
</dict>
<dict>
<key>name</key>
<string>3D终极狂飙</string>
<key>icon</key>
<string>http://p17.qhimg.com/dr/48_48_/t01f55acd4a3ed024eb.png</string>
<key>download</key>
<string>2166万</string>
</dict>
<dict>
<key>name</key>
<string>杀手2</string>
<key>icon</key>
<string>http://p16.qhimg.com/dr/48_48_/t018f89d6e0922f75a1.png</string>
<key>download</key>
<string>1951万</string>
</dict>
<dict>
<key>name</key>
<string>俄罗斯方块</string>
<key>icon</key>
<string>http://p0.qhimg.com/dr/48_48_/t0183a670f1dbff380f.png</string>
<key>download</key>
<string>1290万</string>
</dict>
<dict>
<key>name</key>
<string>刀塔传奇</string>
<key>icon</key>
<string>http://p16.qhimg.com/dr/48_48_/t01c3f62a27c3de7af5.png</string>
<key>download</key>
<string>1249万</string>
</dict>
</array>
</plist>
apps.plist文件中存储的是对应的model数据,图片是通过url存储,那么想要显示cell的图片,就需要先下载图片
初始代码:在tableView里加载cell中图片的代码是这样的:

image.png
- 上面代码存在的问题:
- 将加载图片的代码放在了主线程里面,容易造成UI卡顿,不流畅,下载图片的代码应该放在子线程
- 还有就是,每次用户拖动tableView,一个cell重复的显示消失,会造成tableView:cellForRow这个方法的重复调用,使得同一张图片重复下载,这里是应该避免的,所以应该做缓存
改进一:做内存缓存,解决重复下载问题
重复下载的原因:每次用户拖动tableview,一个cell反复出现在视野,那么cell的数据源方法会重复调用,造成重复下载
-
通过上面分析,对代码做下面处理:
- 设置一张图片,首先检查有没有缓存,有就直接设置图片,如果没有缓存,那就下载
- 定义一个可变字典属性作为内存缓存:@property(nonatomic,strong)NSMutableDictionary *images;
- 存图片的时候,将图片作为value,图片地址url作为key,存入字典:[self.images setObject:image forKey:appM.icon];
- 取图片的时候就通过url去取.判断取出来的image是否为nil来判断是否已经做了这张图片的缓存.
-
那么设置cell图片的时候就有下面的步骤:
- 通过cell对应图片的url去内存缓存中去取图片
- 如果没有取到图片(image==nil),就去下载图片,然后设置图片
- 如果取到了图片(image!=nil),那么就直接设置图片
改进二:做磁盘(沙盒)缓存,解决每次启动app之后,app又会重新下载图片的问题
出现重复下载的原因:每次重新启动app内存缓存就不在了,就会重新下载图片
-
沙盒:
- Library
- caches:缓存
- preferences:偏好设置,存放一些小的数据
- Documents:会备份的数据,官方不允许把缓存保存到这里
- Temporary:临时路径,里面的数据随时可能被删除
- Library
-
现在设置图片变为下面的步骤:
- 首先检查内存缓存,因为从内存缓存中去取比较快:
- 有内存缓存,就直接设置图片
- 没有内存缓存,就去检查沙盒/磁盘缓存
- 检查沙盒/磁盘缓存:
- 通过NSData *data = NSData dataWithContentsOfFile:fullPath去取
- 因为url里面的”/“在文件系统里面是有特殊意义的,不能直接用作图片的名称;这里做磁盘缓存有个技巧就是,用图片的url的最后一个组成部分(lastPathComponent)作为图片的名称,然后通过caches文件夹路径和图片名称拼接成图片的全路径.
- 有磁盘缓存(data!=nil),就直接设置cell图片,然后还需要保存内存缓存,以保证后面从内存缓存中更快的取出图片;
- 没有磁盘缓存(data==nil),就去从网络下载图片
- 通过NSData *data = NSData dataWithContentsOfFile:fullPath去取
- 从网络下载图片:
- 下载完毕,设置图片
- 将图片存到内存缓存中
- 将图片存到磁盘缓存中
- 首先检查内存缓存,因为从内存缓存中去取比较快:
改进后代码如下:
//检查缓存
UIImage *image = [self.images objectForKey:appM.icon];
if(image)
{
//直接设置
cell.imageView.image = image;
NSLog(@"%zd使用了内存缓存",indexPath.row);
}
else
{
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
//得到图片的名称
NSString *filename = [appM.icon lastPathComponent];
//拼接文件的全路径
NSString *fullPath = [cachesPath stringByAppendingPathComponent:filename];
//去检查磁盘缓存
NSData *data = [NSData dataWithContentsOfFile:fullPath];
if(data)
{
UIImage *image = [UIImage imageWithData:data];
cell.imageView.image = image;
//把图片存到内存缓存中
[self.images setObject:image forKey:appM.icon];
NSLog(@"%zd使用了磁盘缓存",indexPath.row);
}
else
{
NSURL *url = [NSURL URLWithString:appM.icon];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
cell.imageView.image = image;
//把图片存到内存缓存中
[self.images setObject:image forKey:appM.icon];
NSLog(@"%zd直接下载",indexPath.row);
//把图片保存到磁盘缓存
[data writeToFile:fullPath atomically:YES];
}
}
改进三:开子线程对图片进行下载,解决UI卡顿,不流畅的问题
UI卡顿的原因:主线程一般不放下载任务,不然有时候下载大的图片,会造成主线程阻塞.
- 开一个子队列:NSOperationQueue *queue,为了不重复创建队列,将这个队列设置为属性,做一个lazy加载,同时设置最大并发数为5(最多允许5个操作);
- 将图片的下载放在self.queue = [NSBlockOperationWithBlock:^{…}]中;
- 设置图片的时候返回主队列设置.
- 这里需要注意的是,虽然下载完毕回到了主线程,但因为是异步下载,return cell的时候还没把图片下载下来,等到下载完毕设置图片后,imageView就不会显示图片,此时需要刷新这一行的cell.
- -(void)reloadRowsAtIndexPaths:(NSArray*)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
//检查缓存
UIImage *image = [self.images objectForKey:appM.icon];
if(image)
{
//直接设置
cell.imageView.image = image;
NSLog(@"%zd使用了内存缓存",indexPath.row);
}else
{
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
//得到图片的名称
NSString *filename = [appM.icon lastPathComponent];
//拼接文件的全路径
NSString *fullPath = [cachesPath stringByAppendingPathComponent:filename];
//去检查磁盘缓存
NSData *data = [NSData dataWithContentsOfFile:fullPath];
//data = nil;
if(data)
{
UIImage *image = [UIImage imageWithData:data];
cell.imageView.image = image;
//把图片存到内存缓存中
[self.images setObject:image forKey:appM.icon];
NSLog(@"%zd使用了磁盘缓存",indexPath.row);
}else
{
//清空图片或者是设置占位图片
cell.imageView.image = [UIImage imageNamed:@"Snip20200808_172"];
//检查操作缓存
NSBlockOperation *downloadOperation = [self.operations objectForKey:appM.icon];
downloadOperation = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:appM.icon];
NSData *data = [NSData dataWithContentsOfURL:url];
for (NSInteger i = 0; i<1000000000; i++)
{
//模拟网络忙的情况
}
UIImage *image = [UIImage imageWithData:data];
//把图片存到内存缓存中
[self.images setObject:image forKey:appM.icon];
NSLog(@"%zd直接下载---%@",indexPath.row,[NSThread currentThread]);
//把图片保存到磁盘缓存
[data writeToFile:fullPath atomically:YES];
//设置图片
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
//cell.imageView.image = image;
//[tableView reloadData];
//刷新指定的行
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
}
}
}
改进四:设置一个NSOperations数组对象作为操作缓存,下载图片前,判断是否已经存在当前图片的下载任务,解决网络不好会重复下载图片的情况
出现重复下载的原因:网络不好时,用户反复快速拖动TableView,一个cell里面的图片如果还没下载下来,却反复出现消失在用户的视野,那么就会造成重复下载(因为在这里还没有执行后面的将图片存到内存缓存和磁盘缓存中,再次加载cell回去检查内存缓存和磁盘缓存,但是都还没有存,因此又会下载图片)
改进五:设置占位图片,解决cell复用时,数据错乱的问题()
出现数据错乱的原因:
- 综上,总体的思路是:
- 首先检查内存缓存,因为从内存缓存中去取比较快:
- 有内存缓存,就直接设置图片
- 没有内存缓存,就去检查沙盒/磁盘缓存
- 检查沙盒/磁盘缓存:
- 通过NSData *data = NSData dataWithContentsOfFile:fullPath去取
- 因为url里面的”/“在文件系统里面是有特殊意义的,不能直接用作图片的名称;这里做磁盘缓存有个技巧就是,用图片的url的最后一个组成部分(lastPathComponent)作为图片的名称,然后通过caches文件夹路径和图片名称拼接成图片的全路径.
- 有磁盘缓存(data!=nil),就直接设置cell图片,然后还需要保存内存缓存,以保证后面从内存缓存中更快的取出图片;
- 没有磁盘缓存(data==nil),就去从网络下载图片
- 通过NSData *data = NSData dataWithContentsOfFile:fullPath去取
- 检查操作缓存:(操作缓存的key也是图片的url,其实这里我觉得只需要用一个数组就可以,将图片的url添加进去)
- 已经存在下载操作,什么不做
- 不存在则开始从网络下载图片
- 从网络下载图片:
- 设置占位图片
- 下载完毕,设置图片
- 将图片存到内存缓存中
- 将图片存到磁盘缓存中
- 首先检查内存缓存,因为从内存缓存中去取比较快:
封装的cell:
//
// GSYTableViewCell.m
// 多图下载
//
// Created by gaosiyang on 16/6/17.
// Copyright © 2016年 gaosiyang. All rights reserved.
//
#import "GSYTableViewCell.h"
@interface GSYTableViewCell()
@property (weak, nonatomic) IBOutlet UIImageView *imageV;
@property (weak, nonatomic) IBOutlet UILabel *nameL;
@property (weak, nonatomic) IBOutlet UILabel *downloadL;
//@property (nonatomic,strong) NSMutableDictionary *images;
@property (nonatomic,strong) NSURLSession *session;
//@property (nonatomic,strong) NSMutableDictionary *dataTasks;
@end
@implementation GSYTableViewCell
static NSMutableDictionary *images = nil;
static NSMutableArray *dataTasks = nil;
-(NSMutableDictionary *)images
{
if (!images) {
images = [NSMutableDictionary dictionary];
}
return images;
}
-(NSURLSession *)session
{
if (!_session) {
_session = [NSURLSession sharedSession];
}
return _session;
}
-(NSMutableArray *)dataTasks
{
if (!dataTasks) {
dataTasks = [NSMutableArray array];
}
return dataTasks;
}
-(void)setItem:(AppItem *)item
{
_item=item;
//设置图片
//检查内存缓存
[self images];
[self dataTasks];
UIImage *image = [images objectForKey:item.icon];
if (image) {
self.imageV.image = image;
NSLog(@"内存缓存");
}
else
{
NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)lastObject];
NSString *imageName = [item.icon lastPathComponent];
NSString *fullPath = [path stringByAppendingPathComponent:imageName];
NSData *data = [NSData dataWithContentsOfFile:fullPath];
//检查磁盘缓存
if (data) {
self.imageV.image = [UIImage imageWithData:data];
NSLog(@"磁盘缓存");
}
else
{
if ([dataTasks containsObject:item.icon]) {
}
else
{
NSLog(@"网络下载");
//从网络下载图片
NSURL *url = [NSURL URLWithString:item.icon];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageV.image = image;
});
//做内存缓存
[images setObject:image forKey:item.icon];
//做磁盘缓存
[data writeToFile:fullPath atomically:YES];
}];
[dataTask resume];
//做下载任务缓存
[dataTasks addObject:item.icon];
}
}
}
self.nameL.text = item.name;
self.downloadL.text = item.download;
}
+(instancetype)tableViewCell
{
return [[[NSBundle mainBundle]loadNibNamed:@"GSYTableViewCell" owner:nil options:nil]lastObject];
}
@end