多线程22——多图下载2_第二种实现方式(三级缓存)
一、分析第一种方式的不足
iOS学习笔记-122.多线程21——多图下载1_第一种实现方式(不靠谱)
中,我们实现的方式存在严重不足。
使用了主线程去下载图片,使其UI卡顿
图片重复下载。
针对上面的问题我们解决方式如下
针对问题1,我们可以使用多线程来下载
针对问题2,我们可以使用三级缓存
二、三级缓存
2.1 什么是三级缓存
三级缓存就是
网络缓存, 不优先加载, 速度慢,浪费流量
本地缓存, 次优先加载, 速度快
内存缓存, 优先加载, 速度最快
2.2 三级缓存的工作原理
首先从内存中获取我们需要的资源,是否存在。如果存在,那么就是使用资> 源,如果不存在进行步骤2
从本地(磁盘)中获取我们需要的资源, > 是否存在。如果存在,那么就是使用资源,如果不存在进行步骤3
从网络中获取我们的资源来使用。
三、多图下载三级缓存分析
流程如图:
根据url我们生成对一个的图片唯一标示符,作为图片的名字。
从内存中判断我们的图片已经存在了,如果存在直接拿来显示。如果不存在继续步骤3
从本地(磁盘)中判断图片是否已经存在,如果存在直接拿来显示。如果不存在继续步骤4
判断我们的操作缓存中,如果存在那么不处理(子线程下载完成以后,会通知到主线程上来),如果不存在,那么添加下载操作到队列中和操作缓存中。
图片下载好以后,把图片添加到内存和本地缓冲中,把操作从操作缓存中移除。添加实现图片的任务到主线程中。
注意:处理上面,我们应该还要处理内存不足的情况,这个时候,我们移除内存中的图片和操作。
四、代码
//
// ViewController.m
// 03_UIview86多线程_多图下载
//
// Created by 杞文明 on 17/9/6.
// Copyright © 2017年 杞文明. All rights reserved.
//
#import "ViewController.h"
#import "QWMAppItem.h"
#import "NSString+MD5.h"
@interface ViewController ()
/** tableView的数据源 */
@property (nonatomic, strong) NSArray *apps;
/** 内存缓存 */
@property (nonatomic, strong) NSMutableDictionary *images;
/** 队列 */
@property (nonatomic, strong) NSOperationQueue *queue;
/** 操作缓存 */
@property (nonatomic, strong) NSMutableDictionary *operations;
@end
@implementation ViewController
-(NSArray*)apps{
if(_apps==nil){
//字典数据
NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
//字典数据转模型数据
NSMutableArray *arrM = [NSMutableArray array];
for (NSDictionary *dict in array) {
[arrM addObject:[QWMAppItem appWitdDict:dict]];
}
_apps = arrM;
}
return _apps;
}
-(NSMutableDictionary*)images{
if(_images==nil){
_images = [NSMutableDictionary dictionary];
}
return _images;
}
-(NSOperationQueue *)queue{
if(_queue == nil){
_queue = [[NSOperationQueue alloc]init];
//设置最大并发数
_queue.maxConcurrentOperationCount = 5;
}
return _queue;
}
-(NSMutableDictionary *)operations{
if(_operations == nil){
_operations = [NSMutableDictionary dictionary];
}
return _operations;
}
/*组数*/
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
/*每组的行数*/
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.apps.count;
}
/*每个cell*/
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString* identifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
//获取数据
QWMAppItem *appItem = self.apps[indexPath.row];
cell.textLabel.text = appItem.name;
cell.detailTextLabel.text = appItem.download;
[self handle2:cell withItem:appItem withIndex:indexPath];
// [self handle1:cell withItem:appItem];
return cell;
}
/*图片的第二种种处理方式*/
-(UITableViewCell*)handle2:(UITableViewCell*)cell withItem:(QWMAppItem*)appItem withIndex:(NSIndexPath*)indexPath{
//1.首先判断内存中是否已经存在,存在直接取
//2.内存中不存在,判断本地是否存在,存在直接取。
//3.本地不存在,判断当前的图片是否已经添加到任务中,如果是,那么不操作
//4.如果没有添加到任务中,那么我们把下载图片的操作添加到任务中
//获取我们图片地址的MD5值作为唯一标识符 MD5+后缀名
NSString *imageID = [NSString stringWithFormat:@"%@.%@",[appItem.icon MD5_32BitLower],[appItem.icon pathExtension]];
UIImage *image = [self.images objectForKey:imageID];
if(image){
//使用内存缓存
cell.imageView.image = image;
NSLog(@"%zd处的图片使用了内存缓存中的图片",indexPath.row);
}else{
//1.获取沙盒的路径
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// NSLog(@"%@",caches);
//2.拼接我们图片的地址
NSString * imageFullPath = [caches stringByAppendingPathComponent:imageID];
//3.获取本地磁盘中的图片数据
NSData *imageData = [NSData dataWithContentsOfFile:imageFullPath];
//4.判断数据是否已经存在,存在说明我们有本地缓存,那么我们直接拿来显示
if (imageData) {
//使用本地缓存
cell.imageView.image = [UIImage imageWithData:imageData];
NSLog(@"%zd处的图片使用了本地缓存中的图片",indexPath.row);
}else{
//都没有,那么我们就需要去网络下载了
//我们获取先去判断一下,我们的这个图片下载任务是否已经在队列中存在,如果有不操作,没有那么我们添加一个下载任务
NSBlockOperation *download = [self.operations objectForKey:imageID];
if(!download){//没有这个任务,那么我们就添加任务
//为了解决滑动时候数据显示错乱,我们现在添加一张图片,用于做默认显示
cell.imageView.image = [UIImage imageNamed:@"image1"];
//创建现在任务
download = [NSBlockOperation blockOperationWithBlock:^{
//下载
NSURL *url = [NSURL URLWithString:appItem.icon];
NSData *imageData = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:imageData];
//容错处理
if (image == nil) {
//如果这个下载有误,那么我们不操作了,并且从操作缓存中移除,为了下一次还能下载
[self.operations removeObjectForKey:imageID];
return ;
}
//下载好了,现在需要如下操作
//1.数据添加到内存缓存中
//2.数据添加到本地中(本地缓存)
//3.主线程中显示数据
[self.images setObject:image forKey:imageID];//内存缓存
[imageData writeToFile:imageFullPath atomically:YES];//本地缓存
[[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{//主线程显示,就是去刷新这一行
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
}]];
//下载已经好了,那么移除图片下载操作
[self.operations removeObjectForKey:imageID];
}];
//把任务添加到队列中
[self.queue addOperation:download];
//添加任务到操作缓存中,等到下载好了再把它移除
[self.operations setObject:download forKey:imageID];
}
}
}
return cell;
}
/*
图片的第一种处理方式,存在的问题
1.UI很不流程 ------> 开启子线程下载
2.图片重复下载 ----->先把之前已经下载的图片保存起来(字典)
*/
-(UITableViewCell*)handle1:(UITableViewCell*)cell withItem:(QWMAppItem*)appItem{
NSURL *url = [NSURL URLWithString:appItem.icon];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
cell.imageView.image = image;
return cell;
}
/*内存警告*/
-(void)didReceiveMemoryWarning{
//内存不存的时候删除我们的缓存
[self.operations removeAllObjects];
[self.images removeAllObjects];
}
@end