博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
4.4 多线程进阶篇<下>(NSOperation)
阅读量:5182 次
发布时间:2019-06-13

本文共 29514 字,大约阅读时间需要 98 分钟。

本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末

如果觉得本文内容过长,请前往本人

本文源码 Demo 详见 Github

https://github.com/shorfng/iOS-4.0-multithreading.git

1.0 NSOperation 的作用

使用 NSOperation 的目的就是为了让开发人员不再关心线程

  • 配合使用 NSOperation(任务) 和 NSOperationQueue(队列) 也能实现多线程编程

NSOperation 和 NSOperationQueue 实现多线程的具体步骤:

(1)先将需要执行的操作封装到一个NSOperation对象中

(2)然后将NSOperation对象添加到NSOperationQueue中

(3)系统会自动将NSOperationQueue中的NSOperation取出来

(4)将取出的NSOperation封装的操作放到一条新线程中执行

使用NSOperation子类的方式有3种:

NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类

  1. NSInvocationOperation
  • NSBlockOperation
  • 自定义子类继承NSOperation,实现内部相应的方法

2.0 NSInvocationOperation

//创建NSInvocationOperation对象- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;//调用start方法开始执行操作,一旦执行操作,就会调用target的sel方法- (void)start;

注意:

  • 默认情况下,操作对象在主线程中执行
  • 调用了start方法后并不会开一条新线程去执行操作,只有添加到队列中才会开启新的线程
  • 即默认情况下,如果操作没有放到队列中queue中,都是同步执行。
  • 只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作

代码示例:

#import "ViewController.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {  [super viewDidLoad];  // Do any additional setup after loading the view, typically from a nib.}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  //创建操作对象,封装要执行的任务  NSInvocationOperation *op =      [[NSInvocationOperation alloc] initWithTarget:self                                           selector:@selector(run)                                             object:nil];  //执行操作  [op start];}- (void)run {  NSLog(@"------%@", [NSThread currentThread]);}- (void)didReceiveMemoryWarning {  [super didReceiveMemoryWarning];  // Dispose of any resources that can be recreated.}@end

打印结果:

NSInvocationOperation[862:29437] ------
{number = 1, name = main}

3.0 NSBlockOperation

//创建 NSBlockOperation 操作对象+ (id)blockOperationWithBlock:(void (^)(void))block;// 添加操作- (void)addExecutionBlock:(void (^)(void))block;

注意:只要NSBlockOperation封装的操作数 > 1,就会异步执行操作


代码示例:

#import "ViewController.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {  [super viewDidLoad];  // Do any additional setup after loading the view, typically from a nib.}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  // 1.创建 NSBlockOperation 操作对象  NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{    // 在主线程    NSLog(@"下载1------%@", [NSThread currentThread]);  }];  // 2.添加操作(额外的任务)(在子线程执行)  [op addExecutionBlock:^{    NSLog(@"下载2------%@", [NSThread currentThread]);  }];  [op addExecutionBlock:^{      [op addExecutionBlock:^{    NSLog(@"下载2------%@", [NSThread currentThread]);  }];  [op addExecutionBlock:^{    NSLog(@"下载3------%@", [NSThread currentThread]);  }];  [op addExecutionBlock:^{    NSLog(@"下载4------%@", [NSThread currentThread]);  }];  // 3.开启执行操作  [op start];}- (void)run {  NSLog(@"------%@", [NSThread currentThread]);}- (void)didReceiveMemoryWarning {  [super didReceiveMemoryWarning];  // Dispose of any resources that can be recreated.}@end

打印结果:

NSBlockOperation[1013:37922] 下载1------
{number = 1, name = main}NSBlockOperation[1013:37952] 下载2------
{number = 2, name = (null)}NSBlockOperation[1013:37955] 下载3------
{number = 3, name = (null)}NSBlockOperation[1013:37951] 下载4------
{number = 4, name = (null)}

4.0 NSOperationQueue

NSOperationQueue的作用:添加操作到NSOperationQueue中,自动执行操作,自动开启线程

  • NSOperation 可以调用 start 方法来执行任务,但默认是同步执行的
  • 如果将 NSOperation 添加到 NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作

