iOS開發(fā)多線程相關(guān)

最近使用多線程比較多,整理一下 iOS 開發(fā)中幾種多線程方案趁餐,以及其使用方法和注意事項(xiàng)喷兼。當(dāng)然也會(huì)給出幾種多線程的案例,在實(shí)際使用中感受它們的區(qū)別后雷。還有一點(diǎn)需要說明的是季惯,這篇文章將會(huì)使用 Objective-c`語言聊一下

概述

這篇文章中,我不會(huì)說多線程是什么臀突、線程和進(jìn)程的區(qū)別勉抓、多線程有什么用,當(dāng)然我也不會(huì)說什么是串行惧辈、什么是并行等問題琳状,這些我們應(yīng)該都知道的。

在 iOS 中其實(shí)目前有 4 套多線程方案盒齿,他們分別是:

  • Pthreads
  • NSThread
  • GCD
  • NSOperation & NSOperationQueue

所以接下來,我會(huì)一一講解這些方案的使用方法和一些案例困食。在將這些內(nèi)容的時(shí)候边翁,我也會(huì)順帶說一些多線程周邊產(chǎn)品。比如: 線程同步硕盹、 延時(shí)執(zhí)行符匾、 單例模式 等等。

Pthreads

其實(shí)這個(gè)方案不用說的瘩例,只是拿來充個(gè)數(shù)啊胶,為了讓大家了解一下就好了甸各。百度百科里是這么說的:

POSIX線程(POSIX threads),簡稱Pthreads焰坪,是線程的POSIX標(biāo)準(zhǔn)趣倾。該標(biāo)準(zhǔn)定義了創(chuàng)建和操縱線程的一整套API。在類Unix操作系統(tǒng)(Unix某饰、Linux儒恋、Mac OS X等)中,都使用Pthreads作為操作系統(tǒng)的線程黔漂。

簡單地說诫尽,這是一套在很多操作系統(tǒng)上都通用的多線程API,所以移植性很強(qiáng)(然并卵)炬守,當(dāng)然在 iOS 中也是可以的牧嫉。不過這是基于 c語言 的框架,使用起來這酸爽减途!感受一下:

OBJECTIVE-C

當(dāng)然第一步要包含頭文件

import <pthread.h>

然后創(chuàng)建線程驹止,并執(zhí)行任務(wù)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    pthread_t thread;
    //創(chuàng)建一個(gè)線程并自動(dòng)執(zhí)行
    pthread_create(&thread, NULL, start, NULL);
}

void *start(void *data) {
    NSLog(@"%@", [NSThread currentThread]);

    return NULL;
}

打印輸出:

2019-08-21 23:57:21.689 testThread[10616:2644653] <NSThread: 0x7fbb48d33690>{number = 2, name = (null)}

看代碼就會(huì)發(fā)現(xiàn)他需要 c語言函數(shù),這是比較蛋疼的观蜗,更蛋疼的是你需要手動(dòng)處理線程的各個(gè)狀態(tài)的轉(zhuǎn)換即管理生命周期臊恋,比如,這段代碼雖然創(chuàng)建了一個(gè)線程墓捻,但并沒有銷毀抖仅。

NSThread

這套方案是經(jīng)過蘋果封裝后的,并且完全面向?qū)ο蟮淖┑凇K阅憧梢灾苯硬倏鼐€程對象撤卢,非常直觀和方便。但是梧兼,它的生命周期還是需要我們手動(dòng)管理放吩,所以這套方案也是偶爾用用,比如 [NSThread currentThread]羽杰,它可以獲取當(dāng)前線程類渡紫,你就可以知道當(dāng)前線程的各種屬性,用于調(diào)試十分方便考赛。下面來看看它的一些用法惕澎。

創(chuàng)建并啟動(dòng)

  • 先創(chuàng)建線程類,再啟動(dòng)

    OBJECTIVE-C
      // 創(chuàng)建
      NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
    
      // 啟動(dòng)
      [thread start];
    
    
  • 創(chuàng)建并自動(dòng)啟動(dòng)

    OBJECTIVE-C
      [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
    
    
  • 使用 NSObject 的方法創(chuàng)建并自動(dòng)啟動(dòng)

    OBJECTIVE-C
      [self performSelectorInBackground:@selector(run:) withObject:nil];
    
    

其他方法

除了創(chuàng)建啟動(dòng)外颜骤,NSThread 還以很多方法唧喉,下面我列舉一些常見的方法,當(dāng)然我列舉的并不完整,更多方法大家可以去類的定義里去看八孝。

OBJECTIVE-C
//取消線程
- (void)cancel;

//啟動(dòng)線程
- (void)start;

//判斷某個(gè)線程的狀態(tài)的屬性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;

//設(shè)置和獲取線程名字
-(void)setName:(NSString *)n;
-(NSString *)name;

//獲取當(dāng)前線程信息
+ (NSThread *)currentThread;

//獲取主線程信息
+ (NSThread *)mainThread;

//使當(dāng)前線程暫停一段時(shí)間董朝,或者暫停到某個(gè)時(shí)刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;

GCD

Grand Central Dispatch,聽名字就霸氣干跛。它是蘋果為多核的并行運(yùn)算提出的解決方案子姜,所以會(huì)自動(dòng)合理地利用更多的CPU內(nèi)核(比如四核、六核)驯鳖,最重要的是它會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程闲询、調(diào)度任務(wù)、銷毀線程)浅辙,完全不需要我們管理扭弧,我們只需要告訴干什么就行。同時(shí)它使用的也是 c語言记舆,不過由于使用了 Block(Swift里叫做閉包)鸽捻,使得使用起來更加方便,而且靈活泽腮。所以基本上大家都使用 GCD 這套方案御蒲。

任務(wù)和隊(duì)列

GCD 中,加入了兩個(gè)非常重要的概念: 任務(wù)隊(duì)列诊赊。

  • 任務(wù):即操作厚满,你想要干什么,說白了就是一段代碼碧磅,在 GCD 中就是一個(gè) Block碘箍,所以添加任務(wù)十分方便。任務(wù)有兩種執(zhí)行方式: 同步執(zhí)行異步執(zhí)行鲸郊,他們之間的區(qū)別是 是否會(huì)創(chuàng)建新的線程丰榴。

    同步執(zhí)行只要是同步執(zhí)行的任務(wù),都會(huì)在當(dāng)前線程執(zhí)行秆撮,不會(huì)另開線程四濒。

    異步執(zhí)行只要是異步執(zhí)行的任務(wù),都會(huì)另開線程职辨,在別的線程執(zhí)行盗蟆。

    更新
    這里說的并不準(zhǔn)確,同步(sync)異步(async) 的主要區(qū)別在于會(huì)不會(huì)阻塞當(dāng)前線程拨匆,直到 Block 中的任務(wù)執(zhí)行完畢姆涩!
    如果是 同步(sync) 操作,它會(huì)阻塞當(dāng)前線程并等待 Block 中的任務(wù)執(zhí)行完畢惭每,然后當(dāng)前線程才會(huì)繼續(xù)往下運(yùn)行。
    如果是 異步(async)操作,當(dāng)前線程會(huì)直接往下執(zhí)行台腥,它不會(huì)阻塞當(dāng)前線程宏赘。

  • 隊(duì)列:用于存放任務(wù)。一共有兩種隊(duì)列黎侈, 串行隊(duì)列并行隊(duì)列察署。

    串行隊(duì)列 中的任務(wù)會(huì)根據(jù)隊(duì)列的定義 FIFO 的執(zhí)行,一個(gè)接一個(gè)的先進(jìn)先出的進(jìn)行執(zhí)行峻汉。

更新:放到串行隊(duì)列的任務(wù)贴汪,GCD 會(huì) FIFO(先進(jìn)先出) 地取出來一個(gè),執(zhí)行一個(gè)休吠,然后取下一個(gè)扳埂,這樣一個(gè)一個(gè)的執(zhí)行。

并行隊(duì)列 中的任務(wù) 根據(jù)同步或異步有不同的執(zhí)行方式瘤礁。

更新:放到并行隊(duì)列的任務(wù)阳懂,GCD 也會(huì) FIFO的取出來,但不同的是柜思,它取出來一個(gè)就會(huì)放到別的線程岩调,然后再取出來一個(gè)又放到另一個(gè)的線程。這樣由于取的動(dòng)作很快赡盘,忽略不計(jì)号枕,看起來,所有的任務(wù)都是一起執(zhí)行的陨享。不過需要注意葱淳,GCD 會(huì)根據(jù)系統(tǒng)資源控制并行的數(shù)量,所以如果任務(wù)很多霉咨,它并不會(huì)讓所有任務(wù)同時(shí)執(zhí)行蛙紫。

雖然很繞,但請看下表:

同步執(zhí)行 異步執(zhí)行
串行隊(duì)列 當(dāng)前線程途戒,一個(gè)一個(gè)執(zhí)行 其他線程坑傅,一個(gè)一個(gè)執(zhí)行
并行隊(duì)列 當(dāng)前線程,一個(gè)一個(gè)執(zhí)行 開很多線程喷斋,一起執(zhí)行

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

  • 主隊(duì)列:這是一個(gè)特殊的 串行隊(duì)列唁毒。什么是主隊(duì)列,大家都知道吧星爪,它用于刷新 UI浆西,任何需要刷新 UI 的工作都要在主隊(duì)列執(zhí)行,所以一般耗時(shí)的任務(wù)都要放到別的線程執(zhí)行顽腾。

      //OBJECTIVE-C
      dispatch_queue_t queue = ispatch_get_main_queue();
    
    
    
  • 自己創(chuàng)建的隊(duì)列凡是自己創(chuàng)建的隊(duì)列都是 串行隊(duì)列近零。 其中第一個(gè)參數(shù)是標(biāo)識(shí)符诺核,用于 DEBUG 的時(shí)候標(biāo)識(shí)唯一的隊(duì)列,可以為空久信。大家可以看xcode的文檔查看參數(shù)意義窖杀。

更新:自己可以創(chuàng)建 串行隊(duì)列, 也可以創(chuàng)建 并行隊(duì)列∪故浚看下面的代碼(代碼已更新)入客,它有兩個(gè)參數(shù),第一個(gè)上面已經(jīng)說了腿椎,第二個(gè)才是最重要的桌硫。
第二個(gè)參數(shù)用來表示創(chuàng)建的隊(duì)列是串行的還是并行的,傳入 DISPATCH_QUEUE_SERIALNULL 表示創(chuàng)建串行隊(duì)列啃炸。傳入 DISPATCH_QUEUE_CONCURRENT 表示創(chuàng)建并行隊(duì)列铆隘。

  //OBJECTIVE-C
  //串行隊(duì)列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
  //并行隊(duì)列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);


  • 全局并行隊(duì)列這應(yīng)該是唯一一個(gè)并行隊(duì)列, 只要是并行任務(wù)一般都加入到這個(gè)隊(duì)列肮帐。這是系統(tǒng)提供的一個(gè)并發(fā)隊(duì)列咖驮。

      //OBJECTIVE-C
      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    

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

  • 同步任務(wù): 不會(huì)另開線程 改:會(huì)阻塞當(dāng)前線程 (SYNC)

    OBJECTIVE-C
      dispatch_sync(<#queue#>, ^{
          //code here
          NSLog(@"%@", [NSThread currentThread]);
      });
    
    
```
  • 異步任務(wù):會(huì)另開線程 改:不會(huì)阻塞當(dāng)前線程 (ASYNC)

    OBJECTIVE-C
      dispatch_async(<#queue#>, ^{
          //code here
          NSLog(@"%@", [NSThread currentThread]);
      });
    
    

