iOS 多線程-NSOperation + NSOperationQueue

本文內(nèi)容:
如何使用NSOperation創(chuàng)建任務(wù)、設(shè)置依賴器联、設(shè)置優(yōu)先級
如何使用NSOperationQueue創(chuàng)建隊(duì)列,添加任務(wù)到隊(duì)列千元、控制隊(duì)列串行、并行執(zhí)行任務(wù)
線程間如何通信
如何保證線程安全
iOS多線程demo地址

上文說到iOS 多線程-GCD
這篇文章來講講NSOperation + NSOperationQueue

NSOperation:基于OC語言的API物独,底層是GCD,增加了一些簡單易用的功能官研,面向?qū)ο蟛僮鳎€程的生命周期系統(tǒng)自動管理始花,在開發(fā)中經(jīng)常使用。

1. NSOperation & NSOperationQueue

NSOperation:執(zhí)行的操作浇垦,也就是任務(wù)的意思男韧,不過NSOperation:是一個抽象基類,不能直接使用寡壮。

NSOperation兩種使用方式:

  1. 使用系統(tǒng)定義的NSInvocationOperation
  2. 使用系統(tǒng)定義的NSBlockOperation
  3. 自定義類繼承NSOperation

任務(wù)的狀態(tài)分為以下幾種:

  1. ready : 就緒
  2. cancelled : 取消 (只能取消沒有開始的任務(wù))
  3. executing : 正在執(zhí)行
  4. finished : 完成
  5. asynchronous : 并發(fā)還是非并發(fā)

NSOperationQueue: 存放任務(wù)的隊(duì)列。
隊(duì)列分為:

  1. 主隊(duì)列:添加到主隊(duì)列的任務(wù)悲靴,只能在主線程執(zhí)行癞尚,除addExecutionBlock添加的額外任務(wù),任務(wù)可能在其他線程執(zhí)行(下面有證明)胳徽;
  2. 其他隊(duì)列:添加到其他隊(duì)列的任務(wù)在子線程執(zhí)行。

常用方法:

NSOperation使用addDependency添加依賴往核, 控制任務(wù)是否進(jìn)入就緒狀態(tài)铆铆,從而控制任務(wù)執(zhí)行順序。
NSOperationQueue使用addOperation添加任務(wù)到隊(duì)列谅猾。
NSOperationQueue設(shè)置 maxConcurrentOperationCount(最大操作并發(fā)數(shù)),用來控制一個隊(duì)列中有多少個任務(wù)同時進(jìn)行敬矩,而不是并發(fā)線程的數(shù)量弧岳。
NSOperation使用cancel 取消當(dāng)前任務(wù)
NSOperationQueue使用cancelAllOperations取消當(dāng)前隊(duì)列的任務(wù)
NSOperationQueue使用setSuspended設(shè)置任務(wù)的暫停和恢復(fù)

注意:

  1. 任務(wù)涧卵、隊(duì)列的取消并不代表可以將當(dāng)前的操作立即取消,而是當(dāng)前的操作執(zhí)行完畢之后不再執(zhí)行新的操作
  2. 暫停和取消的區(qū)別就在于:暫停操作之后還可以恢復(fù)操作乐设,繼續(xù)向下執(zhí)行;而取消操作之后介汹,所有的操作就清空了窗价,無法再接著執(zhí)行剩下的操作。

2.使用步驟

  1. 創(chuàng)建任務(wù)
  2. 創(chuàng)建隊(duì)列
  3. 將任務(wù)添加到隊(duì)列中執(zhí)行

2.1創(chuàng)建任務(wù)

2.1.1 NSInvocationOperation

使用NSInvocationOperation創(chuàng)建任務(wù)帝牡,使用start方式開始任務(wù)

- (void)clickNSOperation{
    NSLog(@"主線程");
    
//    1、NSInvocationOperation
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationAction) object:nil];
    [invocationOperation start];
    NSLog(@"end----");
}
- (void)invocationAction{

    for (NSInteger index = 0; index < 3; index ++) {
        NSLog(@"invocation ==== %ld",(long)index);
        [NSThread sleepForTimeInterval:1.0];
    }
}

輸出結(jié)果:

  1. 在主線程中執(zhí)行(因?yàn)槭窃谥骶€程中調(diào)用)
  2. end最后輸出,start方式執(zhí)行任務(wù)是同步執(zhí)行瓷炮,睡眠時阻塞當(dāng)前線程苍狰。
2019-01-03 15:59:44.044995+0800 Thread[5045:65704] 主線程
2019-01-03 15:59:44.045577+0800 Thread[5045:65704] invocation ==== 0
2019-01-03 15:59:45.046159+0800 Thread[5045:65704] invocation ==== 1
2019-01-03 15:59:46.047736+0800 Thread[5045:65704] invocation ==== 2
2019-01-03 15:59:47.048575+0800 Thread[5045:65704] end----


在主線程調(diào)用就在主線程執(zhí)行响牛,那在子線程中調(diào)用會如何呢呀打?

- (void)clickNSOperation{
    NSLog(@"主線程");
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //    1给涕、NSInvocationOperation
        NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationAction) object:nil];
        //start 是同步方式
        [invocationOperation start];
        NSLog(@"end----");
    });
}

輸出結(jié)果:

  1. 任務(wù)在子線程中執(zhí)行(因?yàn)槭亲又骶€程中調(diào)用)
  2. end最后輸出,start方式執(zhí)行任務(wù)是同步執(zhí)行昼榛,睡眠時阻塞當(dāng)前線程偶宫。
