iOS多线程系列之四:NSOperation以及多线程技术比较

本文导读:NSOperation作为苹果推荐的重要并发技术之一,在开发当中也较为常用。本文将详细介绍NSOperation两个子类以及NSOperationQueue的使用。而笔者前面的文章[iOS多线程基础][1]已经详细介绍了简单的多线程NSThread和基于C语言的功能强大的GCD,有需要的同学可以去看一下。既然有三种多线程技术,那它们又有什么区别呢?使用场景怎样呢?笔者将在本文末尾为大家一一解答

NSOperation是苹果推荐使用的并发技术,它提供了一些用GCD不是很好实现的功能。NSOperation是基于GCD的面向对象的使用OC语言的封装。相比GCD,NSOperation的使用更加简单。NSOperation是一个抽象类,也就是说它并不能直接使用,而是应该使用它的子类。使用它的子类的方法有三种,使用苹果为我们提供的两个子类 NSInvocationOperation,NSBlockOperation和自定义继承自NSOperation的子类。

NSOperation的使用常常是配合NSOperationQueue来进行的。只要是使用NSOperation的子类创建的实例就能添加到NSOperationQueue操作队列之中,一旦添加到队列,操作就会自动异步执行(注意是异步)。如果没有添加到队列,而是使用start方法,则会在当前线程执行。

我们知道,线程间的通信主要是主线程与分线程之间进行的。主线程到分线程,NSOperation子类也有相应带参数的方法;而分线程到主线程,比如更新UI,它也有很方便的获取主队列(被添加到主队列的操作默认会在主线程执行)的方法:[NSOperationQueue mainQueue]。

一、NSInvocationOperation

1.单个NSInvocationOperation

<1>直接创建一个NSInvocationOperation的对象,然后调用start方法会直接在主线程执行

//1.创建
  NSOperation *op = [[NSInvocationOperation alloc]initWithTarget:self 
selector:@selector(downloadImage:) object:@"Invocation"];
 //2.start方法,直接在当前线程执行
    [op start];

#pragma mark - 调用的耗时操作,后面调用的耗时操作都是这个
- (void)downloadImage:(id)obj{
  NSLog(@"%@-----%@",[NSThread currentThread],obj);
}

输出 [1151:50868] <NSThread: 0x7fae624047b0>{number = 1, name = main}-----Invocation

<2>添加到NSOperationQueue

 //1.创建
  NSOperation *op = [[NSInvocationOperation alloc]initWithTarget:self 
selector:@selector(downloadImage:) object:@"Invocation"];
 //2.放到队列里面去
    NSOperationQueue *q = [[NSOperationQueue alloc]init];
  //只要把操作放到队列,会自动异步执行调度方法
    [q addOperation:op];
输出:[1192:55469] <NSThread: 0x7fbe59e45c30>{number = 3, name = (null)}-----Invocation

在number为3,name为空的子线程执行

2.多个NSInvocationOperation

    //队列,GCD里面的并发队列使用最多,所以NSOperation技术直接把GCD里面的并发队列封装起来
  //NSOperationQueue本质就是GCD里面的并发队列
  //操作就是GCD里面异步执行的任务
  NSOperationQueue *q = [[NSOperationQueue alloc]init];
 
  //把多个操作放到队列里面
  for (int i = 0; i < 100; i++) {
    NSOperation *op = [[NSInvocationOperation alloc]initWithTarget:self 
         selector:@selector(downloadImage:) object:[NSString stringWithFormat:@"Invocation%d",i]];
    [q addOperation:op];
  }