更新
為了更好的理解同步和異步,和各種隊(duì)列的使用训枢,下面看兩個(gè)示例:

示例一:
以下代碼在主線程調(diào)用托修,結(jié)果是什么?

NSLog("之前 - %@", NSThread.currentThread())
dispatch_sync(dispatch_get_main_queue(), { () -> Void in 
        NSLog("sync - %@", NSThread.currentThread())
})
NSLog("之后 - %@", NSThread.currentThread())

答案:
只會(huì)打印第一句:之前 - <NSThread: 0x7fb3a9e16470>{number = 1, name = main} 恒界,然后主線程就卡死了睦刃,你可以在界面上放一個(gè)按鈕,你就會(huì)發(fā)現(xiàn)點(diǎn)不了了十酣。
解釋:
同步任務(wù)會(huì)阻塞當(dāng)前線程涩拙,然后把 Block 中的任務(wù)放到指定的隊(duì)列中執(zhí)行,只有等到 Block 中的任務(wù)完成后才會(huì)讓當(dāng)前線程繼續(xù)往下運(yùn)行耸采。
那么這里的步驟就是:打印完第一句后兴泥,dispatch_sync 立即阻塞當(dāng)前的主線程,然后把 Block 中的任務(wù)放到 main_queue 中虾宇,可是 main_queue 中的任務(wù)會(huì)被取出來放到主線程中執(zhí)行搓彻,但主線程這個(gè)時(shí)候已經(jīng)被阻塞了,所以 Block 中的任務(wù)就不能完成嘱朽,它不完成旭贬,dispatch_sync 就會(huì)一直阻塞主線程,這就是死鎖現(xiàn)象搪泳。導(dǎo)致主線程一直卡死稀轨。