2019-01-03 16:01:59.252994+0800 Thread[5075:66813] 主線程
2019-01-03 16:01:59.254415+0800 Thread[5075:66861] invocation ==== 0
2019-01-03 16:02:00.257107+0800 Thread[5075:66861] invocation ==== 1
2019-01-03 16:02:01.259739+0800 Thread[5075:66861] invocation ==== 2
2019-01-03 16:02:02.261961+0800 Thread[5075:66861] end----

由此可見,使用NSInvocationOperation + start方式執(zhí)行任務(wù)唇兑,在哪個線程調(diào)用,就在哪個線程執(zhí)行结耀,執(zhí)行方式是同步執(zhí)行留夜。

2.1.2NSBlockOperation

使用NSBlockOperation創(chuàng)建一個任務(wù)匙铡,并使用start方式開始任務(wù)

- (void)clickNSOperation{
    NSLog(@"主線程");
    
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger index = 0; index < 3; index ++) {
            NSLog(@"1 ==== %ld",(long)index);
            [NSThread sleepForTimeInterval:1.0];
        }
    }];
     [blockOperation start];
     NSLog(@"end----");
}

輸出結(jié)果:

  1. 在主線程中執(zhí)行(因?yàn)槭窃谥骶€程中調(diào)用)
  2. end最后輸出,start方式執(zhí)行任務(wù)是同步執(zhí)行碍粥,睡眠時阻塞當(dāng)前線程鳖眼。
2019-01-03 16:41:28.661599+0800 Thread[5506:85345] 主線程
2019-01-03 16:41:28.662055+0800 Thread[5506:85345] 1 ==== 0
2019-01-03 16:41:29.663436+0800 Thread[5506:85345] 1 ==== 1
2019-01-03 16:41:30.664878+0800 Thread[5506:85345] 1 ==== 2
2019-01-03 16:41:31.666310+0800 Thread[5506:85345] end----

執(zhí)行一個任務(wù)時,省略在子線程調(diào)用start執(zhí)行任務(wù)钦讳,因?yàn)榻Y(jié)果和NSInvocationOperation一樣在哪個線程調(diào)用枕荞,就在哪個線程同步執(zhí)行狮惜。

NSBlockOperation還有一個方法addExecutionBlock用來添加額外任務(wù),可用于執(zhí)行多個任務(wù)剔蹋,比如說想同時執(zhí)行2個任務(wù)。

- (void)clickNSOperation{
    NSLog(@"主線程");
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger index = 0; index < 3; index ++) {
            NSLog(@"1 ==== %ld",(long)index);
            [NSThread sleepForTimeInterval:1.0];
        }
    }];
    [blockOperation addExecutionBlock:^{
        for (NSInteger index = 0; index < 3; index ++) {
            NSLog(@"2 ==== %ld",(long)index);
            [NSThread sleepForTimeInterval:1.0];
        }
    }];

    [blockOperation start];
    NSLog(@"end----");

}

輸出結(jié)果:

  1. 任務(wù)1在主線程執(zhí)行,任務(wù)2在子線程執(zhí)行脱篙。由此證明,addExecutionBlock添加的任務(wù)作谭,可能在其他子線程執(zhí)行赡艰。
  2. end最后輸出芹血,start方式執(zhí)行任務(wù)是同步執(zhí)行珠叔,睡眠時阻塞當(dāng)前線程虱咧。
2019-01-03 16:47:02.219309+0800 Thread[5552:87869] 主線程
2019-01-03 16:47:02.219847+0800 Thread[5552:87896] 1 ==== 0
2019-01-03 16:47:02.219870+0800 Thread[5552:87869] 2 ==== 0
2019-01-03 16:47:03.221204+0800 Thread[5552:87896] 1 ==== 1
2019-01-03 16:47:03.221214+0800 Thread[5552:87869] 2 ==== 1
2019-01-03 16:47:04.222522+0800 Thread[5552:87896] 1 ==== 2
2019-01-03 16:47:04.222522+0800 Thread[5552:87869] 2 ==== 2
2019-01-03 16:47:05.223986+0800 Thread[5552:87869] end----

使用addExecutionBlock方式執(zhí)行多個任務(wù)時表伦,會開啟線程鱼填,具體開啟幾個線程是由系統(tǒng)決定的。

2.1.3 自定義類繼承NSOperation

創(chuàng)建一個類繼承自NSOperation稽寒,這里創(chuàng)建的是XXOperation

#import "XXOperation.h"

@interface XXOperation ()
@property (nonatomic , copy) NSString *operName;
@property (nonatomic , assign) BOOL over;

@end

@implementation XXOperation

- (instancetype)initWithName:(NSString *)operName{
    if (self = [super init]) {
        self.operName = operName;
    }
    return self;
}

- (void)main{
    for (NSInteger index = 0; index < 3; index ++) {
        NSLog(@"index ==== %d,operName = %@",index,self.operName);
        [NSThread sleepForTimeInterval:1.0];
    }
  }

使用start方式調(diào)用

- (void)clickNSOperation{
    NSLog(@"主線程");
    XXOperation *operationA = [[XXOperation alloc]initWithName:@"operationA"];
    [operationA start];
  NSLog(@"end---");
}
 
    

輸出結(jié)果:

  1. 在主線程中執(zhí)行(因?yàn)槭窃谥骶€程中調(diào)用)
  2. end最后輸出,start方式執(zhí)行任務(wù)是同步執(zhí)行,睡眠時阻塞當(dāng)前線程。
