iOS开发多线程篇—自定义NSOperation
一、实现一个简单的tableView显示效果
实现效果展示:
代码示例(使用以前在主控制器中进行业务处理的方式)
1.新建一个项目,让控制器继承自UITableViewController。
// // YYViewController.h // 01-自定义Operation // // Created by apple on 14-6-26. // Copyright (c) 2014年 itcase. All rights reserved. // #import <UIKit/UIKit.h> @interface YYViewController : UITableViewController @end
2.处理storyboard中得界面,如下:
3.根据plist文件,字典转模型
新建一个类,继承自NSObject,作为数据的模型
YYappModel.h文件
// // YYappModel.h // 01-自定义Operation // // Created by apple on 14-6-26. // Copyright (c) 2014年 itcase. All rights reserved. // #import <Foundation/Foundation.h> @interface YYappModel : NSObject /** *应用名称 */ @property(nonatomic,copy)NSString *name; /** * 应用图片 */ @property(nonatomic,copy)NSString *icon; /** * 应用的下载量 */ @property(nonatomic,copy)NSString *download; +(instancetype)appModelWithDict:(NSDictionary *)dict; -(instancetype)initWithDict:(NSDictionary *)dict; @end
YYappModel.m文件
// // YYappModel.m // 01-自定义Operation // // Created by apple on 14-6-26. // Copyright (c) 2014年 itcase. All rights reserved. // #import "YYappModel.h" @implementation YYappModel -(instancetype)initWithDict:(NSDictionary *)dict { if (self=[super init]) { [self setValuesForKeysWithDictionary:dict]; } return self; } //工厂方法 +(instancetype)appModelWithDict:(NSDictionary *)dict { return [[self alloc]initWithDict:dict]; } @end
主控制器中得逻辑控制部分,YYViewController.m文件
// // YYViewController.m // 01-自定义Operation // // Created by apple on 14-6-26. // Copyright (c) 2014年 itcase. All rights reserved. // #import "YYViewController.h" #import "YYappModel.h" @interface YYViewController () @property(nonatomic,strong)NSArray *apps; @end @implementation YYViewController #pragma mark- 懒加载 -(NSArray *)apps { if (_apps==nil) { NSString *path=[[NSBundle mainBundle]pathForResource:@"apps.plist" ofType:nil]; NSArray *tempArray=[NSArray arrayWithContentsOfFile:path]; //字典转模型 NSMutableArray *array=[NSMutableArray array]; for (NSDictionary *dict in tempArray) { YYappModel *app=[YYappModel appModelWithDict:dict]; [array addObject:app]; } _apps=array; } return _apps; } #pragma mark-数据源方法 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.apps.count; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *ID=@"ID"; UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:ID]; if (cell==nil) { cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; } YYappModel *app=self.apps[indexPath.row]; cell.textLabel.text=app.name; cell.detailTextLabel.text=app.download; //下载图片数据 NSLog(@"加载图片数据---%@", [NSThread currentThread]); NSURL *url=[NSURL URLWithString:app.icon]; NSData *data=[NSData dataWithContentsOfURL:url]; UIImage *imgae=[UIImage imageWithData:data]; cell.imageView.image=imgae; NSLog(@"完成显示"); return cell; } @end
打印查看:
二、自定义NSOperation
说明:上面的下载图片数据部分是一个非常耗时的操作,这个操作任务在主线程完成,会严重的影响到用户体验,造成UI卡的现象。下面通过自定义NSOperation,新开线程,让加载图片的任务异步执行。
1.通过代理
在上面的基础上,新建一个类,让其继承自NSOperation。
YYdownLoadOperation.h文件
// // YYdownLoadOperation.h // 01-自定义Operation // // Created by apple on 14-6-26. // Copyright (c) 2014年 itcase. All rights reserved. // #import <Foundation/Foundation.h> #pragma mark-设置代理和代理方法 @class YYdownLoadOperation; @protocol YYdownLoadOperationDelegate <NSObject> -(void)downLoadOperation:(YYdownLoadOperation*)operation didFishedDownLoad:(UIImage *)image; @end @interface YYdownLoadOperation : NSOperation @property(nonatomic,copy)NSString *url; @property(nonatomic,strong)NSIndexPath *indexPath; @property(nonatomic,strong)id <YYdownLoadOperationDelegate> delegate; @end
YYdownLoadOperation.m文件
// // YYdownLoadOperation.m // 01-自定义Operation // // Created by apple on 14-6-26. // Copyright (c) 2014年 itcase. All rights reserved. // #import "YYdownLoadOperation.h" @implementation YYdownLoadOperation -(void)main { NSURL *url=[NSURL URLWithString:self.url]; NSData *data=[NSData dataWithContentsOfURL:url]; UIImage *imgae=[UIImage imageWithData:data]; NSLog(@"--%@--",[NSThread currentThread]); //图片下载完毕后,通知代理 if ([self.delegate respondsToSelector:@selector(downLoadOperation:didFishedDownLoad:)]) { dispatch_async(dispatch_get_main_queue(), ^{//回到主线程,传递数据给代理对象 [self.delegate downLoadOperation:self didFishedDownLoad:imgae]; }); } } @end
主控制器中的业务逻辑:
// // YYViewController.m // 01-自定义Operation // // Created by apple on 14-6-26. // Copyright (c) 2014年 itcase. All rights reserved. // #import "YYViewController.h" #import "YYappModel.h" #import "YYdownLoadOperation.h" @interface YYViewController ()<YYdownLoadOperationDelegate> @property(nonatomic,strong)NSArray *apps; @property(nonatomic,strong)NSOperationQueue *queue; @end @implementation YYViewController #pragma mark- 懒加载apps -(NSArray *)apps { if (_apps==nil) { NSString *path=[[NSBundle mainBundle]pathForResource:@"apps.plist" ofType:nil]; NSArray *tempArray=[NSArray arrayWithContentsOfFile:path]; //字典转模型 NSMutableArray *array=[NSMutableArray array]; for (NSDictionary *dict in tempArray) { YYappModel *app=[YYappModel appModelWithDict:dict]; [array addObject:app]; } _apps=array; } return _apps; } #pragma mark-懒加载queue -(NSOperationQueue *)queue { if (_queue==Nil) { _queue=[[NSOperationQueue alloc]init]; //设置最大并发数为3 _queue.maxConcurrentOperationCount=3; } return _queue; } #pragma mark-数据源方法 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.apps.count; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *ID=@"ID"; UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:ID]; if (cell==nil) { cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; } YYappModel *app=self.apps[indexPath.row]; cell.textLabel.text=app.name; cell.detailTextLabel.text=app.download; //下载图片数据 // NSLog(@"加载图片数据---%@", [NSThread currentThread]); // NSURL *url=[NSURL URLWithString:app.icon]; // NSData *data=[NSData dataWithContentsOfURL:url]; // UIImage *imgae=[UIImage imageWithData:data]; // cell.imageView.image=imgae; //创建一个OPeration对象 YYdownLoadOperation *operation=[[YYdownLoadOperation alloc]init]; operation.url=app.icon; operation.indexPath=indexPath; operation.delegate=self; //把操作对象添加到队列中在去 [self.queue addOperation:operation]; // NSLog(@"完成显示"); return cell; } -(void)downLoadOperation:(YYdownLoadOperation *)operation didFishedDownLoad:(UIImage *)image { //返回图片数据给每行对应的cell的imageview.image //取出tableview中indexPath这一行对应的cell UITableViewCell *cell=[self.tableView cellForRowAtIndexPath:operation.indexPath]; //显示图片 cell.imageView.image=image; // NSLog(@"cell--index--%@---%@",operation.indexPath,[NSThread currentThread]); //一定要刷新表格 [self.tableView reloadData]; NSLog(@"--%@--",[NSThread currentThread]); } @end
说明:通过打印可以发现上面的代码存在很大的问题。
问题1:需要保证一个url对应一个operation对象。
问题2:下载完需要移除。移除执行完毕的操作。
问题3:保证一个url对应一个image。
下面对主控制器中得代码进行改进:
// // YYViewController.m // 01-自定义Operation // // Created by apple on 14-6-26. // Copyright (c) 2014年 itcase. All rights reserved. // #import "YYViewController.h" #import "YYappModel.h" #import "YYdownLoadOperation.h" @interface YYViewController ()<YYdownLoadOperationDelegate> @property(nonatomic,strong)NSArray *apps; @property(nonatomic,strong)NSOperationQueue *queue; @property(nonatomic,strong)NSMutableDictionary *operations; @property(nonatomic,strong)NSMutableDictionary *images; @end @implementation YYViewController #pragma mark- 懒加载apps -(NSArray *)apps { if (_apps==nil) { NSString *path=[[NSBundle mainBundle]pathForResource:@"apps.plist" ofType:nil]; NSArray *tempArray=[NSArray arrayWithContentsOfFile:path]; //字典转模型 NSMutableArray *array=[NSMutableArray array]; for (NSDictionary *dict in tempArray) { YYappModel *app=[YYappModel appModelWithDict:dict]; [array addObject:app]; } _apps=array; } return _apps; } #pragma mark-懒加载queue -(NSOperationQueue *)queue { if (_queue==Nil) { _queue=[[NSOperationQueue alloc]init]; //设置最大并发数为3 _queue.maxConcurrentOperationCount=3; } return _queue; } #pragma mark-懒加载operations -(NSMutableDictionary *)operations { if (_operations==Nil) { _operations=[NSMutableDictionary dictionary]; } return _operations; } #pragma mark-懒加载images -(NSMutableDictionary *)images { if (_images==Nil) { _images=[NSMutableDictionary dictionary]; } return _images; } #pragma mark-数据源方法 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.apps.count; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *ID=@"ID"; UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:ID]; if (cell==nil) { cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; } YYappModel *app=self.apps[indexPath.row]; cell.textLabel.text=app.name; cell.detailTextLabel.text=app.download; //保证一个url对应一个image对象 UIImage *image=self.images[app.icon]; if (image) {//缓存中有图片 cell.imageView.image=image; }else // 缓存中没有图片,得下载 { //先设置一张占位图片 cell.imageView.image=[UIImage imageNamed:@"57437179_42489b0"]; YYdownLoadOperation *operation=self.operations[app.icon]; if (operation) {//正在下载 //什么都不做 }else //当前没有下载,那就创建操作 { operation=[[YYdownLoadOperation alloc]init]; operation.url=app.icon; operation.indexPath=indexPath; operation.delegate=self; [self.queue addOperation:operation];//异步下载 self.operations[app.icon]=operation; } } return cell; } -(void)downLoadOperation:(YYdownLoadOperation *)operation didFishedDownLoad:(UIImage *)image { //1.移除执行完毕的操作 [self.operations removeObjectForKey:operation.url]; //2.将图片放到缓存中 self.images[operation.url]=image; //3.刷新表格(只刷新下载的那一行) [self.tableView reloadRowsAtIndexPaths:@[operation.indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; NSLog(@"--%d--%@--",operation.indexPath.row,[NSThread currentThread]); } @end
打印查看: