上一篇文章的下载图片操作都放在了block中,当遇到复杂的操作,一堆的代码放在block中 ,很明显这不是明智的选择,代码显得很臃肿。 因此,把线程操作放到自定义NSOperation中。
自定义NSOperation的步骤:继承NSOperation、重写- (void)main方法,在里面实现想执行的任务。
重写- (void)main方法的注意点:
1、自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)。
2、经常通过- (BOOL)isCancelled方法检测操作是否被取消,对取消做出响应。
案例代码:
1、新建DownloadOperation继承NSOperation,下载操作放到main方法中。 至于下载后回到主线程要做点什么,它自己本身不知道(不是它的工作范畴)。只负责下载完成后通知其它人,因此委托代理,委托别人去做事。
DownloadOperation.h
~~~
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class DownloadOperation;
//协议
@protocol DownloadOperationDelegate <NSObject>
@optional
- (void)downloadOperation:(DownloadOperation *)operation didFinishDownload:(UIImage *)image;
@end
@interface DownloadOperation : NSOperation
//下载图片地址
@property(nonatomic,copy) NSString *imageUrl;
//表格cell位置
@property(nonatomic,strong) NSIndexPath *indexPath;
//代理
@property(nonatomic,weak) id<DownloadOperationDelegate> delegate;
@end
~~~
DownloadOperation.m
注意:@autoreleasepool 自动释放池 、isCancelled方法检测操作是否被取消,对取消做出响应。
~~~
#import "DownloadOperation.h"
#import <UIKit/UIKit.h>
@implementation DownloadOperation
-(void)main{
@autoreleasepool {//管理内存
if (self.isCancelled) return; //暂停为执行的操作
NSURL *url = [NSURL URLWithString:self.imageUrl];
NSData *data = [NSData dataWithContentsOfURL:url]; // 下载
UIImage *image = [UIImage imageWithData:data]; // NSData -> UIImage
if(self.isCancelled) return;//暂停正在执行的操作
// 回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if ([self.delegate respondsToSelector:@selector(downloadOperation:didFinishDownload:)]) {
[self.delegate downloadOperation:self didFinishDownload:image];
}
}];
}
}
@end
~~~
2、AppsTableViewController.m
~~~
//
// AppsTableViewController.m
// cell图片下载(自定义operation)
#import "AppsTableViewController.h"
#import "App.h"
#import "DownloadOperation.h"
@interface AppsTableViewController ()<DownloadOperationDelegate>
//应用信息集合
@property(nonatomic,strong) NSMutableArray *apps;
//存放所有下载图片的队列
@property(nonatomic,strong) NSOperationQueue *queue;
//存放所有的下载操作(url是key,operation对象是value)
@property(nonatomic,strong) NSMutableDictionary *operations;
//存放所有下载完的图片
@property(nonatomic,strong) NSMutableDictionary *images;
@end
@implementation AppsTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/**
* 懒加载
**/
-(NSMutableArray *)apps{
if (!_apps) {
NSMutableArray *appArr = [NSMutableArray array];
NSString *file =[[NSBundle mainBundle] pathForResource:@"apps" ofType:@"plist"];
NSArray *dictArr = [NSArray arrayWithContentsOfFile:file];
for (NSDictionary *dict in dictArr) {
App *app = [App appWithDict:dict];
[appArr addObject:app];
}
_apps = appArr;
}
return _apps;
}
-(NSOperationQueue *)queue{
if (!_queue) {
_queue = [[NSOperationQueue alloc]init];
}
return _queue;
}
-(NSMutableDictionary *)operations{
if (!_operations) {
_operations = [[NSMutableDictionary alloc]init];
}
return _operations;
}
-(NSMutableDictionary *)images{
if (!_images) {
_images = [[NSMutableDictionary alloc]init];
}
return _images;
}
#pragma mark - Table view data source
- (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:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
App *app = self.apps[indexPath.row];
cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;
// 先从images缓存中取出图片url对应的UIImage
UIImage *image = self.images[app.icon];
if (image) {// 说明图片已经下载成功过(成功缓存)
cell.imageView.image = image;
}else{
// 获得caches的路径, 拼接文件路径
NSString *file = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:[app.icon lastPathComponent]];
// 先从沙盒中取出图片
NSData *data = [NSData dataWithContentsOfFile:file];
if (data) {//沙盒中存在图片
cell.imageView.image = [UIImage imageWithData:data];
}else{//沙盒不存,进行下载操作
//显示占位图片
cell.imageView.image = [UIImage imageNamed:@"placeholder"];
// 下载图片
[self download:app.icon indexPath:indexPath];
}
}
return cell;
}
-(void)download:(NSString *)imageUrl indexPath:(NSIndexPath *)indexPath{
//取出当前图片url对应下的下载操作(operations对象)
DownloadOperation *operation = self.operations[imageUrl];
if (operation) return; //如果存在操作就不往下执行(因为可能该图片下载操作正在进行)
//创建操作,下载图片
operation = [[DownloadOperation alloc]init];
operation.imageUrl = imageUrl;
operation.indexPath = indexPath;
//设置代理
operation.delegate = self;
// 添加操作到队列中
[self.queue addOperation:operation];
// 添加到字典中 (这句代码为了解决重复下载)
self.operations[imageUrl] = operation;
}
/**
* 当用户开始拖拽表格时调用
*/
-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
//暂停下载
[self.queue setSuspended:YES];
}
/**
* 当用户停止拖拽表格时调用
*/
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
//开始下载
[self.queue setSuspended:NO];
}
#pragma mark - 下载操作的代理方法
-(void)downloadOperation:(DownloadOperation *)operation didFinishDownload:(UIImage *)image{
// 存放图片到字典中
if (image) {
//存放所有的下载操作
self.operations[operation.imageUrl] = image;
//将图片存入沙盒中
NSData *data = UIImagePNGRepresentation(image);
NSString *file = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)lastObject]stringByAppendingPathComponent:[operation.imageUrl lastPathComponent]];
[data writeToFile:file atomically:YES];
}
// 从字典中移除下载操作 (防止operations越来越大,保证下载失败后,能重新下载)
[self.operations removeObjectForKey:operation.imageUrl];
// 刷新表格
[self.tableView reloadRowsAtIndexPaths:@[operation.indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
@end
~~~
- 前言
- iOS开发实践之SQLite3
- iOS开发实践之FMDB
- Obj-C与javascript交互之WebViewJavascriptBridge
- iOS开发实践之UIWebView
- iOS开发实践之多线程(基本概念)
- iOS开发实践之多线程(NSThread)
- iOS开发实践之多线程(GCD)
- iOS开发实践之多线程(单例模式)
- iOS开发实践之xib加载注意问题
- iOS开发实践之多线程(NSOperation)
- iOS开发实践之cell下载图片(NSOperation)
- iOS开发实践之cell下载图片(自定义NSOperation)
- iOS开发实践之cell下载图片(SDWebImage)
- iOS开发实践之JSON
- iOS开发实践之XML
- iOS开发实践之GET和POST请求
- iOS开发实践之网络检测Reachability
- iOS开发实践之MD5加密