2019-01-03 16:20:16.400576+0800 Thread[5270:75970] 主線程
2019-01-03 16:20:16.401009+0800 Thread[5270:75970] index ==== 0,operName = operationA
2019-01-03 16:20:17.401740+0800 Thread[5270:75970] index ==== 1,operName = operationA
2019-01-03 16:20:18.402675+0800 Thread[5270:75970] index ==== 2,operName = operationA
2019-01-03 16:20:19.404857+0800 Thread[5270:75970] end---

自定義類繼承NSOperation在子線程中調(diào)用任務(wù),這里就省略了互站,
因?yàn)榻Y(jié)果和NSInvocationOperationNSBlockOperation執(zhí)行一個任務(wù)時一樣冤今,在哪個線程調(diào)用闺兢,就在哪個線程同步執(zhí)行任務(wù)。

但是我們常見的需求中是異步并發(fā)執(zhí)行,如何實(shí)現(xiàn)異步并發(fā)執(zhí)行呢屋谭?我們需要使用NSOperation + NSOperationQueue實(shí)現(xiàn)異步并發(fā)脚囊。

隊(duì)列又分為兩種,添加到主隊(duì)列的任務(wù)桐磁,只能在主線程執(zhí)行悔耘,除addExecutionBlock添加的額外任務(wù),可能在其他線程執(zhí)行我擂。添加到其他隊(duì)列的任務(wù)在子線程執(zhí)行衬以。

由此可見,使用 NSOperation + 其他隊(duì)列是較佳方法校摩,既不阻塞主線程看峻,又能執(zhí)行其他任務(wù)。

2.2創(chuàng)建隊(duì)列

2.2.1主隊(duì)列

//獲取主隊(duì)列
   NSOperationQueue *queue = [NSOperationQueue mainQueue];

2.2.2其他隊(duì)列

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

3.添加任務(wù)到隊(duì)列

  1. addOperation:(NSOperation *)op: 直接添加一個 NSOperation操作
  2. addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait : 添加一組操作,wait標(biāo)志是否阻塞當(dāng)前線程直到所有操作結(jié)束
  3. addOperationWithBlock:(void (^)(void))block:將block中的操作加入隊(duì)列

這里使用addOperation方式添加任務(wù)到隊(duì)列衙吩,并且不設(shè)置maxConcurrentOperationCount

- (void)clickNSOperation{
    XXOperation *operationA = [[XXOperation alloc]initWithName:@"operationA"];
    XXOperation *operationB = [[XXOperation alloc]initWithName:@"operationB"];
    XXOperation *operationC = [[XXOperation alloc]initWithName:@"operationC"];
    XXOperation *operationD = [[XXOperation alloc]initWithName:@"operationD"];
    if (!self.operationQueue) {
        self.operationQueue = [[NSOperationQueue alloc]init];
    }
    [self.operationQueue addOperation:operationA];
    [self.operationQueue addOperation:operationB];
    [self.operationQueue addOperation:operationC];
    [self.operationQueue addOperation:operationD];
    
}

輸出結(jié)果:

  1. 在多個子線程中執(zhí)行互妓,并發(fā)執(zhí)行;
  2. end在任務(wù)之前輸出坤塞,異步執(zhí)行冯勉。
2019-01-03 17:58:51.841334+0800 Thread[6257:118424] 主線程
2019-01-03 17:58:51.841926+0800 Thread[6257:118424] end---
2019-01-03 17:58:51.841962+0800 Thread[6257:118471] index ==== 0,operName = operationC
2019-01-03 17:58:51.841962+0800 Thread[6257:118472] index ==== 0,operName = operationD
2019-01-03 17:58:51.841963+0800 Thread[6257:118473] index ==== 0,operName = operationB
2019-01-03 17:58:51.841975+0800 Thread[6257:118474] index ==== 0,operName = operationA
2019-01-03 17:58:52.847300+0800 Thread[6257:118471] index ==== 1,operName = operationC
2019-01-03 17:58:52.847303+0800 Thread[6257:118472] index ==== 1,operName = operationD
2019-01-03 17:58:52.847300+0800 Thread[6257:118473] index ==== 1,operName = operationB
2019-01-03 17:58:52.847303+0800 Thread[6257:118474] index ==== 1,operName = operationA
2019-01-03 17:58:53.848148+0800 Thread[6257:118473] index ==== 2,operName = operationB
2019-01-03 17:58:53.848148+0800 Thread[6257:118474] index ==== 2,operName = operationA
2019-01-03 17:58:53.848148+0800 Thread[6257:118472] index ==== 2,operName = operationD
2019-01-03 17:58:53.848173+0800 Thread[6257:118471] index ==== 2,operName = operationC

4.控制串行、并行

NSOperationQueue通過設(shè)置maxConcurrentOperationCount來實(shí)現(xiàn)任務(wù)是串行還是并行摹芙。
maxConcurrentOperationCount: 最大操作并發(fā)數(shù)灼狰,用來控制一個隊(duì)列中有多少個任務(wù)同時進(jìn)行,而不是并發(fā)線程的數(shù)量瘫辩。

在NSOperation.h里面伏嗜,可以看見 maxConcurrentOperationCount默認(rèn)值為-1