输出:
**[1222:58476] <NSThread: 0x7fdc14b0cd20>{number = 7, name = (null)}-----Invocation5
**[1222:58478] <NSThread: 0x7fdc1357e5f0>{number = 9, name = (null)}-----Invocation7
**[1222:58307] <NSThread: 0x7fdc14a06ad0>{number = 3, name = (null)}-----Invocation1
**[1222:58477] <NSThread: 0x7fdc134916e0>{number = 8, name = (null)}-----Invocation6
**[1222:58481] <NSThread: 0x7fdc1357e120>{number = 12, name = (null)}-----Invocation10
**[1222:58475] <NSThread: 0x7fdc14801710>{number = 6, name = (null)}-----Invocation4
**[1222:58480] <NSThread: 0x7fdc13415630>{number = 11, name = (null)}-----Invocation9
**[1222:58306] <NSThread: 0x7fdc13512e20>{number = 4, name = (null)}-----Invocation3
··· ···

线程名与输出均没有规律,很明显就是并发队列。

二、NSBlockOperation

NSBlockOperation的用法与NSInvocationOperation相同,只是创建的方式不同,它不需要去调用方法,而是直接使用代码块,显得更方便。这也使得NSBlockOperation比NSInvocationOperation更加流行

    //跟GCD中的并发队列一样
  NSOperationQueue *q = [[NSOperationQueue alloc]init];
  //跟GCD中的主队列一样
//  NSOperationQueue *q = [NSOperationQueue mainQueue];
  //把多个操作放到队列里面
  for (int i = 0; i < 100; i++) {
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@------%d",[NSThread currentThread],i);
     
   }];
    //把Block操作放到队列
    [q addOperation:op];
  }
  NSLog(@"完成");
并发队列输出结果:
**[1378:72440] <NSThread: 0x7f9cb2603460>{number = 6, name = (null)}------5**
**[1378:72442] <NSThread: 0x7f9cb48106a0>{number = 5, name = (null)}------7**
**[1378:72441] <NSThread: 0x7f9cb242b3e0>{number = 7, name = (null)}------6**
**[1378:72325] <NSThread: 0x7f9cb4851550>{number = 9, name = (null)}------2**
**[1378:72320] <NSThread: 0x7f9cb492be70>{number = 4, name = (null)}------3**
**[1378:72313] <NSThread: 0x7f9cb24077b0>{number = 2, name = (null)}------1**
**[1378:72276] 完成
**[1378:72444] <NSThread: 0x7f9cb481cc40>{number = 11, name = (null)}------9**
**[1378:72326] <NSThread: 0x7f9cb4923fe0>{number = 3, name = (null)}------0**
**[1378:72440] <NSThread: 0x7f9cb2603460>{number = 6, name = (null)}------12**
... ...
主队列输出结果:
**[1417:76086] 完成
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------0**
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------1**
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------2**
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------3**
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------4**
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------5**
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------6**
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------7**
... ...

事实上NSBlockOperation有更简单的使用方法

NSOperationQueue *q = [[NSOperationQueue alloc]init];
 
  for (int i = 0; i < 10; i++) {
   
    [q addOperationWithBlock:^{
      NSLog(@"%@------%d",[NSThread currentThread],i);
    }];
  }

三、线程间通信

主线程到子线程传对象,前面的例子里面已经有了,不再缀述。下面的例子就是回到主线程更新UI。

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; c

NSOperationQueue *q = [[NSOperationQueue alloc]init];

  [q addOperationWithBlock:^{
    NSLog(@"耗时操作--%@",[NSThread currentThread]);
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
      NSLog(@"更新UI-----%@",[NSThread currentThread]);
    }];
  }];

四、NSOperationQueue的一些高级操作

NSOperationQueue支持的高级操作有:队列的挂起,队列的取消,添加操作的依赖关系和设置最大并发数

<1>最大并发数

@property (nonatomic,strong)NSOperationQueue *opQueue;

//重写getter方法实现懒加载
- (NSOperationQueue*)opQueue{
  if (_opQueue == nil) {
    _opQueue = [[NSOperationQueue alloc]init]; 
  }
  return _opQueue;
}