示例二:
以下代碼會(huì)產(chǎn)生什么結(jié)果?

let queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL)

NSLog("之前 - %@", NSThread.currentThread())

dispatch_async(queue, { () -> Void in
    NSLog("sync之前 - %@", NSThread.currentThread())
    dispatch_sync(queue, { () -> Void in
         NSLog("sync - %@", NSThread.currentThread())
    })
    NSLog("sync之后 - %@", NSThread.currentThread())

})

NSLog("之后 - %@", NSThread.currentThread())

**答案:**
2019-08-20 02:06:51.058 test[33329:8793087] 之前 - <NSThread: 0x7fe32050dbb0>{number = 1, name = main}
2019-08-20 02:06:51.059 test[33329:8793356] sync之前 - <NSThread: 0x7fe32062e9f0>{number = 2, name = (null)}
2019-08-20 02:06:51.059 test[33329:8793087] 之后 - <NSThread: 0x7fe32050dbb0>{number = 1, name = main}
很明顯 `sync - %@` 和 `sync之后 - %@` 沒有被打印出來岸军!這是為什么呢奋刽?我們再來分析一下:

>**分析:**
我們按執(zhí)行順序一步步來哦:
1. 使用 `DISPATCH_QUEUE_SERIAL` 這個(gè)參數(shù)瓦侮,創(chuàng)建了一個(gè) **串行隊(duì)列**。
2. 打印出 `之前 - %@` 這句杨名。
3. `dispatch_async` 異步執(zhí)行脏榆,所以當(dāng)前線程不會(huì)被阻塞猖毫,于是有了兩條線程台谍,一條當(dāng)前線程繼續(xù)往下打印出 `之后 - %@`這句, 另一臺(tái)執(zhí)行 Block 中的內(nèi)容打印 `sync之前 - %@` 這句。因?yàn)檫@兩條是并行的吁断,所以打印的先后順序無所謂趁蕊。
4. 注意,高潮來了∽幸郏現(xiàn)在的情況和上一個(gè)例子一樣了掷伙。`dispatch_sync`同步執(zhí)行,于是它所在的線程會(huì)被阻塞又兵,一直等到 `sync` 里的任務(wù)執(zhí)行完才會(huì)繼續(xù)往下任柜。于是 `sync` 就高興的把自己 Block 中的任務(wù)放到 `queue` 中,可誰想 `queue` 是一個(gè)串行隊(duì)列沛厨,一次執(zhí)行一個(gè)任務(wù)宙地,所以 `sync` 的 Block 必須等到前一個(gè)任務(wù)執(zhí)行完畢,可萬萬沒想到的是 `queue` 正在執(zhí)行的任務(wù)就是被 `sync` 阻塞了的那個(gè)逆皮。于是又發(fā)生了死鎖宅粥。所以 `sync` 所在的線程被卡死了。剩下的兩句代碼自然不會(huì)打印电谣。 

