xcode.png
图片二级缓存机制(简仿SDWebImage内部实现)
本项目实现了多图下载,内部是二级缓存机制,既在显示图片之前会先去内存缓存中找,如果找到了就显示,如果没有就去磁盘中加载二进制数据,如果磁盘中也没有就会去下载。
此外,该项目做了一定的容错处理,具体代码如下,详细内容都加了注释,如果有什么建议,欢迎前来讨论,如果需要源代码可在评论里留下联系方式,看到后会在第一时间发送,好了,不多说了,代码如下:
#import "ViewController.h"
#import "WJCellItem.h"
@interface ViewController ()
//定义数组来存储从plist中加载的数据
@property (strong,nonatomic)NSArray *dataArr;
//在内存中存储图片的容器
@property (strong,nonatomic)NSCache *images;
//存储操作的字典
@property (strong,nonatomic)NSMutableDictionary *operations;
//队列
@property (strong,nonatomic)NSOperationQueue *queue;
@end
@implementation ViewController
//懒加载创建存储数据的数组
-(NSArray *)dataArr{
if (_dataArr==nil) {
NSString *path=[[NSBundle mainBundle]pathForResource:@"apps.plist" ofType:nil];
NSArray *array=[NSArray arrayWithContentsOfFile:path];
NSMutableArray *arrM=[NSMutableArray array];
for (NSDictionary *dict in array) {
//把从数组中取出来的字典包装成模型
WJCellItem *item=[WJCellItem cellItemWithDict:dict];
[arrM addObject:item];
}
_dataArr=arrM;
}
return _dataArr;
}
//懒加载创建字典
-(NSMutableDictionary *)operations{
if (_operations==nil) {
_operations=[NSMutableDictionary dictionary];
}
return _operations;
}
//懒加载创建队列
-(NSOperationQueue *)queue{
if (_queue==nil) {
_queue=[[NSOperationQueue alloc]init];
}
return _queue;
}
//懒加载创建NSCache
-(NSCache *)images{
if (_images==nil) {
_images=[[NSCache alloc]init];
//最多缓存100张图片
_images.countLimit=100;
}
return _images;
}
//整个tableView只有一组
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
//每一组有多少行
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.dataArr.count;
}
//每一行cell的内容
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//设置cell的标示
static NSString *ID=@"cell";
//tableViwe的重用机制,先从缓冲池里找有没有对应标示的cell
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:ID];
//如果在缓存池里找不到再来创建
if (cell==nil) {
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
//取出模型,给cell设置数据
WJCellItem *item=self.dataArr[indexPath.row];
cell.textLabel.text=item.name;
cell.detailTextLabel.text=item.download;
//由于plist文件中给的是图片的url,所以需要我们去下载
//先去内存缓存中去找,如果内存缓存中有就直接取出来设置
UIImage *image=[self.images objectForKey:item.icon];
if (image) {
cell.imageView.image=image;
//如果内存缓存中没有就去沙盒(磁盘)里找,如果沙盒(磁盘)中有,就从沙盒(磁盘)中取出来设置,并且把图片保存到内存中
}else{
//获取沙盒路径
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
//获取图片路径的最后一个节点
NSString *imagePath=[item.icon lastPathComponent];
//拼接路径获取图片的全路径
NSString *fullPath=[caches stringByAppendingPathComponent:imagePath];
//从磁盘中试着取数据
NSData *data=[NSData dataWithContentsOfFile:fullPath];
if (data) {
UIImage *image=[UIImage imageWithData:data];
cell.imageView.image=image;
//如果磁盘中也没有找到图片数据,我们就先去操作字典中查找有没有这个图片的下载操作,如果有就等待下载完毕,如果没有就需要我们手动下载
}else{
//在图片下载完成之前设置一张占位图片,以防止cell重用引发的图片错乱问题
cell.imageView.image=[UIImage imageNamed:@"xcode"];
//试着去存储操作的字典里找当前的下载操作
NSBlockOperation *download=[self.operations objectForKey:item.icon];
//如果在字典中没有找到该操作,就需要我们手动下载了,由于下载图片是耗时操作,因此需要开启子线程下载
if (download==nil) {
//封装操作
NSBlockOperation *download=[NSBlockOperation blockOperationWithBlock:^{
//获取图片的url
NSURL *url=[NSURL URLWithString:item.icon];
//根据图片的url将图片的二进制数据下载到本地
NSData *data=[NSData dataWithContentsOfURL:url];
//根据二进制数据生成一张图片
UIImage *image=[UIImage imageWithData:data];
//如果没有得到图片,直接返回,防止在设置图片的时候程序崩溃
if (image==nil) {
return ;
}
//将图片保存一份到内存中
[self.images setObject:image forKey:item.icon];
//将图片的二进制数据保存一份到磁盘中
[data writeToFile:fullPath atomically:YES];
//在主队列中设置图片
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
//刷新表格
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}];
}];
//将操作添加到队列中
[self.queue addOperation:download];
//将下载操作添加到缓存中一份,防止重复创建同一个操作
[self.operations setObject:download forKey:item.icon];
}else{
//当在存储操作的字典里找到了当前的操作就会来到这个方法
//在这里面不需要做任何操作,只需要等着图片加载完毕后显示即可
}
}
}
//返回当前的cell
return cell;
}
//发生内存警告时的处理
-(void)didReceiveMemoryWarning{
//清除图片缓存
[self.images removeAllObjects];
//取消所有的任务
[self.queue cancelAllOperations];
}
@end
模型如下:
#import <Foundation/Foundation.h>
@interface WJCellItem : NSObject
@property (nonatomic,copy)NSString *name;
@property (nonatomic,copy)NSString *icon;
@property (nonatomic,copy)NSString *download;
+(instancetype)cellItemWithDict:(NSDictionary*)dict;
@end
#import "WJCellItem.h"
@implementation WJCellItem
+(instancetype)cellItemWithDict:(NSDictionary *)dict{
WJCellItem *item=[[self alloc]init];
[item setValuesForKeysWithDictionary:dict];
return item;
}
@end
NSCache缓存类拓展:
- 在此项目中用到了NSCache缓存类,其实它的用法跟NSMutableDictionary类似,在AFN和SDWebImage框架中被使用来管理缓存。
- 苹果官方解释NSCache在系统内存很低时,会自动释放对象,建议:接收到内存警告时主动调用removeAllObject方法释放对象
- NSCache是线程安全的,在多线程操作中,不需要对NSCache加锁
- NSCache只是对Key进行Strong引用,不是拷贝