添加操作到 NSOperationQueue 中:2种方式

- (void)addOperation:(NSOperation *)op;- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);

代码示例:

#import "ViewController.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {  [super viewDidLoad];  // Do any additional setup after loading the view, typically from a nib.}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  [self operationQueue2];}#pragma mark - 把操作添加到队列中,方式1:addOperation- (void)operationQueue1 {  // 1.创建队列  NSOperationQueue *queue = [[NSOperationQueue alloc] init];  // 2.1 方式1:创建操作(任务)NSInvocationOperation ,封装操作  NSInvocationOperation *op1 =      [[NSInvocationOperation alloc] initWithTarget:self                                           selector:@selector(download1)                                             object:nil];  // 2.2 方式2:创建NSBlockOperation ,封装操作  NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{    NSLog(@"download2 --- %@", [NSThread currentThread]);  }];  // 添加操作  [op2 addExecutionBlock:^{    NSLog(@"download3 --- %@", [NSThread currentThread]);  }];  // 3.把操作(任务)添加到队列中,并自动调用 start 方法  [queue addOperation:op1];  [queue addOperation:op2];}- (void)download1 {  NSLog(@"download1 --- %@", [NSThread currentThread]);}#pragma mark - 把操作添加到队列中,方式2:addOperationWithBlock- (void)operationQueue2 {  // 1.创建队列  NSOperationQueue *queue = [[NSOperationQueue alloc] init];  // 2.添加操作到队列中  [queue addOperationWithBlock:^{    NSLog(@"download1 --- %@", [NSThread currentThread]);  }];  [queue addOperationWithBlock:^{    NSLog(@"download2 --- %@", [NSThread currentThread]);  }];}- (void)didReceiveMemoryWarning {  [super didReceiveMemoryWarning];  // Dispose of any resources that can be recreated.}@end

打印结果:

NSOperationQueue[1658:89517] download2 --- 
{number = 3, name = (null)} NSOperationQueue[1658:89518] download1 ---
{number = 2, name = (null)} NSOperationQueue[1658:89521] download3 ---
{number = 4, name = (null)} NSOperationQueue[1704:92509] download2 ---
{number = 2, name = (null)} NSOperationQueue[1704:92513] download1 ---
{number = 3, name = (null)}

提示:队列的取出是有顺序的,与打印结果并不矛盾。这就好比,选手A,BC虽然起跑的顺序是先A,后B,然后C,但是到达终点的顺序却不一定是A,B在前,C在后。

4.1 最大并发数

并发数:同时执⾏行的任务数 比如,同时开3个线程执行3个任务,并发数就是3

最大并发数:同一时间最多只能执行的任务的个数

最⼤并发数的相关⽅方法:

//最大并发数,默认为-1@property NSInteger maxConcurrentOperationCount;- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

说明:

  • 如果没有设置最大并发数,那么并发的个数是由系统内存和CPU决定的,内存多就开多一点,内存少就开少一点。
  • 最⼤并发数的值并不代表线程的个数,仅仅代表线程的ID。
  • 最大并发数不要乱写(5以内),不要开太多,一般以2~3为宜,因为虽然任务是在子线程进行处理的,但是cpu处理这些过多的子线程可能会影响UI,让UI变卡。
  • 最大并发数的值为1,就变成了串行队列

代码示例:

#import "ViewController.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {  [super viewDidLoad];  // Do any additional setup after loading the view, typically from a nib.}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  // 1.创建队列  NSOperationQueue *queue = [[NSOperationQueue alloc] init];  // 2.设置最大并发操作数(大并发操作数 = 1,就变成了串行队列)  queue.maxConcurrentOperationCount = 2;  // 3.添加操作  [queue addOperationWithBlock:^{    NSLog(@"download1 --- %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:0.01];  }];  [queue addOperationWithBlock:^{    NSLog(@"download2 --- %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:0.01];  }];  [queue addOperationWithBlock:^{    NSLog(@"download3 --- %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:0.01];  }];  [queue addOperationWithBlock:^{    NSLog(@"download4 --- %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:0.01];  }];  [queue addOperationWithBlock:^{    NSLog(@"download5 --- %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:0.01];  }];  [queue addOperationWithBlock:^{    NSLog(@"download6 --- %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:0.01];  }];}- (void)didReceiveMemoryWarning {  [super didReceiveMemoryWarning];  // Dispose of any resources that can be recreated.}@end