static const NSInteger NSOperationQueueDefaultMaxConcurrentOperationCount = -1;
  1. maxConcurrentOperationCount默認(rèn)值為-1,不進(jìn)行限制伐厌,可以并行執(zhí)行;
  2. maxConcurrentOperationCount = 1為串行隊(duì)列承绸,只能串行執(zhí)行;
  3. maxConcurrentOperationCount > 1為并行隊(duì)列挣轨,可以并行執(zhí)行军熏,但是設(shè)置的值不能超過系統(tǒng)限制的最大值,如果超過系統(tǒng)限制的最大值卷扮,設(shè)置的值 = 系統(tǒng)限制的最大值荡澎;

此時將maxConcurrentOperationCount 設(shè)置為1

- (void)clickNSOperation{
    NSLog(@"主線程");
    XXOperation *operationA = [[XXOperation alloc]initWithName:@"operationA"];
    XXOperation *operationB = [[XXOperation alloc]initWithName:@"operationB"];
    XXOperation *operationC = [[XXOperation alloc]initWithName:@"operationC"];
    XXOperation *operationD = [[XXOperation alloc]initWithName:@"operationD"];
    if (!self.operationQueue) {
        self.operationQueue = [[NSOperationQueue alloc]init];
    }
    self.operationQueue.maxConcurrentOperationCount = 1;//值 = 1,串行隊(duì)列晤锹,
    [self.operationQueue addOperation:operationA];
    [self.operationQueue addOperation:operationB];
    [self.operationQueue addOperation:operationC];
    [self.operationQueue addOperation:operationD];
    NSLog(@"end---");
}

輸出結(jié)果:

  1. end在任務(wù)之前輸出摩幔,異步執(zhí)行;
  2. 任務(wù)在多個子線程中執(zhí)行鞭铆;
  3. 任務(wù)一個接著一個串行執(zhí)行或衡,最后呈現(xiàn)結(jié)果就是異步串行。

由此證明:maxConcurrentOperationCount,用來控制一個隊(duì)列中有多少個任務(wù)同時進(jìn)行封断,而不是并發(fā)線程的數(shù)量斯辰。

2019-01-03 18:14:37.042902+0800 Thread[6370:123969] 主線程
2019-01-03 18:14:37.043486+0800 Thread[6370:123969] end---
2019-01-03 18:14:37.043535+0800 Thread[6370:124020] index ==== 0,operName = operationA
2019-01-03 18:14:38.043839+0800 Thread[6370:124020] index ==== 1,operName = operationA
2019-01-03 18:14:39.049215+0800 Thread[6370:124020] index ==== 2,operName = operationA
2019-01-03 18:14:40.054252+0800 Thread[6370:124021] index ==== 0,operName = operationB
2019-01-03 18:14:41.059064+0800 Thread[6370:124021] index ==== 1,operName = operationB
2019-01-03 18:14:42.061843+0800 Thread[6370:124021] index ==== 2,operName = operationB
2019-01-03 18:14:43.067389+0800 Thread[6370:124021] index ==== 0,operName = operationC
2019-01-03 18:14:44.072883+0800 Thread[6370:124021] index ==== 1,operName = operationC
2019-01-03 18:14:45.074068+0800 Thread[6370:124021] index ==== 2,operName = operationC
2019-01-03 18:14:46.076217+0800 Thread[6370:124020] index ==== 0,operName = operationD
2019-01-03 18:14:47.079396+0800 Thread[6370:124020] index ==== 1,operName = operationD
2019-01-03 18:14:48.084846+0800 Thread[6370:124020] index ==== 2,operName = operationD

將最大并發(fā)數(shù)設(shè)置為4

self.operationQueue.maxConcurrentOperationCount = 4;//值 = 4,并行隊(duì)列

輸出結(jié)果:

  1. end在任務(wù)之前輸出坡疼,異步執(zhí)行彬呻;
  2. 任務(wù)在多個子線程中執(zhí)行;
  3. 任務(wù)并發(fā)執(zhí)行柄瑰,最后呈現(xiàn)結(jié)果就是實(shí)現(xiàn)異步并發(fā)執(zhí)行(最完美狀態(tài))
2019-01-03 18:19:58.120798+0800 Thread[6424:126327] 主線程
2019-01-03 18:19:58.121325+0800 Thread[6424:126327] end---
2019-01-03 18:19:58.121362+0800 Thread[6424:126374] index ==== 0,operName = operationB
2019-01-03 18:19:58.121339+0800 Thread[6424:126371] index ==== 0,operName = operationA
2019-01-03 18:19:58.121362+0800 Thread[6424:126373] index ==== 0,operName = operationD
2019-01-03 18:19:58.121371+0800 Thread[6424:126372] index ==== 0,operName = operationC
2019-01-03 18:19:59.122232+0800 Thread[6424:126372] index ==== 1,operName = operationC
2019-01-03 18:19:59.122231+0800 Thread[6424:126371] index ==== 1,operName = operationA
2019-01-03 18:19:59.122287+0800 Thread[6424:126373] index ==== 1,operName = operationD
2019-01-03 18:19:59.122231+0800 Thread[6424:126374] index ==== 1,operName = operationB
2019-01-03 18:20:00.127789+0800 Thread[6424:126371] index ==== 2,operName = operationA
2019-01-03 18:20:00.127789+0800 Thread[6424:126374] index ==== 2,operName = operationB
2019-01-03 18:20:00.127791+0800 Thread[6424:126372] index ==== 2,operName = operationC
2019-01-03 18:20:00.127791+0800 Thread[6424:126373] index ==== 2,operName = operationD

5. 操作依賴

