注意:沙盒缓存应在ZYXDownloadOperation中完成封装不应该在代理中完成,后续完善
ZYXApp.h
#import <Foundation/Foundation.h>
@interface ZYXApp : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *download;
@property (nonatomic, copy) NSString *icon;
+ (instancetype)appWithDict:(NSDictionary *)dict;
@end
ZYXApp.m
#import "ZYXApp.h"
@implementation ZYXApp
+ (instancetype)appWithDict:(NSDictionary *)dict
{
ZYXApp *app = [[self alloc] init];
[app setValuesForKeysWithDictionary:dict]; // KVC
return app;
}
@end
ZYXDownloadOperation.h
#import <Foundation/Foundation.h>
@class ZYXDownloadOperation;
@protocol ZYXDownloadOperationDelegate <NSObject>
@optional
- (void)downloadOperation:(ZYXDownloadOperation *)operation didFinishDownloadWithImage:(UIImage *)image;
@end
@interface ZYXDownloadOperation : NSOperation
@property (nonatomic, copy) NSString *urlString;
@property (nonatomic, strong) NSIndexPath *indexPath;
@property (nonatomic, weak) id<ZYXDownloadOperationDelegate> delegate; // 代理对象属性使用weak
@end
ZYXDownloadOperation.m
#import "ZYXDownloadOperation.h"
@implementation ZYXDownloadOperation
/**
* 自定义NSOperation的步骤很简单
* 重写 - (void)main 方法,在里面实现想执行的任务
*/
- (void)main{
#warning - 自己创建自动释放池(因为如果是异步执行,无法访问主线程的自动释放池)
@autoreleasepool{
NSURL *downloadUrl = [NSURL URLWithString:self.urlString];
NSData *data = [NSData dataWithContentsOfURL:downloadUrl]; // 这行会比较耗时
UIImage *image = [UIImage imageWithData:data];
if ([self.delegate respondsToSelector:@selector(downloadOperation:didFinishDownloadWithImage:)]){
// 线程间通信,NSOperation和GCD的混合使用,子线程获取数据->主线程使用子线程获取的数据
dispatch_async(dispatch_get_main_queue(), ^{ // 回到主线程, 传递图片数据给代理对象
[self.delegate downloadOperation:self didFinishDownloadWithImage:image];
});
}
}
}
@end
ViewController.m
#import "ViewController.h"
#import "ZYXApp.h"
#import "ZYXDownloadOperation.h"
@interface ViewController () <ZYXDownloadOperationDelegate>
@property (nonatomic, strong) NSArray *apps;
@property (nonatomic, strong) NSOperationQueue *queue;
/** key:url value:operation对象 */
@property (nonatomic, strong) NSMutableDictionary *operations;
/** key:url value:image对象*/
@property (nonatomic, strong) NSMutableDictionary *images;
@end
@implementation ViewController
#pragma mark - 懒加载
- (NSArray *)apps
{
if (!_apps) {
NSArray *dictArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
NSMutableArray *appArray = [NSMutableArray array];
for (NSDictionary *dict in dictArray) {
ZYXApp *app = [ZYXApp appWithDict:dict];
[appArray addObject:app];
}
_apps = appArray;
}
return _apps;
}
- (NSOperationQueue *)queue
{
if (!_queue) {
_queue = [[NSOperationQueue alloc] init];
// 最大并发数 == 3
_queue.maxConcurrentOperationCount = 3;
}
return _queue;
}
- (NSMutableDictionary *)operations
{
if (!_operations) {
_operations = [NSMutableDictionary dictionary];
}
return _operations;
}
- (NSMutableDictionary *)images
{
if (!_images) {
_images = [NSMutableDictionary dictionary];
}
return _images;
}
#pragma mark - download task
#pragma mark - UITableViewDatasource 数据源方法
- (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";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:ID];
}
ZYXApp *app = self.apps[indexPath.row];
cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;
// 显示图片
// 保证一个url对应一个ZYXDownloadOperation
// 保证一个url对应UIImage对象
UIImage *image = self.images[app.icon];
if (image) { // 缓存中有图片
cell.imageView.image = image;
}
else { // 缓存中没有图片, 看沙盒缓存是否有
// 获得Library/Caches文件夹
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
// 获得文件名
NSString *filename = [app.icon lastPathComponent];
// 计算出文件的全路径
NSString *file = [cachesPath stringByAppendingPathComponent:filename];
// 加载沙盒的文件数据
NSData *data = [NSData dataWithContentsOfFile:file];
if (data) { // 直接利用沙盒中图片
UIImage *image = [UIImage imageWithData:data];
cell.imageView.image = image;
// 存到字典中
self.images[app.icon] = image;
}
else { // 下载图片
cell.imageView.image = [UIImage imageNamed:@"57437179_42489b0"];
ZYXDownloadOperation *operation = self.operations[app.icon];
if (operation) { // 正在下载
// ... 暂时不需要做其他事
} else { // 没有正在下载
// 创建操作
operation = [[ZYXDownloadOperation alloc] init];
operation.urlString = app.icon;
operation.delegate = self;
operation.indexPath = indexPath;
[self.queue addOperation:operation]; // 异步下载
self.operations[app.icon] = operation;
}
}
}
// SDWebImage : 专门用来下载图片
return cell;
}
#pragma mark - ZYXDownloadOperationDelegate
- (void)downloadOperation:(ZYXDownloadOperation *)operation didFinishDownloadWithImage:(UIImage *)image{
// 数据加载失败
if (image == nil) {
[self.operations removeObjectForKey:operation.urlString];
return;
}
// 1.移除执行完毕的操作
[self.operations removeObjectForKey:operation.urlString];
if (image) {
// 2.将图片放到缓存中(images)
self.images[operation.urlString] = image;
// 3.刷新表格
[self.tableView reloadRowsAtIndexPaths:@[operation.indexPath]
withRowAnimation:UITableViewRowAnimationNone];
// 4.将图片写入沙盒
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
NSIndexPath *indexPath = operation.indexPath;
ZYXApp *app = self.apps[indexPath.row];
NSString *filename = [app.icon lastPathComponent];
NSString *file = [cachesPath stringByAppendingPathComponent:filename];
NSData *data = UIImagePNGRepresentation(image);
[data writeToFile:file atomically:YES];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
// 开始拖拽
[self.queue setSuspended:YES];
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset{
// 开始队列
[self.queue setSuspended:NO];
}
@end