打印结果:

最大并发数[1909:113433] download2 --- 
{number = 3, name = (null)}最大并发数[1909:113432] download1 ---
{number = 2, name = (null)}最大并发数[1909:113432] download4 ---
{number = 2, name = (null)}最大并发数[1909:113431] download3 ---
{number = 4, name = (null)}最大并发数[1909:113428] download5 ---
{number = 5, name = (null)}最大并发数[1909:113432] download6 ---
{number = 2, name = (null)}

4.2 队列的暂停和恢复

队列的暂停:当前任务结束后,暂停执行下一个任务,而非当前任务

//暂停和恢复队列(YES代表暂停队列,NO代表恢复队列)- (void)setSuspended:(BOOL)b;//当前状态- (BOOL)isSuspended;

暂停和恢复的使用场合:

在tableview界面,开线程下载远程的网络界面,对UI会有影响,使用户体验变差。那么这种情况,就可以设置在用户操作UI(如滚动屏幕)的时候,暂停队列(不是取消队列),停止滚动的时候,恢复队列。


代码示例:

#import "ViewController.h"@interface ViewController ()@property(nonatomic, strong) NSOperationQueue *queue; //队列@end@implementation ViewController- (void)viewDidLoad {  [super viewDidLoad];  // 1.创建队列  NSOperationQueue *queue = [[NSOperationQueue alloc] init];  // 2.设置最大并发操作数(大并发操作数 = 1,就变成了串行队列)  queue.maxConcurrentOperationCount = 1;  // 3.添加操作  [queue addOperationWithBlock:^{    NSLog(@"download1 --- %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:3];  }];  [queue addOperationWithBlock:^{    NSLog(@"download2 --- %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:3];  }];  [queue addOperationWithBlock:^{    NSLog(@"download3 --- %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:3];  }];  [queue addOperationWithBlock:^{    NSLog(@"download4 --- %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:3];  }];  self.queue = queue;}#pragma mark - 暂停和恢复- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  if (self.queue.isSuspended) {    self.queue.suspended = NO; // 恢复队列,继续执行  } else {    self.queue.suspended = YES; // 暂停(挂起)队列,暂停执行  }}- (void)didReceiveMemoryWarning {  [super didReceiveMemoryWarning];  // Dispose of any resources that can be recreated.}@end

打印结果:

队列的暂停和恢复[2650:156206] download1 --- 
{number = 3, name = (null)}队列的暂停和恢复[2650:156205] download2 ---
{number = 2, name = (null)}队列的暂停和恢复[2650:156206] download3 ---
{number = 3, name = (null)}队列的暂停和恢复[2650:156385] download4 ---
{number = 4, name = (null)}

4.3 队列的取消

取消队列的所有操作:相等于调用了所有 NSOperation 的 -(void)cancel 方法,

当前任务结束后,取消执行下面的所有任务,而非当前任务

// 也可调用NSOperation的 -(void)cancel 方法取消单个操作- (void)cancelAllOperations;

代码示例:

#import "ViewController.h"@interface ViewController ()@property(nonatomic, strong) NSOperationQueue *queue; //队列@end@implementation ViewController- (void)viewDidLoad {  [super viewDidLoad];  // 1.创建队列  NSOperationQueue *queue = [[NSOperationQueue alloc] init];  // 2.设置最大并发操作数(大并发操作数 = 1,就变成了串行队列)  queue.maxConcurrentOperationCount = 1;  // 3.添加操作  [queue addOperationWithBlock:^{    NSLog(@"download1 --- %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:3];  }];  [queue addOperationWithBlock:^{    NSLog(@"download2 --- %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:3];  }];  [queue addOperationWithBlock:^{    NSLog(@"download3 --- %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:3];  }];  [queue addOperationWithBlock:^{    NSLog(@"download4 --- %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:3];  }];  self.queue = queue;}#pragma mark - 取消队列的所有操作- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  // 取消队列的所有操作(相等于调用了所有NSOperation的-(void)cancel方法)  [self.queue cancelAllOperations];}- (void)didReceiveMemoryWarning {  [super didReceiveMemoryWarning];  // Dispose of any resources that can be recreated.}@end