操作依賴 : 控制任務(wù)是否進(jìn)入就緒狀態(tài)闸氮,從而控制任務(wù)執(zhí)行順序

  1. - (void)addDependency:(NSOperation *)op;:添加依賴
  2. (void)removeDependency:(NSOperation *)op : 刪除依賴
  3. @property (readonly, copy) NSArray<NSOperation *> *dependencies:返回當(dāng)前依賴數(shù)組

上一個例子中,有4個任務(wù)狱意,分別是A湖苞、B、C详囤、D 财骨,設(shè)置D依賴于A ,A 依賴于C藏姐,C 依賴于B隆箩,千萬不能再設(shè)置B依賴于D,不然就是死循環(huán)了羔杨。

- (void)clickNSOperation{
    NSLog(@"主線程");
    XXOperation *operationA = [[XXOperation alloc]initWithName:@"operationA"];
    XXOperation *operationB = [[XXOperation alloc]initWithName:@"operationB"];
    XXOperation *operationC = [[XXOperation alloc]initWithName:@"operationC"];
    XXOperation *operationD = [[XXOperation alloc]initWithName:@"operationD"];
    if (!self.operationQueue) {
        self.operationQueue = [[NSOperationQueue alloc]init];
    }
//    self.operationQueue.maxConcurrentOperationCount = 1;//值 = 1捌臊,串行隊(duì)列
    self.operationQueue.maxConcurrentOperationCount = 4;//值 = 4,并行隊(duì)列

    // 設(shè)置D依賴于A 兜材,A 依賴于C理澎,C 依賴于B
    [operationD addDependency:operationA];
    [operationA addDependency:operationC];
    [operationC addDependency:operationB];
    
    [self.operationQueue addOperation:operationA];
    [self.operationQueue addOperation:operationB];
    [self.operationQueue addOperation:operationC];
    [self.operationQueue addOperation:operationD];
     NSLog(@"end---");
}

輸出結(jié)果:

  1. end在任務(wù)之前輸出,異步執(zhí)行曙寡;
  2. 任務(wù)執(zhí)行順序B糠爬、C、A举庶、D执隧。
    本來添加到隊(duì)列的任務(wù)順序是A、B户侥、C镀琉、D,執(zhí)行A時蕊唐,A依賴于C屋摔,C還未執(zhí)行結(jié)束,A處于未就緒狀態(tài)替梨,只有等C執(zhí)行結(jié)束后钓试,A才能處于就緒狀態(tài)署尤,才能執(zhí)行任務(wù)。B沒有依賴亚侠,直接執(zhí)行,同理C依賴于B俗扇,硝烂,D依賴于A,所以執(zhí)行順序是B铜幽、C滞谢、A、D
2019-01-04 16:36:29.830947+0800 Thread[19747:188253] 主線程
2019-01-04 16:36:29.831568+0800 Thread[19747:188253] end---
2019-01-04 16:36:29.831603+0800 Thread[19747:188290] index ==== 0,operName = operationB
2019-01-04 16:36:30.832856+0800 Thread[19747:188290] index ==== 1,operName = operationB
2019-01-04 16:36:31.837187+0800 Thread[19747:188290] index ==== 2,operName = operationB
2019-01-04 16:36:32.842000+0800 Thread[19747:188289] index ==== 0,operName = operationC
2019-01-04 16:36:33.846089+0800 Thread[19747:188289] index ==== 1,operName = operationC
2019-01-04 16:36:34.851604+0800 Thread[19747:188289] index ==== 2,operName = operationC
2019-01-04 16:36:35.856626+0800 Thread[19747:188290] index ==== 0,operName = operationA
2019-01-04 16:36:36.859237+0800 Thread[19747:188290] index ==== 1,operName = operationA
2019-01-04 16:36:37.862654+0800 Thread[19747:188290] index ==== 2,operName = operationA
2019-01-04 16:36:38.866634+0800 Thread[19747:188289] index ==== 0,operName = operationD
2019-01-04 16:36:39.870859+0800 Thread[19747:188289] index ==== 1,operName = operationD
2019-01-04 16:36:40.873716+0800 Thread[19747:188289] index ==== 2,operName = operationD

6.優(yōu)先級

queuePriority除抛,設(shè)置隊(duì)列中的任務(wù)的優(yōu)先級狮杨,目的是:控制進(jìn)入就緒狀態(tài)的任務(wù)的執(zhí)行順序。

沒有設(shè)置優(yōu)先級默認(rèn)是NSOperationQueuePriorityNormal到忽,幾個優(yōu)先級有以下幾個選項(xiàng):

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

什么樣的任務(wù)處于就緒狀態(tài)呢橄教?

添加到隊(duì)列中,并且依賴關(guān)系都已經(jīng)滿足喘漏,比如上面A依賴于C护蝶,C還未執(zhí)行結(jié)束,A就處于未就緒狀態(tài)翩迈,C執(zhí)行結(jié)束后持灰,A才處于就緒狀態(tài)。

