多线程-NSOperation和NSOperationQueue


NSOperation

NSOperation类是用来封装在单个任务相关的代码和数据的抽象类。NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。
** 因为它是用来封装任务的,大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列,
但是NSOperation本身又有执行多线程的能力跟GCD里的任务还是有区别的** 。

操作步骤也很好理解:

  • 将要执行的任务封装到一个 NSOperation 对象中。
  • 将此任务添加到一个 NSOperationQueue 对象中。
    然后系统就会自动在执行任务。
创建任务

NSOperation 只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation 和 NSBlockOperation 。创建一个 Operation 后,需要调用 start 方法来启动任务,它会默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其 cancel 方法即可。

 #.创建NSInvocationOperation对象
  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
  //开始执行
 [operation start];
 # 创建一个NSOperation不应该直接调用start方法(如果直接start则会在主线程中调用)而是应该放到NSOperationQueue中启动。

 #.创建NSBlockOperation对象
 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
  }];
  //开始任务
  [operation start];

注意:之前说过这样的任务,默认会在当前线程执行。但是 NSBlockOperation 还有一个方法:addExecutionBlock: ,通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务 会并发执行,它会 在主线程和其它的多个线程 执行这些任务.。并且ddExecutionBlock 方法必须在 start() 方法之前执行,否则就会报错。

# 创建NSBlockOperation对象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@", [NSThread currentThread]);
  }];
# 添加多个Block
  for (NSInteger i = 0; i < 5; i++) {
      [operation addExecutionBlock:^{
          NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
      }];
  }
  #开始任务,你会发现,会在多个线程中打印,其中包括主线程。
 [operation start];

自定义Operation

除了上面的两种 Operation 以外,我们还可以自定义 Operation。自定义 Operation 需要继承 NSOperation 类,并实现其 main() 方法,因为在调用 start() 方法的时候,内部会调用 main() 方法完成相关逻辑。所以如果以上的两个类无法满足你的欲望的时候,你就需要自定义了。你想要实现什么功能都可以写在里面。除此之外,你还需要实现 cancel() 在内的各种方法。

创建队列

我们可以调用一个 NSOperation 对象的 start() 方法来启动这个任务,这个默认是 同步执行 的。就算是 addExecutionBlock 方法,也会在 当前线程和其他线程 中执行,也就是说还是会占用当前线程。如果你不想这个任务在主线程中执行(代码默认情况下都在主线程中执行。)这是就要用到队列 NSOperationQueue 。按类型来说的话一共有两种类型:主队列、其他队列。只要添加到队列,会自动调用任务的 start() 方法。

  • 主队列
    主队列是串行队列,添加到主队列的任务都会一个接一个地排着队在主线程处理。
    # 获取到主队列
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
  • 其他创建的队列
    主队列比较特殊,所以会单独有一个类方法来获得主队列。并且其他队列的任务会在其他线程并行执行。请注意这里是并行执行。

    #1.创建一个其他队列    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    #2.创建NSBlockOperation对象
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
    }];
     #.添加多个Block
    for (NSInteger i = 0; i < 5; i++) {
        [operation addExecutionBlock:^{
            NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
        }];
    }
     #.队列添加任务
    [queue addOperation:operation];
    
NSOperationQueue的一些特殊使用
  • 设置最大并发数

我们将 NSOperationQueue 与 GCD的队列 相比较就会发现,这里没有串行队列,那如果我想要10个任务在其他线程串行的执行的话,NSOperationQueue 有一个参数 maxConcurrentOperationCount 最大并发数,用来设置最多可以让多少个任务同时执行。当你把它设置为 1 的时候,就相当于是串行队列了

  • NSOperationQueue 添加一个任务到队列
    - (void)addOperationWithBlock:(void (^)(void))block; 这样就可以添加一个任务到队列中了。

  • NSOperation 之间添加依赖

    NSOperation 有一个非常实用的功能,那就是对任务添加依赖。比如有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖了:

    #1.任务一:下载图片
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下载图片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
    }];
    
    #2.任务二:打水印
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"打水印   - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
    }];
    
    #3.任务三:上传图片
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"上传图片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
    }];
    
    #4.设置依赖
    [operation2 addDependency:operation1];      //任务二依赖任务一
    [operation3 addDependency:operation2];      //任务三依赖任务二
    
    #5.创建队列并加入任务
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
    

    注意
    A.不能添加相互依赖,会死锁,比如 A依赖B,B依赖A。
    B.可以使用 removeDependency 来解除依赖关系。
    C.可以在不同的队列之间依赖,依赖是添加到任务身上的,和队列没关系。

  • 线程的挂起

    #pragma mark - 线程的挂起
    #暂停继续(对队列的暂停和继续),挂起的是队列,不会影响已经在执行的操作
    - (IBAction)pause:(UIButton *)sender {
    //判断操作的数量,当前队列里面是不是有操作?
    if (self.opQueue.operationCount == 0) {
      NSLog(@"当前队列没有操作");
      return;
    }
    self.opQueue.suspended = !self.opQueue.isSuspended;
    if (self.opQueue.suspended) {
      NSLog(@"暂停");
    }else{
      NSLog(@"继续");
    }
    

    }

  • 取消队列里的所有操作

    #pragma mark - 取消队列里的所有操作
    - (IBAction)cancelAll:(UIButton *)sender {
    # 只能取消所有队列的里面的操作,正在执行的无法取消
    # 取消操作并不会影响队列的挂起状态
    [self.opQueue cancelAllOperations];
    NSLog(@"取消队列里所有的操作");
    //取消队列的挂起状态
    #(只要是取消了队列的操作,我们就把队列处于不挂起状态,以便于后续的开始)
    self.opQueue.suspended = NO;
    }
    

其他方法

以上就是一些主要方法, 下面还有一些常用方法需要大家注意:

#  NSOperation
BOOL executing; //判断任务是否正在执行
BOOL finished; //判断任务是否完成
#当一个 operation 被取消时,它的 completion block 仍然会执行,
#所以我们需要在真正执行代码前检查一下 isCancelled 方法的返回值。
- void (^completionBlock)(void); //用来设置完成后需要执行的操作
 
- (void)cancel; //取消任务
- (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕

#  NSOperationQueue
NSUInteger operationCount; //获取队列的任务数
- (void)cancelAllOperations; //取消队列中所有的任务
#阻塞当前线程直到此队列中的所有任务执行完毕
# **可以用来处理所有任务完成后的事件**
- (void)waitUntilAllOperationsAreFinished; 
[queue setSuspended:YES]; // 暂停queue
[queue setSuspended:NO]; // 继续queue
NSOperation和GCD的区别和类似的地方
  • GCD是纯C语言的API,NSOperationQueue是基于GCD的OC版本封装
  • GCD只支持FIFO的队列,NSOperationQueue可以很方便地调整执行顺序、设置最大并发数量
  • NSOperationQueue可以在轻松在Operation间设置依赖关系,而GCD需要写很多的代码才能实现
  • NSOperationQueue支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)
  • GCD的执行速度比NSOperationQueue快
    **任务之间没有什么依赖关系,而是需要更高的并发能力:GCD **
    任务之间有依赖、或者要监听任务的执行情况、需要暂停、继续任务:NSOperationQueue

小结

以上就是关于 NSOperation和NSOperationQueue 的主要知识了,后期如果有更多内容会同步更新。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容