打印结果:

队列的取消[3041:167756] download1 --- 
{number = 3, name = (null)}队列的取消[3041:167749] download2 ---
{number = 2, name = (null)}

4.4 操作优先级

设置NSOperation在queue中的优先级,可以改变操作的执行优先级:

@property NSOperationQueuePriority queuePriority;- (void)setQueuePriority:(NSOperationQueuePriority)p;

优先级的取值:优先级高的任务,调用的几率会更大

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {NSOperationQueuePriorityVeryLow = -8L,NSOperationQueuePriorityLow = -4L,NSOperationQueuePriorityNormal = 0,NSOperationQueuePriorityHigh = 4,NSOperationQueuePriorityVeryHigh = 8};

4.5 操作依赖

NSOperation之间可以设置依赖来保证执行顺序:不能循环依赖(不能A依赖于B,B又依赖于A)

// 操作B依赖于操作A(一定要让操作A执行完后,才能执行操作B)[operationB addDependency:operationA];

可以在不同queue的NSOperation之间创建依赖关系(跨队列依赖):

731328-20160328103529035-1305813480.png

注意:

  • 一定要在把操作添加到队列之前,进行设置操作依赖。
  • 任务添加的顺序并不能够决定执行顺序,执行的顺序取决于依赖。

代码示例:

#import "ViewController.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {  [super viewDidLoad];  // Do any additional setup after loading the view, typically from a nib.}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  NSOperationQueue *queue = [[NSOperationQueue alloc] init];  //创建对象,封装操作  NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{    NSLog(@"download1----%@", [NSThread currentThread]);  }];  NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{    NSLog(@"download2----%@", [NSThread currentThread]);  }];  NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{    NSLog(@"download3----%@", [NSThread currentThread]);  }];  NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{    for (NSInteger i = 0; i < 5; i++) {      NSLog(@"download4----%@", [NSThread currentThread]);    }  }];  NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{    NSLog(@"download5----%@", [NSThread currentThread]);  }];  //操作的监听  op5.completionBlock = ^{    NSLog(@"op5执行完毕---%@", [NSThread currentThread]);  };  //设置操作依赖(op4执行完,才执行 op3)  [op3 addDependency:op1];  [op3 addDependency:op2];  [op3 addDependency:op4];  //把操作添加到队列中  [queue addOperation:op1];  [queue addOperation:op2];  [queue addOperation:op3];  [queue addOperation:op4];  [queue addOperation:op5];}- (void)didReceiveMemoryWarning {  [super didReceiveMemoryWarning];  // Dispose of any resources that can be recreated.}@end

打印结果:

操作依赖[4196:150518] download5----
{number = 3, name = (null)}操作依赖[4196:150506] download1----
{number = 4, name = (null)}操作依赖[4196:150509] download4----
{number = 2, name = (null)}操作依赖[4196:150510] download2----
{number = 5, name = (null)}操作依赖[4196:150518] op5执行完毕---
{number = 3, name = (null)}操作依赖[4196:150509] download4----
{number = 2, name = (null)}操作依赖[4196:150509] download4----
{number = 2, name = (null)}操作依赖[4196:150509] download4----
{number = 2, name = (null)}操作依赖[4196:150509] download4----
{number = 2, name = (null)}操作依赖[4196:150509] download3----
{number = 2, name = (null)}

操作的监听

可以监听一个操作的执行完毕:

@property (nullable, copy) void (^completionBlock)(void);- (void)setCompletionBlock:(void (^)(void))block;

代码详见4.5 操作依赖 示例代码

5.0 线程间通信(图片下载示例)