### 隊(duì)列組

隊(duì)列組可以將很多隊(duì)列添加到一個(gè)組里秽梅,這樣做的好處是,當(dāng)這個(gè)組里所有的任務(wù)都執(zhí)行完了剿牺,隊(duì)列組會(huì)通過一個(gè)方法通知我們企垦。下面是使用方法,這是一個(gè)很實(shí)用的功能晒来。

###### OBJECTIVE-C

``` objective-c
//1.創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
//2.創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//3.多次使用隊(duì)列組的方法執(zhí)行任務(wù), 只有異步方法
//3.1.執(zhí)行3次循環(huán)
dispatch_group_async(group, queue, ^{
   for (NSInteger i = 0; i < 3; i++) {
       NSLog(@"group-01 - %@", [NSThread currentThread]);
   }
});

//3.2.主隊(duì)列執(zhí)行8次循環(huán)
dispatch_group_async(group, dispatch_get_main_queue(), ^{
   for (NSInteger i = 0; i < 8; i++) {
       NSLog(@"group-02 - %@", [NSThread currentThread]);
   }
});

//3.3.執(zhí)行5次循環(huán)
dispatch_group_async(group, queue, ^{
   for (NSInteger i = 0; i < 5; i++) {
       NSLog(@"group-03 - %@", [NSThread currentThread]);
   }
});

//4.都完成后會(huì)自動(dòng)通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
   NSLog(@"完成 - %@", [NSThread currentThread]);
});

NSOperation和NSOperationQueue

NSOperation 是蘋果公司對 GCD 的封裝钞诡,完全面向?qū)ο螅允褂闷饋砀美斫狻?大家可以看到 NSOperation 和 NSOperationQueue 分別對應(yīng) GCD 的 任務(wù) 和 隊(duì)列 潜索。操作步驟也很好理解:

  1. 將要執(zhí)行的任務(wù)封裝到一個(gè) NSOperation 對象中臭增。
  2. 將此任務(wù)添加到一個(gè) NSOperationQueue 對象中。

然后系統(tǒng)就會(huì)自動(dòng)在執(zhí)行任務(wù)竹习。至于同步還是異步誊抛、串行還是并行請繼續(xù)往下看:

添加任務(wù)

值得說明的是,NSOperation 只是一個(gè)抽象類整陌,所以不能封裝任務(wù)拗窃。但它有 2 個(gè)子類用于封裝任務(wù)瞎领。分別是:NSInvocationOperationNSBlockOperation 。創(chuàng)建一個(gè) Operation 后随夸,需要調(diào)用 start 方法來啟動(dòng)任務(wù)九默,它會(huì) 默認(rèn)在當(dāng)前隊(duì)列同步執(zhí)行。當(dāng)然你也可以在中途取消一個(gè)任務(wù)宾毒,只需要調(diào)用其 cancel 方法即可驼修。

  • NSInvocationOperation : 需要傳入一個(gè)方法名。

    OBJECTIVE-C
      //1.創(chuàng)建NSInvocationOperation對象
      NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    
      //2.開始執(zhí)行
      [operation start];
    
    
###### OBJECTIVE-C

```
  //1.創(chuàng)建NSBlockOperation對象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@", [NSThread currentThread]);
  }];

  //2.開始任務(wù)
  [operation start];

