iOS-多线程:NSOperation、NSOperationQueue

  1. NSOperation 简介
  2. NSOperation 和 GCD 比较
  3. NSInvocationOperation使用
  4. NSBlockOperation使用
  5. NSOperationQueue使用
  6. 其他方法

操作和操作队列、使用步骤和基本使用方法、控制串行/并发执行、
NSOperation 操作依赖和优先级、线程间的通信、线程同步和线程安全,以及 NSOperation、NSOperationQueue 常用属性和方法归纳。

NSOperation 简介:

NSOperation 是苹果公司对 GCD 的封装,简单易用,完全面向对象,
NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列 。

优点:

  1. 可添加完成的代码块,在操作完成后执行。
  2. 添加操作之间的依赖关系,方便控制执行顺序。
  3. 设定操作执行的优先级。
  4. 可以很方便的取消一个操作的执行。
  5. 使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。

NSOperation 和 GCD 比较:

在 NSOperation、NSOperationQueue 中也有类似的任务(操作)和队列(操作队列)的概念。

操作(Operation):
执行操作的意思,换句话说就是你在线程中执行的那段代码。
在 GCD 中是放在 block 中的。在 NSOperation 中,我们使用 NSOperation 子类 NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。
操作队列(Operation Queues):
这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发、串行。
NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。

操作步骤:

  • NSOperation 单独使用:
    NSOperation只是一个抽象类,不能封装任务,他的两个子类可以封装任务:NSInvocationOperation和NSBlockOperation。或者使用自定义继承自 NSOperation 的子类
    创建一个NSOperation后,start启动,cancel取消。
    把NSOperation添加到NSOperationQueue中自动调用start()方法
    NSBlockOperation通过addExecutionBlock可以给Operation添加多个执行Block。
    单独使用 NSOperation 的情况下系统同步执行操作

  • NSOperation 和 NSOperationQueue
    将要执行的任务封装到一个 NSOperation 对象中。
    将NSOperation添加到一个 NSOperationQueue 对象中,系统会⾃动将NSOperationQueue中的NSOperation取出来,将取出的NSOperation封装的操作放到⼀条新线程中执⾏.
    首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。

NSInvocationOperation使用:

/**
 * 使用子类 NSInvocationOperation
 */
- (void)useInvocationOperation {

    // 1.创建 NSInvocationOperation 对象
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // 2.调用 start 方法开始执行操作
    [op start];
}

/**
 * 任务1
 */
- (void)task1 {
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
    }
}

操作对象默认在主线程中执行(操作是在当前线程执行的,如果在其他线程中执行操作,则打印结果为其他线程。),只有将NSOperation添加到NSOperationQueue队列中才会开启新的线程,才会异步执行操作。如果操作没有放到队列queue中,都是同步执行。(所以上面在主线程中执行)
(比如两个循环AB,A执行完再执行B,不会交叉执行)

// 在其他线程使用子类 NSInvocationOperation
[NSThread detachNewThreadSelector:@selector(useInvocationOperation) toTarget:self withObject:nil];

可以看到:在其他线程中单独使用子类 NSInvocationOperation,操作是在当前调用的其他线程执行的,并没有开启新线程。

NSBlockOperation使用:

/**
 * 使用子类 NSBlockOperation
 */
- (void)useBlockOperation {

    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 2.调用 start 方法开始执行操作
    [op start];
}

(在没有使用 NSOperationQueue、在主线程中单独使用 NSBlockOperation 执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程。)
只要NSBlockOperation封装的操作数 > 1,(就会异步执行操作,会开启新线程。(可以控制串行还是并发)

注意:

NSBlockOperation和NSInvocationOperation 。因为代码是在主线程中调用的,所以打印结果为主线程。如果在其他线程中执行操作,则打印结果为其他线程。

NSBlockOperation 还提供了一个方法 addExecutionBlock:,通过 addExecutionBlock: 就可以为 NSBlockOperation 添加额外的操作。这些操作(包括 blockOperationWithBlock 中的操作)可以在不同的线程中同时(并发)执行。只有当所有相关的操作已经完成执行时,才视为完成。

/**
 * 使用子类 NSBlockOperation
 * 调用方法 AddExecutionBlock:
 */
- (void)useBlockOperationAddExecutionBlock {

    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 2.添加额外的操作
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"5---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"6---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"7---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"8---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 3.调用 start 方法开始执行操作
    [op start];
}

可以看出:使用子类 NSBlockOperation,并调用方法 AddExecutionBlock: 的情况下,blockOperationWithBlock:方法中的操作 和 addExecutionBlock: 中的操作是在不同的线程中异步执行的。而且,这次执行结果中 blockOperationWithBlock:方法中的操作也不是在当前线程(主线程)中执行的。从而印证了blockOperationWithBlock: 中的操作也可能会在其他线程(非当前线程)中执行。

如果添加的操作多的话,blockOperationWithBlock: 中的操作也可能会在其他线程(非当前线程)中执行。这是由系统决定的,并不是说添加到 blockOperationWithBlock: 中的操作一定会在当前线程中执行。
一般情况下,如果一个 NSBlockOperation 对象封装了多个操作。NSBlockOperation 是否开启新线程,取决于操作的个数。如果添加的操作的个数多,就会自动开启新线程。当然开启的线程数是由系统来决定的。

NSOperationQueue使用:

NSOperationQueue 一共有两种队列:主队列、自定义队列。

凡是添加到主队列中的操作,都会放到主线程中执行。NSOperationQueue *queue = [NSOperationQueue mainQueue];

自定义队列同时包含了串行、并发功能。添加到自定义队列中的操作,就会自动放到子线程中执行。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

NSOperationQueue的作⽤:NSOperation可以调⽤start⽅法来执⾏任务,但(分别创建多个)默认是同步执行的
如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
添加操作到NSOperationQueue中,自动执行操作,自动开启线程

  • 两种添加方法:
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block; 
/**
 * 使用 addOperation: 将操作加入到操作队列中
 */
- (void)addOperationToQueue {

    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.创建操作
    // 使用 NSInvocationOperation 创建操作1
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // 使用 NSInvocationOperation 创建操作2
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];

    // 使用 NSBlockOperation 创建操作3
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op3 addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 3.使用 addOperation: 添加所有操作到队列中
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
    [queue addOperation:op3]; // [op3 start]
}

