您的位置:首页 > 产品设计 > UI/UE

NSOperation之为UItabView制作图片缓存——在didReceiveMemoryWarning方法中做图片缓存的清理操作

2015-10-23 20:54 585 查看
将图片保存到Model模型中的优缺点如下:

优点:不用重复下载,利用MVC刷新表格,不会造成数据混乱,加载速度比较快。

缺点:内存问题,所有下载好的图像都会保存在模型内。如果数据比较多假设为2000个就会造成内存警告。

又因为图像与模型的耦合性太强。导致清理内存非常困难。

为了解决内存问题,需要为UITabView制作图片缓存。制作图片缓存的步骤如下:

(1)新建一个全局的可变字典。键用来保存图片下载地址,对应的值为对应的图片UIImage对象。

(2)给Cell单元格设置图片时先根据模型内的链接名称从图片缓存中进行寻找,有对应的图片,则直接设置。没有的话则进行(3)

(3)取出当前Cell所对应的Model,先判断操作缓存池是否已经存在下载对应图片的操作,如果有的话直接返回。如果没有的话,则添加任务。必须注意的是:图片缓存是一个可变字典,只要是字典,每一个键对应的值都不可以为空,否则会崩溃。因此在将图片保存到缓存的时候必须加图片是否为空的判断操作。

如何清理缓存?

清理缓存需要在当前控制器的didReceiveMemoryWarning方法中执行清理缓存的操作,清理缓存需要执行以下几步 。

一、清理图片缓存。[self.imageCache removeAllObjects];

二、清理操作缓存。 [self.operationCacheremoveAllObjects];

三、取消下载队列内的任务。[self.opQueuecancelAllOperations];

新建工程,代码复制上一个工程进行修改:

CZApp.h

//
//  CZApp.h
//  NSoperation之网络图片下载
//
//  Created by apple on 15/10/23.
//  Copyright (c) 2015年 LiuXun. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface CZApp : NSObject
@property(nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *icon;
@property (nonatomic, strong) NSString *download;

/**
保存网络下载的图像
*/
// @property(nonatomic, strong) UIImage *image;

+(instancetype) appWithDict:(NSDictionary *) dict;
@end
CZApp.m

//
//  CZApp.m
//  NSoperation之网络图片下载
//
//  Created by apple on 15/10/23.
//  Copyright (c) 2015年 LiuXun. All rights reserved.
//

#import "CZApp.h"

@implementation CZApp
+(instancetype) appWithDict:(NSDictionary *) dict
{
CZApp *app = [[self alloc] init];
[app setValuesForKeysWithDictionary:dict];
return app;
}
@end
控制器代码如下:

//
//  ViewController.m
//  NSoperation之网络图片下载
//
//  Created by apple on 15/10/23.
//  Copyright (c) 2015年 LiuXun. All rights reserved.
//

#import "ViewController.h"
#import "CZApp.h"
@interface ViewController ()
// plist文件数据的容器
@property (nonatomic, strong) NSArray *appList;

// 管理下载的全局队列
@property (nonatomic, strong) NSOperationQueue *opQueue;

/**所有下载的缓冲池*/
@property (nonatomic, strong) NSMutableDictionary *operationCache;

/**保存所有图像的缓存*/
@property (nonatomic, strong) NSMutableDictionary *imageCache;
@end

@implementation ViewController

// 懒加载
-(NSArray *)appList
{
if (_appList == nil) {
NSArray *dicArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
// 字典转模型
NSMutableArray *arryM = [NSMutableArray array];
for(NSDictionary *dict in dicArray){
CZApp *app = [CZApp appWithDict:dict];
[arryM addObject:app];
}
_appList = arryM;
}
return _appList;
}

-(NSOperationQueue *)opQueue
{
if (_opQueue == nil) {
_opQueue = [[NSOperationQueue alloc] init];
}
return _opQueue;
}

-(NSMutableDictionary *)operationCache
{
if (_operationCache == nil) {
_operationCache = [[NSMutableDictionary alloc] init];
}
return _operationCache;
}

-(NSMutableDictionary *)imageCache
{
if (_imageCache == nil) {
_imageCache = [[NSMutableDictionary alloc] init];
}
return _imageCache;
}

#pragma mark - 实现数据源方法
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.appList.count;
}

