多线程- NSOperation
---恢复内容开始---
lNSOperation和NSOperationQueue实现多线程的具体步骤
先将需要执行的操作封装到一个NSOperation对象中
然后将NSOperation对象添加到NSOperationQueue中
系统会自动将NSOperationQueue中的NSOperation取出来
将取出的NSOperation封装的操作放到一条新线程中执行
注意:NSOperation是一个抽象的类,通过它的三个子类来封装任务 NSIvocationOperation NSBlockOperation 自定义NSOperation的方式
@implementation ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"-----"); [self customOperation]; } -(void)invocationOperation { //1.封装操作 /* 第一个参数:目标对象 self 第二个参数:调用方法 第三个参数:调用方法需要传递的参数 */ 通过NSIvocationOPeration封装操作 NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil]; NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil]; //2.启动操作 这里必须要启动操作 [op1 start]; [op2 start]; } -(void)blockOperation { //1.封装操作 通过NSBlockOperation封装操作 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ //主线程执行 NSLog(@"download1---%@",[NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download2---%@",[NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download3---%@",[NSThread currentThread]); }]; //追加任务 //追加的任务在子线程中并发执行意味着会开线程 [op3 addExecutionBlock:^{ NSLog(@"download4---%@",[NSThread currentThread]); }]; [op3 addExecutionBlock:^{ NSLog(@"download5---%@",[NSThread currentThread]); }]; [op3 addExecutionBlock:^{ NSLog(@"download6---%@",[NSThread currentThread]); }]; //2.开始执行 [op1 start]; [op2 start]; [op3 start]; } -(void)customOperation { //1.封装任务 自定义NSOperation的方式封装任务也就是创建一个继承与NSOperation的类 重写-main方法,在方法中写要执行的任务 /*内部会调用MSHOperation的main方法*/ MSHOperation *op1 = [[MSHOperation alloc]init]; MSHOperation *op2 = [[MSHOperation alloc]init]; //2.开始执行 [op1 start]; [op2 start]; } -(void)download1 { NSLog(@"download1---%@",[NSThread currentThread]); }
以上直接封装操作,没什么作用,只有将封装的操作NAOperation对象放进NSOperationQueue才会创建线程
注意:默认异步,并发执行的执行的,如果想在控制它像GCD中的串行执行人任务的话,通过队列的最大并发数来设置(
queue.maxConcurrentOperationCount = 1;
)
接下是将封装的操作放进NSOperationqueu中
@implementation ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"-----"); [self customOperationWithQueue]; } //GCD /* 01 串行队列 自己创建|主队列 02 并发队列 自己创建|全局并发队列 */ //NSOperation /* 获取队列的两种方式 01 主队列 同GCD mainQueue 刷新UI 02 非主队列 alloc init 特点:同时具备了串行和并发的功能|默认是并发的 */ -(void)invocationOperationWithQueue { //0.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //1.封装操作 /* 第一个参数:目标对象 self 第二个参数:调用方法 第三个参数:调用方法需要传递的参数 */ NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil]; NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download2) object:nil]; //2.启动操作 //[op1 start]; 将NSOperation封装到队列中,不需要调用该方法,内部默认会执行该方法 //[op2 start]; //2.把操作添加到队列 在队列中添加多个NSOperation [queue addOperation:op1]; //[op1 start] [queue addOperation:op2]; //[op2 start] } -(void)blockOperationWithQueue { //0.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //1.封装操作 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ //主线程执行 NSLog(@"download1---%@",[NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download2---%@",[NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download3---%@",[NSThread currentThread]); }]; //追加任务 //追加的任务在子线程中并发执行 [op3 addExecutionBlock:^{ NSLog(@"download4---%@",[NSThread currentThread]); }]; [op3 addExecutionBlock:^{ NSLog(@"download5---%@",[NSThread currentThread]); }]; [op3 addExecutionBlock:^{ NSLog(@"download6---%@",[NSThread currentThread]); }]; //2.开始执行 // [op1 start]; 将封装操作的NSBlockOperation对象添加到队列中,内部也会调用- start方法 // [op2 start]; // [op3 start]; [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3];
开方法常用简便的方法,下面是它的原理 //简便方法:该方法内部会自动将block块里面的任务封装为一个NSBlockOperation对象,然后添加到队列 [queue addOperationWithBlock:^{ NSLog(@"download7---%@",[NSThread currentThread]); }]; } -(void)customOperationWithQueue { //0.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //1.封装任务 /*内部会调用MSHOperation的main方法*/ MSHOperation *op1 = [[MSHOperation alloc]init]; MSHOperation *op2 = [[MSHOperation alloc]init]; //2.开始执行 //[op1 start]; //main 自定义的NSOperation内部会调用Start方法,并且也会调用它的-main方法 //[op2 start]; [queue addOperation:op1]; [queue addOperation:op2]; }
下面是NSOperationqueu的一些常用属性的使用
最大并发数 同时执行的任务数 比如,同时开3个线程执行3个任务,并发数就是3 最大并发数的相关方法 - (NSInteger)maxConcurrentOperationCount; - (void)setMaxConcurrentOperationCount:(NSInteger)cnt; 队列的取消、暂停、恢复 - (void)cancelAllOperations; 提示:也可以调用NSOperation的- (void)cancel方法取消单个操作 暂停和恢复队列 - (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列 - (BOOL)isSuspended; 操作依赖 NSOperation之间可以设置依赖来保证执行顺序 比如一定要让操作A执行完后,才能执行操作B,可以这么写 [operationB addDependency:operationA]; // 操作B依赖于操作A 可以在不同queue的NSOperation之间创建依赖关系
注意:不能相互依赖
p比如A依赖B,B依赖A
监听操作完成
-(void)maxCount { //0.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //设置最大并发数 //-1 表示不受限制 queue.maxConcurrentOperationCount = 0; //1.封装操作 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ //主线程执行 NSLog(@"download1---%@",[NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download2---%@",[NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download3---%@",[NSThread currentThread]); 这里监听的是op3对象的任务完成
[op3 setCompletionBlock:^{ NSLog(@"download3下载完成"); }]; }]; [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3]; }
NSOperation线程之间的通信
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { //0.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //1.封装操作 NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:@"http://static.fever38.com/hotpolls/option_pic/2013032019073965331_500X.png"]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; NSLog(@"download--%@",[NSThread currentThread]); //回到主线程刷洗UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; NSLog(@"UI--%@",[NSThread currentThread]); }]; }]; //2.添加操作到队列 [queue addOperation:download]; }
多图下载的案例
@interface ViewController () /** tableView的数据源*/ @property (nonatomic ,strong)NSArray *apps; /** 内存缓存*/ @property (nonatomic ,strong)NSMutableDictionary *images; @property (nonatomic ,strong) NSOperationQueue *queue; /** 操作缓存*/ @property (nonatomic ,strong)NSMutableDictionary *operations; @end @implementation ViewController #pragma mark ---------------------- #pragma mark Lazy loading -(NSMutableDictionary *)operations { if (_operations == nil) { _operations = [NSMutableDictionary dictionary]; } return _operations; } -(NSOperationQueue *)queue { if (_queue == nil) { _queue = [[NSOperationQueue alloc]init]; _queue.maxConcurrentOperationCount = 6; } return _queue; } -(NSMutableDictionary *)images { if (_images == nil) { _images = [NSMutableDictionary dictionary]; } return _images; } -(NSArray *)apps { if (_apps == nil) { //加载数据 NSArray *arrayM = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"apps.plist" ofType:nil]]; //字典转模型 字典数组--->模型数组 NSMutableArray *arrM = [NSMutableArray arrayWithCapacity:arrayM.count]; for (NSDictionary *dict in arrayM) { [arrM addObject:[MSHApp appWithDict:dict]]; } _apps = arrM; NSLog(@"%@",arrM); } return _apps; } #pragma mark ---------------------- #pragma mark UITablViewDataSource -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.apps.count; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { //1.确定标识符 NSString *ID = @"app"; //2.创建cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; //3.设置cell //拿到该行cell对应的数据模型 MSHApp *appM = self.apps[indexPath.row]; //设置标题和子标题 cell.textLabel.text = appM.name; cell.detailTextLabel.text = appM.download;
// 加载图片先根据key去缓存中去取 UIImage *image = [self.images objectForKey:appM.icon]; if (image) { cell.imageView.image = image; }else { 如果缓存区中没有,则去沙盒中去取 //现尝试去沙河中取,如果没有那么才下载 NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString *fileName = [appM.icon lastPathComponent]; //文件的全路径 NSString *fullPth = [caches stringByAppendingPathComponent:fileName]; //NSLog(@"%@",fullPth); NSData *data = [NSData dataWithContentsOfFile:fullPth]; //沙盒缓存清空 data = nil; if (data) { UIImage *image = [UIImage imageWithData:data]; cell.imageView.image = image; //保存图片到内存缓存 注意:这里要记得从沙盒中获取图片以后,要添加到缓存区中 [self.images setObject:image forKey:appM.icon]; NSLog(@"沙河中有这样图片了"); }else {
如果沙盒中也没有,先设置站位图片,在根据URL区下载照片 //设置展占位图片 cell.imageView.image = [UIImage imageNamed:@"Snip20151222_498"]; //查看该图片的下载操作是否存在 线程是并发执行的,先判断有没有下载操作存在 NSBlockOperation *download = [self.operations objectForKey:appM.icon]; if (download == nil) { download = [NSBlockOperation blockOperationWithBlock:^{ //显示图片 NSURL *url = [NSURL URLWithString:appM.icon]; [NSThread sleepForTimeInterval:1.0]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; NSLog(@"-下载图片操作---%zd",indexPath.row); if (image == nil) { [self.operations removeObjectForKey:appM.icon]; return ; } //保存图片到内存缓存 [self.images setObject:image forKey:appM.icon]; //保存图片到沙河缓存 [data writeToFile:fullPth atomically:YES]; //线程间通信 [[NSOperationQueue mainQueue]addOperationWithBlock:^{ // NSLog(@"----%zd",indexPath.row); // cell.imageView.image = image; //刷新某一行 [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; }]; [self.operations removeObjectForKey:appM.icon]; }]; //加入到操作缓存 [self.operations setObject:download forKey:appM.icon]; //把操作添加到队列 [self.queue addOperation:download]; } } } //4.返回cell return cell; } /* Documents:会备份,不能把下载的数据保存在这个文件下面 Libriary caches preferences:偏好设置 tmp:临时文件夹 */ //问题 //01 UI不流畅 ------>开子线程下载 //01 数据不显示 //02 图片重复下载 ---操作缓存 //02 重复下载 ----内存缓存|优化(沙河缓存)
多图下载的案例(通过SDWebImageView)
框架下载地址 https://github.com/rs/SDWebImage
#import "ViewController.h" #import "MSHApp.h" 自定义模型 #import "UIImageView+WebCache.h" 包含的框架的头文件 @interface ViewController () /** tableView的数据源*/ @property (nonatomic ,strong)NSArray *apps; /** 内存缓存*/ @property (nonatomic ,strong)NSMutableDictionary *images; 缓存 @property (nonatomic ,strong) NSOperationQueue *queue; 存储操作 /** 操作缓存*/ @property (nonatomic ,strong)NSMutableDictionary *operations; @end @implementation ViewController #pragma mark ---------------------- #pragma mark Lazy loading -(NSMutableDictionary *)operations { if (_operations == nil) { _operations = [NSMutableDictionary dictionary]; } return _operations; } -(NSOperationQueue *)queue { if (_queue == nil) { _queue = [[NSOperationQueue alloc]init]; _queue.maxConcurrentOperationCount = 6; } return _queue; } -(NSMutableDictionary *)images { if (_images == nil) { _images = [NSMutableDictionary dictionary]; } return _images; } -(NSArray *)apps { if (_apps == nil) { //加载数据 NSArray *arrayM = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"apps.plist" ofType:nil]]; //字典转模型 字典数组--->模型数组 NSMutableArray *arrM = [NSMutableArray arrayWithCapacity:arrayM.count]; for (NSDictionary *dict in arrayM) { [arrM addObject:[MSHApp appWithDict:dict]]; } _apps = arrM; NSLog(@"%@",arrM); } return _apps; } #pragma mark ---------------------- #pragma mark UITablViewDataSource -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.apps.count; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { //1.确定标识符 NSString *ID = @"app"; //2.创建cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; //3.设置cell //拿到该行cell对应的数据模型 MSHApp *appM = self.apps[indexPath.row]; //设置标题和子标题 cell.textLabel.text = appM.name; cell.detailTextLabel.text = appM.download;
这里这需要一句话,解决上面的所有逻辑 牛逼!!!!吧
第一个参数是图片的URL
第二个图片是站位图片
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:appM.icon] placeholderImage:[UIImage imageNamed:@"Snip20151222_498"]]; NSLog(@"%@",[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]); //4.返回cell return cell; }
---恢复内容结束---