多图下载-SDWebImage的使用和原理

多图下载
利用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;

【多图下载-SDWebImage的使用和原理】(2)SDWebImage内部结构(面试)

多图下载-SDWebImage的使用和原理
文章图片
1.png 1.请说明SDWebImage内部是如何进行缓存处理的?(缓存处理机制&缓存类&缓存时候键值对如何设置)答:
1)缓存机制:内部使用了内存缓存和磁盘缓存二级缓存机制,默认情况下在存储数据的时候,会先对图片进行内存缓存,然后做磁盘缓存。在读取图片数据的时候先检查内存缓存,如果不存在则再检查磁盘缓存。
2)缓存类:内部使用NSCache专门处理内存缓存。该类的使用方法和可变字典类似,是线程安全的,且具备自动回收清理的功能。
3)缓存时候键值对如何设置:把图片的URL作为KEY保存,把图片(UIimage)作为Value来保存。
SDWebImage底层基本原理:
  1. 实现功能及思路:
    0. cell多图片展示:通过NSData网络下载小图片
    1. 重复下载问题:设置任务缓存,每次下载前先判断
    2. 内存暴涨问题:图片二级缓存机制。
    3. 卡住主线程:在子线程中下载,下载完毕后回主线程刷新UI
    4. 图片显示错误:通过reloadRowsAtIndexPaths定点刷新UI
  1. 实现步骤:

    多图下载-SDWebImage的使用和原理
    文章图片
    自定义NSOperation下载图片思路 – 有沙盒缓存.png
  • 实现细节:
    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的循环利用,所以刷新要通过reloadRowsAtIndexPaths5. 内存警告处理: 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

    推荐阅读