#import "ViewController.h"@interface ViewController ()@property(weak, nonatomic) IBOutlet UIImageView *imageView;@end@implementation ViewController- (void)viewDidLoad {  [super viewDidLoad];  // Do any additional setup after loading the view, typically from a nib.}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  [self test2];}#pragma mark - 线程间通信(图片合成)- (void)test1 {  // 1.队列  NSOperationQueue *queue = [[NSOperationQueue alloc] init];  __block UIImage *image1 = nil;  // 2.下载图片1  NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{    // 图片的网络路径    NSURL *url =        [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/"                             @"8/1/9981681/200910/11/1255259355826.jpg"];    // 加载图片    NSData *data = [NSData dataWithContentsOfURL:url];    // 生成图片    image1 = [UIImage imageWithData:data];  }];  __block UIImage *image2 = nil;  // 3.下载图片2  NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{    // 图片的网络路径    NSURL *url = [NSURL        URLWithString:            @"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];    // 加载图片    NSData *data = [NSData dataWithContentsOfURL:url];    // 生成图片    image2 = [UIImage imageWithData:data];  }];  // 4.合成图片  NSBlockOperation *combine = [NSBlockOperation blockOperationWithBlock:^{    // 开启新的图形上下文    UIGraphicsBeginImageContext(CGSizeMake(100, 100));    // 绘制图片1    [image1 drawInRect:CGRectMake(0, 0, 50, 100)];    image1 = nil;    // 绘制图片2    [image2 drawInRect:CGRectMake(50, 0, 50, 100)];    image2 = nil;    // 取得上下文中的图片    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();    // 结束上下文    UIGraphicsEndImageContext();    // 5.回到主线程    [[NSOperationQueue mainQueue] addOperationWithBlock:^{      self.imageView.image = image;    }];  }];  // 设置依赖操作  [combine addDependency:download1];  [combine addDependency:download2];  //把操作添加到队列中  [queue addOperation:download1];  [queue addOperation:download2];  [queue addOperation:combine];}#pragma mark - 线程间通信(图片下载)- (void)test2 {  [[[NSOperationQueue alloc] init] addOperationWithBlock:^{    // 图片的网络路径    NSURL *url =        [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/"                             @"8/1/9981681/200910/11/1255259355826.jpg"];    // 加载图片    NSData *data = [NSData dataWithContentsOfURL:url];    // 生成图片    UIImage *image = [UIImage imageWithData:data];    // 回到主线程    [[NSOperationQueue mainQueue] addOperationWithBlock:^{      self.imageView.image = image;    }];  }];}- (void)didReceiveMemoryWarning {  [super didReceiveMemoryWarning];  // Dispose of any resources that can be recreated.}@end

6.0 自定义NSOperation

自定义NSOperation的步骤很简单:

  • 重写- (void)main方法,在里面实现想执行的任务

重写- (void)main方法的注意点:

  • 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
  • 经常通过- (BOOL)isCancelled方法检测操作是否被取消,对取消做出响应

ViewController.m

#import "TDOperation.h"#import "ViewController.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {  [super viewDidLoad];  // Do any additional setup after loading the view, typically from a nib.}- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  // 1.创建队列  NSOperationQueue *queue = [[NSOperationQueue alloc] init];  // 2.创建自定义 TDGOperation  TDOperation *op = [[TDOperation alloc] init];  // 3.把操作(任务)添加到队列中,并自动调用 start 方法  [queue addOperation:op];}- (void)didReceiveMemoryWarning {  [super didReceiveMemoryWarning];  // Dispose of any resources that can be recreated.}@end

TDOperation.h(继承自:NSOperation)

#import 
@interface TDOperation : NSOperation@end

TDOperation.m

#import "TDOperation.h"@implementation TDOperation//需要执行的任务- (void)main {  for (NSInteger i = 0; i < 3; i++) {    NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);  }  // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行  if (self.isCancelled)    return;  for (NSInteger i = 0; i < 3; i++) {    NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);  }  // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行  if (self.isCancelled)    return;  for (NSInteger i = 0; i < 3; i++) {    NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);  }  // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行  if (self.isCancelled)    return;}@end

运行结果:

自定义NSOperation[1567:84075] download1 -0-- 
{number = 2, name = (null)}自定义NSOperation[1567:84075] download1 -1--
{number = 2, name = (null)}自定义NSOperation[1567:84075] download1 -2--
{number = 2, name = (null)}自定义NSOperation[1567:84075] download2 -0--
{number = 2, name = (null)}自定义NSOperation[1567:84075] download2 -1--
{number = 2, name = (null)}自定义NSOperation[1567:84075] download2 -2--
{number = 2, name = (null)}自定义NSOperation[1567:84075] download3 -0--
{number = 2, name = (null)}自定义NSOperation[1567:84075] download3 -1--
{number = 2, name = (null)}自定义NSOperation[1567:84075] download3 -2--
{number = 2, name = (null)}