以下幾種條件负饲,都是在maxConcurrentOperationCount = 1的情況下堤魁,剔除并發(fā)引起的問題。

  1. 如果隊(duì)列中的任務(wù)都處于就緒狀態(tài)下返十,并且都沒有設(shè)置優(yōu)先級妥泉,都是默認(rèn)優(yōu)先級的時候,任務(wù)的執(zhí)行順序吧慢,就是先添加到隊(duì)列里面的任務(wù)先執(zhí)行涛漂。(控制串行、并行的第一個例子可以證明)

  2. 如果隊(duì)列中的任務(wù)都處于就緒狀態(tài)下检诗,并且設(shè)置優(yōu)先級的時候匈仗,任務(wù)的執(zhí)行順序,就是優(yōu)先級越高的任務(wù)先執(zhí)行逢慌。

  3. 如果隊(duì)列中的任務(wù)有處于就緒狀態(tài)的悠轩,有未處于就緒狀態(tài)的,就緒狀態(tài)的優(yōu)先級低攻泼,未就緒狀態(tài)的優(yōu)先級高火架,此時還是執(zhí)行就緒狀態(tài)優(yōu)先級低的操作鉴象,因?yàn)橐蕾囮P(guān)系控制任務(wù)是否進(jìn)入就緒狀態(tài),從而控制任務(wù)執(zhí)行順序何鸡,而優(yōu)先級則是控制進(jìn)入就緒狀態(tài)任務(wù)的執(zhí)行順序纺弊。

情況2的實(shí)現(xiàn)代碼

- (void)clickNSOperation{
    NSLog(@"主線程");
    XXOperation *operationA = [[XXOperation alloc]initWithName:@"operationA"];
    XXOperation *operationB = [[XXOperation alloc]initWithName:@"operationB"];
    XXOperation *operationC = [[XXOperation alloc]initWithName:@"operationC"];
    XXOperation *operationD = [[XXOperation alloc]initWithName:@"operationD"];
    if (!self.operationQueue) {
        self.operationQueue = [[NSOperationQueue alloc]init];
    }
   self.operationQueue.maxConcurrentOperationCount = 1;//值 = 1,串行隊(duì)列
//    self.operationQueue.maxConcurrentOperationCount = 4;//值 = 4骡男,并行隊(duì)列
   //設(shè)置優(yōu)先級
    operationA.queuePriority = NSOperationQueuePriorityVeryHigh;
    operationB.queuePriority = NSOperationQueuePriorityVeryHigh;
    operationC.queuePriority = NSOperationQueuePriorityHigh;
    operationD.queuePriority = NSOperationQueuePriorityVeryHigh;
    [self.operationQueue addOperation:operationA];
    [self.operationQueue addOperation:operationB];
    [self.operationQueue addOperation:operationC];
    [self.operationQueue addOperation:operationD];
     NSLog(@"end---");

輸出結(jié)果:任務(wù)執(zhí)行順序A淆游、B、D隔盛、C犹菱,跟任務(wù)優(yōu)先級關(guān)系成正比,優(yōu)先級越高吮炕,越先執(zhí)行腊脱,C優(yōu)先級比D低,所以D先執(zhí)行龙亲。

2019-01-07 10:58:02.635752+0800 Thread[1130:25079] index ==== 0,operName = operationA
2019-01-07 10:58:03.641122+0800 Thread[1130:25079] index ==== 1,operName = operationA
2019-01-07 10:58:04.645798+0800 Thread[1130:25079] index ==== 2,operName = operationA
2019-01-07 10:58:05.651342+0800 Thread[1130:25076] index ==== 0,operName = operationB
2019-01-07 10:58:06.651660+0800 Thread[1130:25076] index ==== 1,operName = operationB
2019-01-07 10:58:07.656731+0800 Thread[1130:25076] index ==== 2,operName = operationB
2019-01-07 10:58:08.661404+0800 Thread[1130:25079] index ==== 0,operName = operationD
2019-01-07 10:58:09.666161+0800 Thread[1130:25079] index ==== 1,operName = operationD
2019-01-07 10:58:10.668855+0800 Thread[1130:25079] index ==== 2,operName = operationD
2019-01-07 10:58:11.672342+0800 Thread[1130:25076] index ==== 0,operName = operationC
2019-01-07 10:58:12.676247+0800 Thread[1130:25076] index ==== 1,operName = operationC
2019-01-07 10:58:13.677245+0800 Thread[1130:25076] index ==== 2,operName = operationC

情況3的實(shí)現(xiàn)代碼

- (void)clickNSOperation{
    NSLog(@"主線程");
    XXOperation *operationA = [[XXOperation alloc]initWithName:@"operationA"];
    XXOperation *operationB = [[XXOperation alloc]initWithName:@"operationB"];
    XXOperation *operationC = [[XXOperation alloc]initWithName:@"operationC"];
    XXOperation *operationD = [[XXOperation alloc]initWithName:@"operationD"];
    if (!self.operationQueue) {
        self.operationQueue = [[NSOperationQueue alloc]init];
    }
   self.operationQueue.maxConcurrentOperationCount = 1;//值 = 1陕凹,串行隊(duì)列
//    self.operationQueue.maxConcurrentOperationCount = 4;//值 = 4,并行隊(duì)列
     // 設(shè)置D依賴于A 俱笛,A 依賴于C捆姜,C 依賴于B
    [operationD addDependency:operationA];
    [operationA addDependency:operationC];
    [operationC addDependency:operationB];
     //設(shè)置優(yōu)先級
    operationA.queuePriority = NSOperationQueuePriorityVeryHigh;
    operationB.queuePriority = NSOperationQueuePriorityVeryHigh;
    operationC.queuePriority = NSOperationQueuePriorityHigh;
    operationD.queuePriority = NSOperationQueuePriorityVeryHigh;

    [self.operationQueue addOperation:operationA];
    [self.operationQueue addOperation:operationB];
    [self.operationQueue addOperation:operationC];
    [self.operationQueue addOperation:operationD];
     NSLog(@"end---");

輸出結(jié)果:
如果只設(shè)置依賴關(guān)系,不設(shè)置優(yōu)先級迎膜,執(zhí)行順序是B泥技、C、A磕仅、D
如果不設(shè)置依賴關(guān)系珊豹,只設(shè)置優(yōu)先級,執(zhí)行順序是A榕订、B店茶、D、C
同時設(shè)置依賴關(guān)系和優(yōu)先級劫恒,執(zhí)行順序依舊是B贩幻、C、A两嘴、D丛楚,因?yàn)橐蕾囮P(guān)系控制任務(wù)是否進(jìn)入就緒狀態(tài),從而控制任務(wù)執(zhí)行順序憔辫,而優(yōu)先級則是控制進(jìn)入就緒狀態(tài)任務(wù)的執(zhí)行順序趣些。因?yàn)镈依賴于A ,A 依賴于C贰您,C 依賴于B坏平,所以此時就緒狀態(tài)決定執(zhí)行順序拢操。