```

之前說過這樣的任務(wù)诈铛,默認(rèn)會(huì)在當(dāng)前線程執(zhí)行乙各。但是 `NSBlockOperation` 還有一個(gè)方法:`addExecutionBlock:` ,通過這個(gè)方法可以給 Operation 添加多個(gè)執(zhí)行 Block幢竹。這樣 Operation 中的任務(wù) **會(huì)并發(fā)執(zhí)行**耳峦,它會(huì) **在主線程和其它的多個(gè)線程** 執(zhí)行這些任務(wù),注意下面的打印結(jié)果:

###### OBJECTIVE-C

```
      //1.創(chuàng)建NSBlockOperation對象
      NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
          NSLog(@"%@", [NSThread currentThread]);
      }];

      //添加多個(gè)Block
      for (NSInteger i = 0; i < 5; i++) {
          [operation addExecutionBlock:^{
              NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
          }];
      }

      //2.開始任務(wù)
      [operation start];

```


###### 打印輸出

> 2019-08-21 17:50:16.585 test[17527:4095467] 第2次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}
> 
> 2019-08-21 17:50:16.585 test[17527:4095666] 第1次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}
> 
> 2019-08-21 17:50:16.585 test[17527:4095665] <NSThread: 0x7ff5c961b610>{number = 3, name = (null)}
> 
> 2019-08-21 17:50:16.585 test[17527:4095662] 第0次 - <NSThread: 0x7ff5c948d310>{number = 2, name = (null)}
> 
> 2019-08-21 17:50:16.586 test[17527:4095666] 第3次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}
> 
> 2019-08-21 17:50:16.586 test[17527:4095467] 第4次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}

?

**NOTE**:`addExecutionBlock` 方法必須在 `start()` 方法之前執(zhí)行焕毫,否則就會(huì)報(bào)錯(cuò):

> ‘*** -[NSBlockOperation addExecutionBlock:]: blocks cannot be added after the operation has started executing or finished'

?
  • 自定義Operation

    除了上面的兩種 Operation 以外简卧,我們還可以自定義 Operation弟灼。自定義 Operation 需要繼承 NSOperation 類辖试,并實(shí)現(xiàn)其 main() 方法帅涂,因?yàn)樵谡{(diào)用 start() 方法的時(shí)候,內(nèi)部會(huì)調(diào)用 main() 方法完成相關(guān)邏輯幸乒。所以如果以上的兩個(gè)類無法滿足你的欲望的時(shí)候懦底,你就需要自定義了。你想要實(shí)現(xiàn)什么功能都可以寫在里面罕扎。除此之外聚唐,你還需要實(shí)現(xiàn) cancel() 在內(nèi)的各種方法。所以這個(gè)功能提供給高級玩家腔召,我在這里就不說了杆查,等我需要用到時(shí)在研究它,到時(shí)候可能會(huì)再做更新臀蛛。

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

看過上面的內(nèi)容就知道亲桦,我們可以調(diào)用一個(gè) NSOperation 對象的 start() 方法來啟動(dòng)這個(gè)任務(wù),但是這樣做他們默認(rèn)是 同步執(zhí)行 的浊仆。就算是 addExecutionBlock 方法客峭,也會(huì)在 當(dāng)前線程和其他線程 中執(zhí)行,也就是說還是會(huì)占用當(dāng)前線程抡柿。這是就要用到隊(duì)列 NSOperationQueue 了舔琅。而且,按類型來說的話一共有兩種類型:主隊(duì)列洲劣、其他隊(duì)列备蚓。只要添加到隊(duì)列课蔬,會(huì)自動(dòng)調(diào)用任務(wù)的 start() 方法

  • 主隊(duì)列

    細(xì)心的同學(xué)就會(huì)發(fā)現(xiàn),每套多線程方案都會(huì)有一個(gè)主線程(當(dāng)然啦郊尝,說的是iOS中二跋,像 pthread 這種多系統(tǒng)的方案并沒有,因?yàn)?UI線程 理論需要每種操作系統(tǒng)自己定制)流昏。這是一個(gè)特殊的線程扎即,必須串行。所以添加到主隊(duì)列的任務(wù)都會(huì)一個(gè)接一個(gè)地排著隊(duì)在主線程處理横缔。

    //OBJECTIVE-C
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    
    
    
    
  • 其他隊(duì)列

    因?yàn)橹麝?duì)列比較特殊铺遂,所以會(huì)單獨(dú)有一個(gè)類方法來獲得主隊(duì)列。那么通過初始化產(chǎn)生的隊(duì)列就是其他隊(duì)列了茎刚,因?yàn)橹挥羞@兩種隊(duì)列,除了主隊(duì)列撤逢,其他隊(duì)列就不需要名字了膛锭。

    注意:其他隊(duì)列的任務(wù)會(huì)在其他線程并行執(zhí)行。

    OBJECTIVE-C
    //1.創(chuàng)建一個(gè)其他隊(duì)列    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //2.創(chuàng)建NSBlockOperation對象
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
    }];
    
    //3.添加多個(gè)Block
    for (NSInteger i = 0; i < 5; i++) {
        [operation addExecutionBlock:^{
            NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
        }];
    }
    
    //4.隊(duì)列添加任務(wù)
    [queue addOperation:operation];
    
    
###### 打印輸出

> 2019-08-21 20:26:28.463 test[18622:4443534] <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}
> 
> 2019-08-21 20:26:28.463 test[18622:4443536] 第2次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}
> 
> 2019-08-21 20:26:28.463 test[18622:4443535] 第0次 - <NSThread: 0x7fd022f237f0>{number = 4, name = (null)}
> 
> 2019-08-21 20:26:28.463 test[18622:4443533] 第1次 - <NSThread: 0x7fd022d372b0>{number = 3, name = (null)}
> 
> 2019-08-21 20:26:28.463 test[18622:4443534] 第3次 - <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}
> 
> 2019-08-21 20:26:28.463 test[18622:4443536] 第4次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}

OK, 這時(shí)應(yīng)該發(fā)問了蚊荣,大家將 NSOperationQueueGCD的隊(duì)列 相比較就會(huì)發(fā)現(xiàn)初狰,這里沒有串行隊(duì)列,那如果我想要10個(gè)任務(wù)在其他線程串行的執(zhí)行怎么辦互例?

這就是蘋果封裝的妙處奢入,你不用管串行、并行媳叨、同步腥光、異步這些名詞。NSOperationQueue 有一個(gè)參數(shù) maxConcurrentOperationCount 最大并發(fā)數(shù)糊秆,用來設(shè)置最多可以讓多少個(gè)任務(wù)同時(shí)執(zhí)行武福。當(dāng)你把它設(shè)置為 1 的時(shí)候,他不就是串行了嘛痘番!

NSOperationQueue 還有一個(gè)添加任務(wù)的方法捉片,- (void)addOperationWithBlock:(void (^)(void))block; ,這是不是和 GCD 差不多汞舱?這樣就可以添加一個(gè)任務(wù)到隊(duì)列中了伍纫,十分方便。

NSOperation 有一個(gè)非常實(shí)用的功能昂芜,那就是添加依賴莹规。比如有 3 個(gè)任務(wù):A: 從服務(wù)器上下載一張圖片,B:給這張圖片加個(gè)水印说铃,C:把圖片返回給服務(wù)器访惜。這時(shí)就可以用到依賴了:

OBJECTIVE-C
//1.任務(wù)一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下載圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//2.任務(wù)二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"打水印   - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//3.任務(wù)三:上傳圖片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"上傳圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//4.設(shè)置依賴
[operation2 addDependency:operation1];      //任務(wù)二依賴任務(wù)一
[operation3 addDependency:operation2];      //任務(wù)三依賴任務(wù)二

//5.創(chuàng)建隊(duì)列并加入任務(wù)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

打印結(jié)果

2019-08-21 21:24:28.622 test[19392:4637517] 下載圖片 - <NSThread: 0x7fc10ad4d970>{number = 2, name = (null)}

2019-08-21 21:24:29.622 test[19392:4637515] 打水印 - <NSThread: 0x7fc10af20ef0>{number = 3, name = (null)}

2019-08-21 21:24:30.627 test[19392:4637515] 上傳圖片 - <NSThread: 0x7fc10af20ef0>{number = 3, name = (null)}

  • 注意:不能添加相互依賴嘹履,會(huì)死鎖,比如 A依賴B债热,B依賴A砾嫉。
  • 可以使用 removeDependency 來解除依賴關(guān)系。
  • 可以在不同的隊(duì)列之間依賴窒篱,反正就是這個(gè)依賴是添加到任務(wù)身上的焕刮,和隊(duì)列沒關(guān)系。

其他方法

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

  • NSOperation

    BOOL executing; //判斷任務(wù)是否正在執(zhí)行

    BOOL finished; //判斷任務(wù)是否完成

    void (^completionBlock)(void); //用來設(shè)置完成后需要執(zhí)行的操作

    • (void)cancel; //取消任務(wù)

    • (void)waitUntilFinished; //阻塞當(dāng)前線程直到此任務(wù)執(zhí)行完畢

  • NSOperationQueue

    NSUInteger operationCount; //獲取隊(duì)列的任務(wù)數(shù)

    • (void)cancelAllOperations; //取消隊(duì)列中所有的任務(wù)

    • (void)waitUntilAllOperationsAreFinished; //阻塞當(dāng)前線程直到此隊(duì)列中的所有任務(wù)執(zhí)行完畢

    [queue setSuspended:YES]; // 暫停queue

    [queue setSuspended:NO]; // 繼續(xù)queue

好啦墙杯,到這里差不多就講完了配并。當(dāng)然,我講的并不完整高镐,可能有一些知識(shí)我并沒有講到溉旋,但作為常用方法,這些已經(jīng)足夠了嫉髓。不過我在這里只是告訴你了一些方法的功能观腊,只是怎么把他們用到合適的地方,就需要多多實(shí)踐了算行。下面我會(huì)說一些關(guān)于多線程的案例梧油,是大家更加什么地了解。

其他用法

在這部分州邢,我會(huì)說一些和多線程知識(shí)相關(guān)的案例儡陨,可能有些很簡單,大家早都知道的量淌,不過因?yàn)檫@篇文章講的是多線程嘛骗村,所以應(yīng)該盡可能的全面嘛。還有就是类少,我會(huì)盡可能的使用多種方法實(shí)現(xiàn)叙身,讓大家看看其中的區(qū)別。

線程同步

所謂線程同步就是為了防止多個(gè)線程搶奪同一個(gè)資源造成的數(shù)據(jù)安全問題硫狞,所采取的一種措施信轿。當(dāng)然也有很多實(shí)現(xiàn)方法,請往下看:

  • 互斥鎖 :給需要同步的代碼塊加一個(gè)互斥鎖残吩,就可以保證每次只有一個(gè)線程訪問此代碼塊财忽。

    OBJECTIVE-C
    @synchronized(self) {
      //需要執(zhí)行的代碼塊
    }
    
    
  • 同步執(zhí)行 :我們可以使用多線程的知識(shí),把多個(gè)線程都要執(zhí)行此段代碼添加到同一個(gè)串行隊(duì)列泣侮,這樣就實(shí)現(xiàn)了線程同步的概念即彪。當(dāng)然這里可以使用 GCDNSOperation 兩種方案,我都寫出來。

    OBJECTIVE-C

//GCD
//需要一個(gè)全局變量queue隶校,要讓所有線程的這個(gè)操作都加到一個(gè)queue中
dispatch_sync(queue, ^{
    NSInteger ticket = lastTicket;
    [NSThread sleepForTimeInterval:0.1];
    NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
    ticket -= 1;
    lastTicket = ticket;
});

//NSOperation & NSOperationQueue
//重點(diǎn):1\. 全局的 NSOperationQueue, 所有的操作添加到同一個(gè)queue中
//       2\. 設(shè)置 queue 的 maxConcurrentOperationCount 為 1
//       3\. 如果后續(xù)操作需要Block中的結(jié)果漏益,就需要調(diào)用每個(gè)操作的waitUntilFinished,阻塞當(dāng)前線程深胳,一直等到當(dāng)前操作完成绰疤,才允許執(zhí)行后面的。waitUntilFinished 要在添加到隊(duì)列之后舞终!

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSInteger ticket = lastTicket;
    [NSThread sleepForTimeInterval:1];
    NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
    ticket -= 1;
    lastTicket = ticket;
}];

[queue addOperation:operation];

[operation waitUntilFinished];

//后續(xù)要做的事

延遲執(zhí)行

所謂延遲執(zhí)行就是延時(shí)一段時(shí)間再執(zhí)行某段代碼轻庆。下面說一些常用方法。

  • perform

    OBJECTIVE-C
    // 3秒后自動(dòng)調(diào)用self的run:方法敛劝,并且傳遞參數(shù):@"abc"
    [self performSelector:@selector(run:) withObject:@"abc" afterDelay:3];
    
    
  • GCD

    可以使用 GCD 中的 dispatch_after 方法余爆,OC 和 Swift 都可以使用,這里只寫 OC 的夸盟,Swift 的是一樣的蛾方。

    OBJECTIVE-C
    // 創(chuàng)建隊(duì)列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 設(shè)置延時(shí),單位秒
    double delay = 3; 
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{
      // 3秒后需要執(zhí)行的任務(wù)
    });
    
    
  • NSTimer

    NSTimer 是iOS中的一個(gè)計(jì)時(shí)器類满俗,除了延遲執(zhí)行還有很多用法转捕,不過這里直說延遲執(zhí)行的用法。同樣只寫 OC 版的唆垃,Swift 也是相同的。

    OBJECTIVE-C
    [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc" repeats:NO];
    
    

單例模式

至于什么是單例模式痘儡,我也不多說辕万,我只說說一般怎么實(shí)現(xiàn)。在 Objective-C 中沉删,實(shí)現(xiàn)單例的方法已經(jīng)很具體了渐尿,雖然有別的方法,但是一般都是用一個(gè)標(biāo)準(zhǔn)的方法了矾瑰,下面來看看砖茸。

OBJECTIVE-C
@interface Tool : NSObject <NSCopying>

+ (instancetype)sharedTool;

@end

@implementation Tool

static id _instance;

+ (instancetype)sharedTool {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[Tool alloc] init];
    });

    return _instance;
}

@end

這里之所以將單例模式,是因?yàn)槠渲杏玫搅?GCD 的 dispatch_once 方法殴穴。

從其他線程回到主線程的方法

我們都知道在其他線程操作完成后必須到主線程更新UI凉夯。所以,介紹完所有的多線程方案后采幌,我們來看看有哪些方法可以回到主線程劲够。

  • NSThread

    //Objective-C
    [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
    
    //Swift
    //swift 取消了 performSelector 方法。
    
    
  • GCD

    //Objective-C
    dispatch_async(dispatch_get_main_queue(), ^{
    
    });
    
    
    
  • NSOperationQueue

    //Objective-C
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    
    }];
    
    
    
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末休傍,一起剝皮案震驚了整個(gè)濱河市征绎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌磨取,老刑警劉巖人柿,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柴墩,死亡現(xiàn)場離奇詭異,居然都是意外死亡凫岖,警方通過查閱死者的電腦和手機(jī)江咳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來隘截,“玉大人扎阶,你說我怎么就攤上這事∩舭牛” “怎么了东臀?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長犀农。 經(jīng)常有香客問我惰赋,道長,這世上最難降的妖魔是什么呵哨? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任赁濒,我火速辦了婚禮,結(jié)果婚禮上孟害,老公的妹妹穿的比我還像新娘拒炎。我一直安慰自己,他們只是感情好挨务,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布击你。 她就那樣靜靜地躺著,像睡著了一般谎柄。 火紅的嫁衣襯著肌膚如雪丁侄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天朝巫,我揣著相機(jī)與錄音鸿摇,去河邊找鬼。 笑死劈猿,一個(gè)胖子當(dāng)著我的面吹牛拙吉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播糙臼,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼庐镐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了变逃?” 一聲冷哼從身側(cè)響起必逆,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后名眉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體粟矿,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年损拢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了陌粹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,769評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡福压,死狀恐怖掏秩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情荆姆,我是刑警寧澤蒙幻,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站胆筒,受9級特大地震影響邮破,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜仆救,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一抒和、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧彤蔽,春花似錦摧莽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至员魏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間叠聋,已是汗流浹背撕阎。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留碌补,地道東北人虏束。 一個(gè)月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像厦章,于是被迫代替她去往敵國和親镇匀。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評論 2 354

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