6.1 自定义NSOperation队列的取消操作

代码示例:

ViewController.m#import "TDOperation.h"#import "ViewController.h"@interface ViewController ()@property(nonatomic, strong) NSOperationQueue *queue; //队列@end@implementation ViewController- (void)viewDidLoad {  [super viewDidLoad];  // 1.创建队列  NSOperationQueue *queue = [[NSOperationQueue alloc] init];  // 2.设置最大并发操作数(大并发操作数 = 1,就变成了串行队列)  queue.maxConcurrentOperationCount = 2;  // 3.添加操作 - 自定义 NSOperation  [queue addOperation:[[TDOperation alloc] init]];  self.queue = queue;}#pragma mark - 取消队列的所有操作- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  // 取消队列的所有操作(相等于调用了所有NSOperation的-(void)cancel方法)  [self.queue cancelAllOperations];}- (void)didReceiveMemoryWarning {  [super didReceiveMemoryWarning];  // Dispose of any resources that can be recreated.}@end

TDOperation.h

#import 
@interface TDOperation : NSOperation@end

TDOperation.m

#import "TDOperation.h"@implementation TDOperation//需要执行的任务- (void)main {  for (NSInteger i = 0; i < 1000; i++) {    NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);  }  // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行  if (self.isCancelled)    return;  for (NSInteger i = 0; i < 1000; i++) {    NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);  }  // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行  if (self.isCancelled)    return;  for (NSInteger i = 0; i < 1000; i++) {    NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);  }  // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行  if (self.isCancelled)    return;}@end

6.2 多图下载

沙盒结构:

Documents Library    - Caches    - Preference tmp

自定义NSOperation下载图片思路 – 有沙盒缓存

731328-20160328110251488-255618107.png


代码示例:

ViewController.m

