注意:沙盒緩存應在ZYXDownloadOperation中完成封裝不應該在代理中完成,后續(xù)完善
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 方法,在里面實現(xiàn)想執(zhí)行的任務
*/
- (void)main{
#warning - 自己創(chuàng)建自動釋放池(因為如果是異步執(zhí)行,無法訪問主線程的自動釋放池)
@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的混合使用,子線程獲取數(shù)據(jù)->主線程使用子線程獲取的數(shù)據(jù)
dispatch_async(dispatch_get_main_queue(), ^{ // 回到主線程, 傳遞圖片數(shù)據(jù)給代理對象
[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];
// 最大并發(fā)數(shù) == 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 數(shù)據(jù)源方法
- (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];
// 加載沙盒的文件數(shù)據(jù)
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 { // 沒有正在下載
// 創(chuàng)建操作
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{
// 數(shù)據(jù)加載失敗
if (image == nil) {
[self.operations removeObjectForKey:operation.urlString];
return;
}
// 1.移除執(zhí)行完畢的操作
[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

沙盒緩存.png