#pragma mark - 高级操作:最大并发数

  //设置最大的并发数量(并非线程的数量)
  self.opQueue.maxConcurrentOperationCount = 2;
  //把多个操作放到队列里面
  for (int i = 0; i < 10; i++) {
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
      [NSThread sleepForTimeInterval:3.0];
      NSLog(@"%@------%d",[NSThread currentThread],i);
     
    }];
    //把Block操作放到队列
    [self.opQueue addOperation:op];
  }

<2>线程的挂起

#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(@"继续");
  }
}

<3>取消队列里的所有操作

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

<4>依赖关系

    /*
  * 例子
  *
  * 1.下载一个小说压缩包
  *  2.解压缩,删除压缩包
  * 3.更新UI
  */
 
  NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"1.下载一个小说压缩包,%@",[NSThread currentThread]);
   
  }];
 
  NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"2.解压缩,删除压缩包,%@",[NSThread currentThread]);
   
  }];
 
  NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"3.更新UI,%@",[NSThread currentThread]);
   
  }];
 
  //指定任务之间的依赖关系 --依赖关系可以跨队列(可以再子线程下载,在主线程更新UI)
 
  [op2 addDependency:op1];
  [op3 addDependency:op2];
//  [op1 addDependency:op3];  一定不能出现循环依赖
 
  //waitUntilFinished  类似GCD中的调度组的通知
  //NO不等待,直接执行输出come here
  //YES等待任务执行完再执行输出come here
  [self.opQueue addOperations:@[op1,op2] waitUntilFinished:YES];
 
 
  //在主线程更新UI
  [[NSOperationQueue mainQueue] addOperation:op3];
  [op3 addDependency:op2];
  NSLog(@"come here");

还有一个NSOperationQueuePriority,队列优先级的概念,因为用的极少,所以这里不做介绍,确实有需要的同学可以自己百度或者查看Documentation and API Reference。

五、三种多线程技术

<1>NSThread

  • 优点:NSThread 比其他两个轻量级,使用简单
  • 缺点:需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁会有一定的系统开销

<2>GCD
GCD 是iOS 4.0以后才出现的并发技术

  • 使用方式:将任务添加到队列(串行/并行(全局)),指定执行任务的方法,(同步(阻塞)/异步 )
  • 拿到主队列:dispatch_get_main_queu()
  • NSOperation无法做到的:1.一次性执行,2.延迟执行,3.调度组(op实现要复杂的多 )

<3>NSOperation
NSOperation iOS2.0的时候就出现了(当时不好用,后来苹果对其进行改造)

  • 使用方式:将操作(异步执行)添加到队列(并发/全局)
  • 拿到主队列:[NSOperationQueue mainQueue] 主队列,任务添加到主队列就会在主线程执行
  • 提供了GCD不好实现的:1.最大并发数,2.暂停和继续,3.取消所有任务,4.依赖关系

GCD是比较底层的封装,我们知道较低层的代码一般性能都是比较高的,相对于NSOperationQueue。所以追求性能,而功能够用的话就可以考虑使用GCD。如果异步操作的过程需要更多的用户交互和被UI显示出来,NSOperationQueue会是一个好选择。如果任务之间没有什么依赖关系,而是需要更高的并发能力,GCD则更有优势。
高德纳的教诲:“在大概97%的时间里,我们应该忘记微小的性能提升。过早优化是万恶之源。”只有Instruments显示有真正的性能提升时才有必要用低级的GCD。


iOS多线程系列之一:多线程基础
iOS多线程系列之二: NSThread
iOS多线程系列之三:GCD
iOS多线程系列之四:NSOperation以及多线程技术比较
参考文章:
[1]:http://www.jianshu.com/users/59cf63472310/latest_articles
[2] http://www.jianshu.com/p/af3c666685ba
[3]http://www.jianshu.com/p/8cc9b347bba7
[4]https://www.cnblogs.com/beckwang0912/p/7136862.html

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

推荐阅读更多精彩内容