#import "TDApp.h"#import "ViewController.h"@interface ViewController ()@property(nonatomic, strong) NSArray *apps;                   //所有数据@property(nonatomic, strong) NSMutableDictionary *imageCache; //内存缓存的图片@property(nonatomic, strong) NSOperationQueue *queue;         //队列对象@property(nonatomic, strong) NSMutableDictionary *operations; //所有的操作对象@end@implementation ViewController- (void)viewDidLoad {  [super viewDidLoad];  // Do any additional setup after loading the view, typically from a nib.}#pragma mark - 数据源方法- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {  return self.apps.count;}#pragma mark - Cell- (UITableViewCell *)tableView:(UITableView *)tableView         cellForRowAtIndexPath:(NSIndexPath *)indexPath {  // 重用标识  static NSString *ID = @"app";  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];  TDApp *app = self.apps[indexPath.row];#pragma mark - app 名称  cell.textLabel.text = app.name;#pragma mark - 下载量  cell.detailTextLabel.text = app.download;#pragma mark - 图片  // 1.先从内存缓存中取出图片  UIImage *image = self.imageCache[app.icon];  // 2.判断内存中是否有图片  if (image) {    // 2.1 内存中有图片,直接设置图片    cell.imageView.image = image;  } else {    // 2.2 内存中没有图片,将图片文件数据写入沙盒中    //(1)获得Library/Caches文件夹    NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(        NSCachesDirectory, NSUserDomainMask, YES) firstObject];    //(2)获得文件名    NSString *filename = [app.icon lastPathComponent];    //(3)计算出文件的全路径    NSString *file = [cachesPath stringByAppendingPathComponent:filename];    //(4)加载沙盒的文件数据    NSData *data = [NSData dataWithContentsOfFile:file];    // 2.3 判断沙盒中是否有图片    if (data) {      // 有图片,直接利用沙盒中图片,设置图片      UIImage *image = [UIImage imageWithData:data];      cell.imageView.image = image;      // 并将图片存到字典中      self.imageCache[app.icon] = image;    } else {      // 没有图片,先设置一个占位图      cell.imageView.image = [UIImage imageNamed:@"placeholder"];      // 取出图片,并判断这张图片是否有下载操作      NSOperation *operation = self.operations[app.icon];      if (operation == nil) {        // 如果这张图片暂时没有下载操作,则需要创建一个下载操作        // 下载图片是耗时操作,放到子线程        operation = [NSBlockOperation blockOperationWithBlock:^{          // 下载图片          NSData *data =              [NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]];          // 如果数据下载失败          if (data == nil) {            // 下载失败,移除操作            [self.operations removeObjectForKey:app.icon];            return;          }            // 下载成功,将图片放在 image 中          UIImage *image = [UIImage imageWithData:data];          // 存到字典中          self.imageCache[app.icon] = image;          //回到主线程显示图片          [[NSOperationQueue mainQueue] addOperationWithBlock:^{            [tableView reloadRowsAtIndexPaths:@[ indexPath ]                             withRowAnimation:UITableViewRowAnimationNone];          }];          // 将图片文件数据写入沙盒中          [data writeToFile:file atomically:YES];          // 下载完毕,移除操作          [self.operations removeObjectForKey:app.icon];        }];        // 添加到队列中(队列的操作不需要移除,会自动移除)        [self.queue addOperation:operation];        // 并将图片存到字典中        self.operations[app.icon] = operation;      }    }  }  return cell;}#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) {      [appArray addObject:[TDApp appWithDict:dict]];    }    _apps = appArray;  }  return _apps;}#pragma mark - 懒加载- (NSMutableDictionary *)imageCache {  if (!_imageCache) {    _imageCache = [NSMutableDictionary dictionary];  }  return _imageCache;}#pragma mark - 懒加载- (NSOperationQueue *)queue {  if (!_queue) {    _queue = [[NSOperationQueue alloc] init];    _queue.maxConcurrentOperationCount = 3;  }  return _queue;}#pragma mark - 懒加载- (NSMutableDictionary *)operations {  if (!_operations) {    _operations = [NSMutableDictionary dictionary];  }  return _operations;}- (void)didReceiveMemoryWarning {  [super didReceiveMemoryWarning];  // Dispose of any resources that can be recreated.}@end

TDApp.h

#import 
@interface TDApp : NSObject@property(nonatomic, strong) NSString *icon; // 图片@property(nonatomic, strong) NSString *download; //下载量@property(nonatomic, strong) NSString *name; // 名字+ (instancetype)appWithDict:(NSDictionary *)dict;@end

TDApp.m

#import "TDApp.h"@implementation TDApp+ (instancetype)appWithDict:(NSDictionary *)dict {  TDApp *app = [[self alloc] init];  [app setValuesForKeysWithDictionary:dict];  return app;}@end

6.3 多图下载 - SDWebImage

SDWebImage:

  • iOS中著名的网络图片处理框架
  • 包含的功能:图片下载、图片缓存、下载进度监听、gif处理等等
  • 框架地址:https://github.com/rs/SDWebImage

  • SDWebImage的图片缓存周期是:1周


代码示例:

ViewController.m

