多图下载
利用SDWebImage设置UIButton的图片
- 正确用法
[button sd_setImageWithURL:[NSURL URLWithString:url] forState:UIControlStateNormal placeholderImage:image];
SDWebImage(多图下载框架)
(1)SDWebImage基本使用
01 设置imageView的图片
/*
第一个参数:要下载图片的url
第二个参数:占位图片
第三个参数:下载的策略 kNilOptions表示采用默认
第四个参数:progress 获取下载进度
第五个参数:completed 下载完成
*/
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"Snip20151102_160"] options:kNilOptions progress:^(NSInteger receivedSize, NSInteger expectedSize) {
NSLog(@"%f",1.0* receivedSize/expectedSize);
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(@"下载完成");
}];
02 设置图片并计算下载进度
//下载并设置图片
/*
第一个参数:要下载图片的url地址
第二个参数:设置该imageView的占位图片
第三个参数:传一个枚举值,告诉程序你下载图片的策略是什么
第一个block块:获取当前图片数据的下载进度
receivedSize:已经下载完成的数据大小
expectedSize:该文件的数据总大小
第二个block块:当图片下载完成之后执行该block中的代码
image:下载得到的图片数据
error:下载出现的错误信息
SDImageCacheType:图片的缓存策略(不缓存,内存缓存,沙盒缓存)
imageURL:下载的图片的url地址
*/
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"placehoder"] options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {
//计算当前图片的下载进度
NSLog(@"%.2f",1.0 *receivedSize / expectedSize);
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
}];
03 系统级内存警告如何处理(面试)
//取消当前正在进行的所有下载操作
//内部自动实现[[SDWebImageManager sharedManager].imageCache clearDisk]
[[SDWebImageManager sharedManager] cancelAll];
//清除缓存数据(面试)
//cleanDisk:删除过期的文件数据,计算当前未过期的已经下载的文件数据的大小,如果发现该数据大小大于我们设置的最大缓存数据大小,那么程序内部会按照按文件数据缓存的时间从远到近删除,直到小于最大缓存数据为止。kDefaultCacheMaxCacheAge:默认缓存周期是一周
//clearMemory:直接删除文件,重新创建新的文件夹
//[[SDWebImageManager sharedManager].imageCache cleanDisk];
[[SDWebImageManager sharedManager].imageCache clearMemory];
04 SDWebImage默认的缓存时间是1周
05 如何播放gif图片
/*
5-1 把用户传入的gif图片->NSData
5-2 根据该Data创建一个图片数据源(NSData->CFImageSourceRef)
5-3 计算该数据源中一共有多少帧,把每一帧数据取出来放到图片数组中
5-4 根据得到的数组+计算的动画时间-》可动画的image
[UIImage animatedImageWithImages:images duration:duration];
*/
06 如何判断当前图片类型
+ (NSString *)sd_contentTypeForImageData:(NSData *)data;
(2)SDWebImage内部结构(面试)
1.请说明SDWebImage内部是如何进行缓存处理的?(缓存处理机制&缓存类&缓存时候键值对如何设置)答:
1)缓存机制:内部使用了内存缓存和磁盘缓存二级缓存机制,默认情况下在存储数据的时候,会先对图片进行内存缓存,然后做磁盘缓存。在读取图片数据的时候先检查内存缓存,如果不存在则再检查磁盘缓存。
2)缓存类:内部使用NSCache专门处理内存缓存。该类的使用方法和可变字典类似,是线程安全的,且具备自动回收清理的功能。
3)缓存时候键值对如何设置:把图片的URL作为KEY保存,把图片(UIimage)作为Value来保存。
SDWebImage底层基本原理:
- 实现功能及思路:
0. cell多图片展示:通过NSData网络下载小图片
1. 重复下载问题:设置任务缓存,每次下载前先判断
2. 内存暴涨问题:图片二级缓存机制。
3. 卡住主线程:在子线程中下载,下载完毕后回主线程刷新UI
4. 图片显示错误:通过reloadRowsAtIndexPaths定点刷新UI
-
实现步骤:
-
实现细节:
1.图片加载流程: 1. 图片存在判断:先加载image,通过image是否为空判断,不为空,返回图片;不为空,通过另外方式加载,继续判断;如果通过路径是否存在,数组、字典包含元素等方式判断比较麻烦 2. 加载顺序:图片缓存(一级缓存)加载--磁盘缓存(二级缓存)加载--先用占位图片显示,新开队列及任务下载图片 2.缓存处理: 1. 图片缓存外的图片在获取时都要写入图片缓存,在主线程中立即写入。即从沙盒中找到图片还是下载完图片后都要写入图片缓存中(一级缓存) 2. 磁盘缓存外的图片,在下载完后要写入,由于写入操作耗时,可以在子线程中执行。(二级缓存) 3. 图片缓存和磁盘缓存建议使用字典方式保存,Key值可以用图片的后缀名保存;保存前要对value值进行非空判断 4. 磁盘缓存地址:为了方便下次使用,最好将数据写入沙盒中,方便以后直接使用。documents下面的文件会被备份,另外苹果官方严禁将下载的图片放到documents,弃之。library--perference保存偏好设置的,弃之;tmp中的文件会被随即删除,弃之。最终方案是放在library--cache中,不会备份,定期可删除 5. 为避免重复下载,可设置任务缓存,每次创建新任务前先判断是否已经存在任务,若存在则等待;图片下载后(无论成功与失败)都应该清空任务缓存 3.图片下载: 1. 在下载图片前,主线程先用占位图片显示cell.imageView.image. 2. 下载任务可以封装成一个方法来异步执行 3. 先根据app.icon从任务缓存中加载任务,判断任务是否已经在operations中,若是,则等待下载完毕;否则再创建新的任务 4. 小文件的下载直接通过NSData下载最好,使用NSURLSessionDownLoadTask-block还是会有点麻烦 5. 网络请求非空判断:图片下载完毕后,在写入图片缓存前,需要进行非空判断,这是因为字典保存的value不能为空,所以当下载的图片为空时,要先移除操作缓存,并返回。移除操作缓存是因为不移除,下次就不会重新加载) 6. ,最终下载完毕后需要实现:1.回到主线程刷新UI,并写入图片缓存;2.清除下载任务缓存;3.将图片写入到沙盒缓存 7. 下载图片耗时,应该新开子线程来下载图片;可以通过NSOperationQueue来下载任务(懒加载非主队列),并设置最大并发数优化性能 4. 主线程刷新UI 1. 下载完毕后要回到主线程刷新UI。 2. 由于cell的循环利用,所以刷新要通过reloadRowsAtIndexPaths 5. 内存警告处理: 1.将数据保存到字典中时,可能会收到内存警告,这时要情况所有内存图片和操作缓存,并停止队列,使程序得以保存)
基本代码
###在vc.m中
@interface ViewController ()
/** tableView的数据源 */
@property (nonatomic ,strong)NSArray *apps;
/** 图片缓存*/
@property (nonatomic ,strong ,nonnull)NSMutableDictionary *images;
/** 任务缓存 */
@property (nonatomic ,strong ,nonnull)NSMutableDictionary *operations;
/** 队列 */
@property (nonatomic ,strong)NSOperationQueue *queue;
@end
@implementation ViewController
#pragma mark - lazy loading
-(NSArray *)apps
{
if (_apps == nil) {
//1.加载本地的plist文件
NSArray *appplistArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"apps.plist" ofType:nil]];
//2.字典转模型 appplistArray(字典数组)---->模型数组
NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:appplistArray.count];
for (NSDictionary *dict in appplistArray) {
[arrayM addObject:[FZQApp appWithDict:dict]];
}
_apps = arrayM;
}
return _apps;
}
-(NSMutableDictionary *)images
{
if (_images == nil) {
_images = [NSMutableDictionary dictionary];
}
return _images;
}
-(NSMutableDictionary *)operations
{
if (_operations == nil) {
_operations = [NSMutableDictionary dictionary];
}
return _operations;
}
-(NSOperationQueue *)queue
{
if (_queue == nil) {
//创建队列
_queue = [[NSOperationQueue alloc]init];
//设置最大并发数
_queue.maxConcurrentOperationCount = 5;
}
return _queue;
}
#pragma mark ---------------------------
#pragma mark UITbaleViewDataSource
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.apps.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"app";
//1.创建cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
//2.设置cell的数据
//2.1 取出该cell对应的数据
FZQApp *app = self.apps[indexPath.row];
//2.2 设置
cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;
cell.imageView.image = [self setUpImage:self.apps[indexPath.row] indexPath:indexPath];
//3.返回cell
return cell;
}
#pragma mark - Life Cycle
//收到内存警告时清除缓存和队列任务
-(void)didReceiveMemoryWarning
{
self.images = nil;
self.operations = nil;
[self.queue cancelAllOperations];
}
@end
#pragma mark - Methods
/** 加载并设置图片 */
-(UIImage *)setUpImage:(FZQApp *)app indexPath:(NSIndexPath *)indexPath
{
/******** 缓存中查找图片 ********/
//从缓存中获取图片
UIImage *image = self.images[app.iconUrl];
//判断图片是否已经存在,存在则直接显示
if (image) return image;
/******** 磁盘缓存中查找图片 ********/
//获取cache路径
NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
//拼接图片磁盘缓存路径
NSString *imgsPath = [cachePath stringByAppendingPathComponent:[app.iconUrl lastPathComponent]];
//从磁盘中获取图片
image = [UIImage imageWithContentsOfFile:imgsPath];
//磁盘缓存中是否存在图片
if(image){
//存在写入到内存缓存中,并返回图片
[self.images setObject:image forKey:app.iconUrl];
return image;
}
/******** 网络上加载图片 ********/
//若不存在则到网上去下载图片,先用占位图片显示
image = IMAGE(@"Snip20151102_160.png");
//生成下载图片任务
[self downLoadOperation:app indexPath:indexPath imagesPath:imgsPath];
return image;
}
/* 设置下载任务 */
- (void)downLoadOperation:(FZQApp *)app indexPath:(NSIndexPath *)indexPath imagesPath:(NSString *)imagesPath
{
//设置下载任务
NSBlockOperation *downLoadOpe = self.operations[app.iconUrl];
//判断下载任务是否存在
if(downLoadOpe == nil){
//不存在则新建下载任务,并记录下载任务
downLoadOpe = [NSBlockOperation blockOperationWithBlock:^{
//加载数据
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:app.iconUrl]];
//下载图片
UIImage *image = [UIImage imageWithData:data];
//判断下载图片是否为空,若是需要清除下载任务并返回
if (!image) {
//清除任务
[self.operations removeObjectForKey:app.iconUrl];
//返回
return ;
}
//回到主线程刷新UI
[self refreshView:indexPath image:image];
//写入内存缓存
[self.images setObject:image forKey:app.iconUrl];
//写入到磁盘缓存中
[data writeToFile:imagesPath atomically:YES];
//清除下载任务
[self.operations removeObjectForKey:app.iconUrl];
}];
//添加任务
[self.queue addOperation:downLoadOpe];
// 记录下载任务
[self.operations setObject:downLoadOpe forKey:app.iconUrl];
}
}
/* 刷新UI */
- (void)refreshView:(NSIndexPath *)indexPath image:(UIImage *)image
{
//下载完毕后回到主线程刷新数据
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
//设置cell图片
[self.tableView cellForRowAtIndexPath:indexPath].imageView.image = image;
//刷新数据
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}];
}
###在模型.h中
@interface FZQApp : NSObject
/** 名称 */
@property (nonatomic ,strong)NSString *name;
/** 图片的url */
@property (nonatomic ,strong)NSString *icon;
/** 下载数量 */
@property (nonatomic ,strong)NSString *download;
+(instancetype)appWithDict:(NSDictionary *)dict;
@end
###在模型.m中
@implementation FZQApp
+(instancetype)appWithDict:(NSDictionary *)dict
{
FZQApp *app = [[FZQApp alloc]init];
//KVC
[app setValuesForKeysWithDictionary:dict];
return app;
}
@end