2019-01-07 11:18:25.281981+0800 Thread[1162:31573] 主線程
2019-01-07 11:18:25.282591+0800 Thread[1162:31573] end---
2019-01-07 11:18:25.282638+0800 Thread[1162:31615] index ==== 0,operName = operationB
2019-01-07 11:18:26.287156+0800 Thread[1162:31615] index ==== 1,operName = operationB
2019-01-07 11:18:27.292264+0800 Thread[1162:31615] index ==== 2,operName = operationB
2019-01-07 11:18:28.297512+0800 Thread[1162:31615] index ==== 0,operName = operationC
2019-01-07 11:18:29.301186+0800 Thread[1162:31615] index ==== 1,operName = operationC
2019-01-07 11:18:30.302530+0800 Thread[1162:31615] index ==== 2,operName = operationC
2019-01-07 11:18:31.304189+0800 Thread[1162:31615] index ==== 0,operName = operationA
2019-01-07 11:18:32.304707+0800 Thread[1162:31615] index ==== 1,operName = operationA
2019-01-07 11:18:33.308931+0800 Thread[1162:31615] index ==== 2,operName = operationA
2019-01-07 11:18:34.309684+0800 Thread[1162:31615] index ==== 0,operName = operationD
2019-01-07 11:18:35.313671+0800 Thread[1162:31615] index ==== 1,operName = operationD
2019-01-07 11:18:36.317910+0800 Thread[1162:31615] index ==== 2,operName = operationD

7.線程間的通信

在iOS開發(fā)工程中,我們一般在主線程中進(jìn)行UI刷新舶替,如:點(diǎn)擊令境、拖拽、滾動事件顾瞪,耗時操作放在其他線程中展父,而當(dāng)耗時操作結(jié)束后,回到主線程玲昧,就需要用到線程間的通信

- (void)clickNSOperation{
    NSLog(@"主線程");
    self.operationQueue = [[NSOperationQueue alloc]init];
    [self.operationQueue addOperationWithBlock:^{
        for (NSInteger index = 0; index < 3; index ++) {
            NSLog(@"invocation ==== %ld",(long)index);
            [NSThread sleepForTimeInterval:1.0];
        }
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"回到主線程");
            NSLog(@"end---");
        }];
    }];
}

輸出結(jié)果:完成耗時操作后,回到了主線程

2019-01-07 13:59:09.784407+0800 Thread[1449:46686] 主線程
2019-01-07 13:59:09.785071+0800 Thread[1449:46730] invocation ==== 0
2019-01-07 13:59:10.789103+0800 Thread[1449:46730] invocation ==== 1
2019-01-07 13:59:11.793907+0800 Thread[1449:46730] invocation ==== 2
2019-01-07 13:59:12.796989+0800 Thread[1449:46686] 回到主線程
2019-01-07 13:59:12.797208+0800 Thread[1449:46686] end---

8.線程安全與線程同步

線程安全:在多個線程中同時訪問并操作同一對象篮绿,運(yùn)行結(jié)果與預(yù)期的值相同就是線程安全孵延。線程安全問題都是由全局變量靜態(tài)變量引起的,若每個線程中對全局變量亲配、靜態(tài)變量只有讀操作尘应,而無寫操作,一般來說吼虎,這個全局變量是線程安全的潘悼;若有多個線程同時執(zhí)行寫操作淋硝,一般都需要考慮線程同步,否則的話就可能影響線程安全。

線程同步:可理解為線程A和B一塊配合粱锐,A執(zhí)行到一定程度時要依靠B的某個結(jié)果,于是停下來鸠姨,示意B運(yùn)行择膝;B依言執(zhí)行,再將結(jié)果給A油湖;A再繼續(xù)操作巍扛。

例子還是用之前的賣票例子,兩個站點(diǎn)賣票,創(chuàng)建一個TicketManager乏德,在外面調(diào)用startToSale方法撤奸,TicketManager里面sale方法加鎖,保證線程安全喊括。

//  TicketManager.m
//  Thread
//
//  Created by Summer on 2018/12/25.
//  Copyright ? 2018 Summer. All rights reserved.
//

#import "TicketManager.h"


#define Total 50

@interface TicketManager ()

@property (nonatomic,assign) NSInteger tickets;//剩余票數(shù)
@property (nonatomic,assign) NSInteger saleCount;//賣出票數(shù)
//使用NSThread
//@property (nonatomic,strong) NSThread *threadBJ;//北京票點(diǎn)
//@property (nonatomic,strong) NSThread *threadSH;//上海票點(diǎn)
//使用NSInvocationOperation + NSOperationQueue
@property (nonatomic , strong) NSInvocationOperation *operationBJ;
@property (nonatomic , strong) NSInvocationOperation *operationSH;
@property (nonatomic , strong)  NSOperationQueue *queue;
/*
 NSLock胧瓜、NSConditionLock、NSRecursiveLock瘾晃、NSCondition加鎖方式都一樣贷痪,都是實(shí)現(xiàn)NSLocking協(xié)議

 */