/**
问题1:如果网速比较慢,会很卡
解决方法:使用异步下载

问题2:图片没有Frame,所有cell初始化的时候,给imageView的frame是0。异步下载完之后不显示  解决办法:使用占位图(如果展位图比较大, 自定义cell可以解决)

问题3:如果图片下载速度不一致,同时用户快速滚动的时候,会因为Cell的重用导致图片混乱
解决方法:MVC,使用Model(模型)保存下载的图像,再次刷新表格。

问题4:在用户快速滚动的时候,会重复添加下载任务到下载队列。
解决方法:建立下载操作的缓冲池。首先检查缓冲池里是否有当前图片的下载操作。有的话就不创建下载操作。从而保证一张图片只添加一个下载操作。其实就是建立一个全局的字典属性。

问题5: 将图片保存到模型里的优缺点
优点:不用重复下载,利用MVC刷新表格,不会造成数据混乱,加载速度比较快
缺点:内存,所有下载好的图像都会记录在模型里。如果数据比较多(2000)个就会造成内存警告。

图像根模型耦合性太强。导致清理内存非常困难
*/
/**
代码重构:1.如果代码太长。
目的:
- 写的时候,如果思路清楚,能够一次性写完,但是也要注意同构。
- 时间长了,不好阅读
- 重构代码,便于维护

重构方法:
如果有一部分代码专门解决某一问题,就封装起来。
1. 新建一个方法—> 剪切代码。
2. 传参数。
3. 在原来剪切代码的地方,调用抽取的方法。
4. 注意,测试。
5. 注意if嵌套,在实际的开发,非常忌讳很深的嵌套。
*/
// cell里面的imageView子控件是懒加载
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"AppCell";
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 给Cell设置数据
CZApp *app = self.appList[indexPath.row];
cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;

// 判断模型里面是否有图像
if ([self.imageCache objectForKey:app.icon]) {
NSLog(@" 图片已经下载......");
cell.imageView.image = self.imageCache[app.icon];
}else{ // 如果模型内没有图片就异步下载

// 显示图片—占位图
cell.imageView.image = [UIImage imageNamed:@"user_default"];
#warning 从这里开始剪切的代码
// 下载图片
[self downloadImage:indexPath];
}
return cell;
}

-(void)downloadImage:(NSIndexPath *)indexPath
{
CZApp *app = self.appList[indexPath.row];
/**
如果下载缓冲池里面有当前图片的下载操作,就不用创建下载操作,没有才创建
缓冲池字典中 key:存放当前图片的url,字符串类型。
Value:保存下载操作
*/
if (self.operationCache[app.icon]) {
NSLog(@"正在玩命下载中......");
return;
}
// 缓冲池没有下载操作

// 异步下载图片
NSBlockOperation  *downLoadOp = [NSBlockOperation blockOperationWithBlock:^{
// 模拟延时
[NSThread sleepForTimeInterval:2];
NSLog(@"正在下载中......");

//  1. 下载图片(二进制数据)
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]];
UIImage *image = [UIImage imageWithData:data];

//  2. 将下载的数据保存到模型
// 字典的赋值不能为nil,赋值为nil会崩溃
if (image) {
[self.imageCache setObject:image forKey:app.icon];
}

//  3. 在主线程更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//            cell.imageView.image = [UIImage imageWithData:data];
// 因为Cell的图片是懒加载的方式添加的。只设置了Image但是没有设置Frame,只有调用layoutSubView()方法才会给Cell子控件重新布局
// layoutSubView是UIView的方法
// 注意:点击Cell也会触发layoutSubView()方法
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
/** reload 会重新调用cell的初始化方法, 会重新判断模型里面是否有图像
有的话就直接显示
*/
}];
}];

// 将操作添加到队列
[self.opQueue addOperation:downLoadOp];
NSLog(@"操作的数量------------->%ld", self.opQueue.operationCount);

// 将操作添加到缓冲池中(使用图片的url作为key)
[self.operationCache setObject:downLoadOp forKey:app.icon];
}

/**
在真实开发中,一定要注意这个方法
*/
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];

// 需要在这里做一些内存清理的工作,如果不处理会被系统强制闪退
// 清理内存
[self.imageCache  removeAllObjects];

// 清理操作的缓存
[self.operationCache removeAllObjects];

// 取消下载队列内的任务
[self.opQueue cancelAllOperations];
}
@end
等图片下载完成后再模拟一次内存警告:操作如下



运行结果如下:



模拟内存警告后,瞬间截图如下:



上拉发现图片不在了,说明清空图片缓存
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: