4.5 NSOperation->2.0 NSOperationQueue

本文并非最终版本,如果想要关注更新或更正的内容请关注文集,联系方式详见文末,如有疏忽和遗漏,欢迎指正。


本文相关目录:
==================== 所属文集:4.0 多线程 ====================
4.1 多线程基础->1.0 进程 & 线程
······················ 2.0 多线程简介
4.2 pthread
4.3 NSThread->1.0 创建线程
····················· 2.0 线程属性
····················· 3.0 线程状态/线程生命周期
····················· 4.0 多线程安全隐患
····················· 5.0 线程间通讯和常用方法
4.4 GCD->1.0 GCD简介和使用
·············· 2.0 线程间的通信
·············· 3.0 其他用法
·············· 4.0 GCD 的定时器事件
4.5 NSOperation->1.0 NSOperation简介
························ 2.0 NSOperationQueue
························ 3.0 线程间通信
························ 4.0 自定义NSOperation
4.6 RunLoop - 运行循环
===================== 所属文集:4.0 多线程 =====================


2.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在后。


2.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)}

2.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)}

2.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)}

2.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
};

2.5 操作依赖

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

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

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


4.5 NSOperation->2.0 NSOperationQueue_第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 {

  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)}

2.6操作的监听

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

@property (nullable, copy) void (^completionBlock)(void);

- (void)setCompletionBlock:(void (^)(void))block;

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


本文源码 Demo 详见 Github
https://github.com/shorfng/iOS_4.0_multithreading.git





作者:蓝田(Loto)
出处:

如果你觉得本篇文章对你有所帮助,请点击文章末尾下方“喜欢”
如有疑问,请通过以下方式交流:
评论区回复微信(加好友请注明“+称呼”)发送邮件[email protected]



本文版权归作者和本网站共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

你可能感兴趣的:(4.5 NSOperation->2.0 NSOperationQueue)