#import "TDApp.h"#import "UIImageView+WebCache.h"#import "ViewController.h"@interface ViewController ()@property(nonatomic, strong) NSArray *apps;                   //所有数据@property(nonatomic, strong) NSMutableDictionary *imageCache; //内存缓存的图片@property(nonatomic, strong) NSOperationQueue *queue;         //队列对象@property(nonatomic, strong) NSMutableDictionary *operations; //所有的操作对象@end@implementation ViewController- (void)viewDidLoad {  [super viewDidLoad];  // Do any additional setup after loading the view, typically from a nib.}#pragma mark - 数据源方法- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {  return self.apps.count;}#pragma mark - Cell- (UITableViewCell *)tableView:(UITableView *)tableView         cellForRowAtIndexPath:(NSIndexPath *)indexPath {  // 重用标识  static NSString *ID = @"app";  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];  TDApp *app = self.apps[indexPath.row];#pragma mark - app 名称  cell.textLabel.text = app.name;#pragma mark - 下载量  cell.detailTextLabel.text = app.download;#pragma mark - 图片  // expectedSize: 图片的总字节数  receivedSize: 已经接收的图片字节数  [cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon]      placeholderImage:[UIImage imageNamed:@"placeholder"]      options:0 // 0 表示什么都不做      progress:^(NSInteger receivedSize, NSInteger expectedSize) {        NSLog(@"下载进度:%f", 1.0 * receivedSize / expectedSize);      }      completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType,                  NSURL *imageURL) {        NSLog(@"下载完图片");      }];  return cell;}#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) {      [appArray addObject:[TDApp appWithDict:dict]];    }    _apps = appArray;  }  return _apps;}#pragma mark - 懒加载- (NSMutableDictionary *)imageCache {  if (!_imageCache) {    _imageCache = [NSMutableDictionary dictionary];  }  return _imageCache;}#pragma mark - 懒加载- (NSOperationQueue *)queue {  if (!_queue) {    _queue = [[NSOperationQueue alloc] init];    _queue.maxConcurrentOperationCount = 3;  }  return _queue;}#pragma mark - 懒加载- (NSMutableDictionary *)operations {  if (!_operations) {    _operations = [NSMutableDictionary dictionary];  }  return _operations;}#pragma mark - 设置控制器的内存警告- (void)didReceiveMemoryWarning {  [super didReceiveMemoryWarning];  self.imageCache = nil;  self.operations = nil;  [self.queue cancelAllOperations];}@end

TDApp.h

#import 
@interface TDApp : NSObject@property(nonatomic, strong) NSString *icon; // 图片@property(nonatomic, strong) NSString *download; //下载量@property(nonatomic, strong) NSString *name; // 名字+ (instancetype)appWithDict:(NSDictionary *)dict;@end

TDApp.m

#import "TDApp.h"@implementation TDApp+ (instancetype)appWithDict:(NSDictionary *)dict {  TDApp *app = [[self alloc] init];  [app setValuesForKeysWithDictionary:dict];  return app;}@end

7.0【区别】GCD & NSOperationQueue 队列类型的创建方式

GCD 队列类型的创建方式:

(1)并发队列:手动创建、全局

(2)串行队列:手动创建、主队列


NSOperationQueue的队列类型的创建方法:

(1)主队列:[NSOperationQueue mainQueue]

  • 凡是添加到主队列中的任务(NSOperation),都会放到主线程中执行

(2)其他队列(同时包含了串行、并发功能):[NSOperationQueue alloc]init]

  • 添加到这种队列中的任务(NSOperation),就会自动放到子线程中执行

注:关于SDWebImage框架的详解会另外再写博客

如果你觉得本篇文章对你有所帮助,请点击文章末尾右下角“推荐”,^_^

作者:蓝田(Loto)

出处:http://www.cnblogs.com/shorfng/
如有疑问,请在下方 评论区回复 OR 发送邮件 至 联系我。
本文版权归作者和本网站共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

转载于:https://www.cnblogs.com/shorfng/p/5327918.html

你可能感兴趣的文章
解决ThinkPHP关闭调试模式时报错的问题汇总
查看>>
【APT】SqlServer游标使用
查看>>
关于ExecuteNonQuery()返回值为-1
查看>>
Firefox修復QQ快速登錄
查看>>
PAT——1060. 爱丁顿数
查看>>
分布式技术追踪 2017年第二十期
查看>>
git添加公钥后报错sign_and_send_pubkey: signing failed: agent refused operation的解决办法
查看>>
Linux环境变量永久设置方法(zsh)
查看>>
MVC4.0 利用IActionFilter实现简单的后台操作日志功能
查看>>
脑袋卡在窗子里
查看>>
ruby 中文字符to_json后乱码(unicode)
查看>>
《大道至简》第六章读后感
查看>>
codeforce 597C-Subsequences(dp+树状数组)
查看>>
[android](学习笔记6)为应用程序添加对话框(1)
查看>>
windows下mongodb安装与使用
查看>>
rotate the clock
查看>>
bugku 变量
查看>>
Python 环境傻瓜式搭建 :Anaconda概述
查看>>
趁热打铁第一季《移动APP开发使用什么样的原型设计工具比较合适?》
查看>>
debian6之eclipse和jdk安装
查看>>