@property (nonatomic,strong) NSCondition *condition;
@property (nonatomic , strong) dispatch_semaphore_t semaphore;

@end

@implementation TicketManager

- (instancetype)init{
    self = [super init];
    if (self) {
        _condition = [[NSCondition alloc]init];
        _semaphore = dispatch_semaphore_create(1);
        
        _tickets = Total;
//        _threadBJ = [[NSThread alloc]initWithTarget:self selector:@selector(sale) object:nil];
//        _threadSH = [[NSThread alloc]initWithTarget:self selector:@selector(sale) object:nil];
        
//        [_threadBJ setName:@"北京"];
//        [_threadSH setName:@"上海"];
        
        
        _operationBJ = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(sale) object:nil];
        [_operationBJ setName:@"北京"];
        
        _operationSH = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(sale) object:nil];
        [_operationSH setName:@"上海"];
    }
    return self;
}

- (void)sale{
    while (1) {
        //1、synchronized
        @synchronized (self) {
            if (self.tickets > 0 ) {
                [NSThread sleepForTimeInterval:0.1];
                self.tickets --;
                self.saleCount = Total - self.tickets;
                NSLog(@"%@ , 賣出 = %ld蹦误,剩余= %ld",[NSThread currentThread],(long)self.saleCount,(long)self.tickets);
            }else{
                break;//一定要break劫拢,不然就會死循環(huán)
            }
        }
//        2肉津、NSCondition
//        [self.condition lock];
//        if (self.tickets > 0 ) {
//            [NSThread sleepForTimeInterval:0.1];
//            self.tickets --;
//            self.saleCount = Total - self.tickets;
//            NSLog(@"%@ , 賣出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
//        }else{
//            break;
//        }
//        [self.condition unlock];
//
        //3舱沧、dispatch_semaphore方式
//        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
//        if (self.tickets > 0 ) {
//            [NSThread sleepForTimeInterval:0.1];
//            self.tickets --;
//            self.saleCount = Total - self.tickets;
//            NSLog(@"%@ , 賣出 = %d妹沙,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
//        }else{
//            dispatch_semaphore_signal(self.semaphore);
//            break;
//        }
//        dispatch_semaphore_signal(self.semaphore);
    }
}

- (void)startToSale{
//    [_threadSH start];
//    [_threadBJ start];
    
    _queue = [[NSOperationQueue alloc]init];
    [_queue addOperation:_operationBJ];
    [_queue addOperation:_operationSH];
    
}

輸出結(jié)果:正常賣票

省略一部分
2019-01-07 14:46:23.488836+0800 Thread[1622:63417]  , 賣出 = 45,剩余= 5
2019-01-07 14:46:23.592623+0800 Thread[1622:63417]  , 賣出 = 46熟吏,剩余= 4
2019-01-07 14:46:23.695578+0800 Thread[1622:63417]  , 賣出 = 47距糖,剩余= 3
2019-01-07 14:46:23.798669+0800 Thread[1622:63417]  , 賣出 = 48,剩余= 2
2019-01-07 14:46:23.903085+0800 Thread[1622:63417]  , 賣出 = 49牵寺,剩余= 1
2019-01-07 14:46:24.007916+0800 Thread[1622:63417]  , 賣出 = 50悍引,剩余= 0

到此NSOperation + NSOperationQueue就結(jié)束了,多線程系列也結(jié)束了帽氓,2019繼續(xù)加油~~

參考博客:
iOS 多線程:『NSOperation趣斤、NSOperationQueue』詳盡總結(jié)
iOS多線程慕課網(wǎng)視頻

文章鏈接:
iOS 多線程- pThread和NSThread
iOS 多線程-GCD

喜歡就點(diǎn)個贊吧????
有錯之處,還請指出黎休,感謝????

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浓领,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子势腮,更是在濱河造成了極大的恐慌联贩,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捎拯,死亡現(xiàn)場離奇詭異泪幌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)署照,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門座菠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人藤树,你說我怎么就攤上這事浴滴。” “怎么了岁钓?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵升略,是天一觀的道長。 經(jīng)常有香客問我屡限,道長品嚣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任钧大,我火速辦了婚禮翰撑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘啊央。我一直安慰自己眶诈,他們只是感情好涨醋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著逝撬,像睡著了一般浴骂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宪潮,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天溯警,我揣著相機(jī)與錄音,去河邊找鬼狡相。 笑死梯轻,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的尽棕。 我是一名探鬼主播檩淋,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼萄金!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起媚朦,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤氧敢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后询张,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孙乖,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年份氧,在試婚紗的時候發(fā)現(xiàn)自己被綠了唯袄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡蜗帜,死狀恐怖恋拷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情厅缺,我是刑警寧澤蔬顾,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站湘捎,受9級特大地震影響诀豁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窥妇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一舷胜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧活翩,春花似錦烹骨、人聲如沸翻伺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽穆趴。三九已至,卻和暖如春遇汞,著一層夾襖步出監(jiān)牢的瞬間未妹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工空入, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留络它,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓歪赢,卻偏偏與公主長得像化戳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子埋凯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容