系统自动将NSOperationqueue中的NSOperation对象取出,将其封装的操作放到一条新的线程中执行。上面的代码示例中,一共有四个任务,operation1和operation2分别有一个任务,operation3有两个任务。一共四个任务,开启了四条线程。通过任务执行的时间全部都是273可以看出,这些任务是并行执行的。

使用 NSOperation 子类创建操作,并使用 addOperation: 将操作加入到操作队列后能够开启新线程,进行并发执行。

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

  • (void)addOperationWithBlock:(void (^)(void))block;
    无需先创建操作,在 block 中添加操作,直接将包含操作的 block 加入到队列中。
/**
 * 使用 addOperationWithBlock: 将操作加入到操作队列中
 */

- (void)addOperationWithBlockToQueue {
    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.使用 addOperationWithBlock: 添加操作到队列中
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
}

可以看出:使用 addOperationWithBlock: 将操作加入到操作队列后能够开启新线程,进行并发执行。

NSOperationQueue封装了线程的管理 可以管理队列中任务的并发数量,执行顺序,队列的暂停开启等。使用 addOperationWithBlock: 将操作加入到操作队列后能够开启新线程,进行并发执行。

最大并发数,取消、暂停、恢复,操作优先级,添加任务、添加依赖,添加监听

一、并发数 NSOperationQueue

  • (NSInteger)maxConcurrentOperationCount;
  • (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
    //最大并发操作数 用来控制一个特定队列中可以有多少个操作同时参与并发执行。
    如果没有设置最大并发数,那么并发的个数是由系统内存和CPU决定的
    最大并发数不要乱写(5以内),不要开太多,否则可能会影响UI,让UI变卡。
    最大并发数, 不能设置为0, 否则任务不会被执行
    maxConcurrentOperationCount 控制串行 并发
    maxConcurrentOperationCount 控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。而且一个操作也并非只能在一个线程中运行。= 1 并不是只开一条子线程(通常会开两条子线程,循环回收复用)

二、队列的取消,暂停和恢复 NSOperationQueue

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

任务只要被取消, 就不会再恢复了
取消任务和暂停任务一样, 不会取消当前正在执行的任务, 只能取消还未执行的任务。暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。

1.如果在任务执行的过程中暂停队列中的任务, 那么当前正在执行的任务并不会被暂停, 而是会暂停队列中的下一个任务
2.恢复任务, 是从队列第一个没有被执行过的任务开始恢复
也可以调用NSOperation的- (void)cancel⽅法取消单个操作
比如:在tableview界面,在用户操作UI(如滚动屏幕)的时候,暂停队列(不是取消队列),停止滚动的时候,恢复队列。

//NSOperation

  • (BOOL)isReady; 判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。
  • (BOOL)isFinished; 判断操作是否已经结束。
  • (BOOL)isCancelled; 判断操作是否已经标记为取消。
  • (BOOL)isExecuting; 判断操作是否正在在运行。

三、操作优先级 NSOperation

  • (NSOperationQueuePriority)queuePriority;
  • (void)setQueuePriority:(NSOperationQueuePriority)p;
    优先级高的任务,调用的几率会更大。
    queuePriority 属性决定了进入准备就绪状态下的操作之间的开始执行顺序。并且,优先级不能取代依赖关系。

四、操作依赖 NSOperation

[operationB addDependency:operationA]; // 操作B后执行
注意:不能循环依赖(不能A依赖于B,B又依赖于A)。
注意:一定要在添加到NSOperationQueue之前,进行设置。可添加多个依赖
除了同一quere操作间建立依赖关系,当然也可以在不同queue的NSOperation之间创建依赖关系
通过removeDependency方法来删除依赖对象

五、操作的监听 NSOperation

  • (void (^)(void))completionBlock;

  • (void)setCompletionBlock:(void (^)(void))block;
    在上一个任务执行完后,会执行operation.completionBlock=^{}代码段,且是在当前线程执行。//操作同步
    op1.completionBlock = ^(){
    NSLog(@"任务1执行完毕");

    };

参考:
https://www.jianshu.com/p/4b1d77054b35
https://blog.csdn.net/ssirreplaceable/article/details/53357334
http://www.cocoachina.com/game/20151201/14517.html
http://www.cnblogs.com/wendingding/p/3809042.html
http://www.cnblogs.com/wendingding/p/3809150.html

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

推荐阅读更多精彩内容