IOS 多線程知識學習

學習多線程乡范,轉載兩篇大神的帖子女责,留著以后回顧动漾!
第一篇:關于iOS多線程,你看我就夠了
第二篇:GCD使用經驗與技巧淺談
都很不錯负饲,還有很多關聯(lián)文章,慢慢學N沽础7凳!多看原帖M治ⅰ6纯印!
第三篇:IOS GCD開發(fā)學習中

在這篇文章中蝇率,整理一下 iOS 開發(fā)中幾種多線程方案迟杂,以及其使用方法和注意事項。當然也會給出幾種多線程的案例本慕,在實際使用中感受它們的區(qū)別排拷。還有一點需要說明的是,這篇文章將會使用 Swift 和 Objective-c 兩種語言講解!

概述

這篇文章中间狂,我不會說多線程是什么攻泼、線程和進程的區(qū)別、多線程有什么用鉴象,當然我也不會說什么是串行忙菠、什么是并行等問題,這些我們應該都知道的纺弊。

在 iOS 中其實目前有 4 套多線程方案牛欢,他們分別是:

  • Pthreads
  • NSThread
  • GCD (Grand Central Dispatch )
  • NSOperation & NSOperationQueue

所以接下來,我會一一講解這些方案的使用方法和一些案例淆游。在將這些內容的時候傍睹,我也會順帶說一些多線程周邊產品隔盛。比如: 線程同步、 延時執(zhí)行拾稳、 單例模式 等等吮炕。

Pthreads

其實這個方案不用說的,只是拿來充個數(shù)访得,為了讓大家了解一下就好了龙亲。百度百科里是這么說的:

POSIX線程(POSIX threads),簡稱Pthreads悍抑,是線程的POSIX標準鳄炉。該標準定義了創(chuàng)建和操縱線程的一整套API。在類Unix操作系統(tǒng)(Unix搜骡、Linux拂盯、Mac OS X等)中,都使用Pthreads作為操作系統(tǒng)的線程记靡。

簡單地說谈竿,這是一套在很多操作系統(tǒng)上都通用的多線程API,所以移植性很強(然并卵)簸呈,當然在 iOS 中也是可以的榕订。不過這是基于 c語言 的框架,使用起來這酸爽蜕便!感受一下:


define NSEC_PER_SEC 1000000000ull
define USEC_PER_SEC 1000000ull
define NSEC_PER_USEC 1000ull
關鍵詞解釋:

NSEC:納秒劫恒。
USEC:微妙。
SEC:秒
PER:每
所以:
NSEC_PER_SEC轿腺,每秒有多少納秒两嘴。
USEC_PER_SEC,每秒有多少毫秒族壳。(注意是指在納秒的基礎上)
NSEC_PER_USEC憔辫,每毫秒有多少納秒。
所以仿荆,延時1秒可以寫成如下幾種:

dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC); 
dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC); 
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC); 

最后一個“USEC_PER_SEC * NSEC_PER_USEC”贰您,翻譯過來就是“每秒的毫秒數(shù)乘以每毫秒的納秒數(shù)”,也就是“每秒的納秒數(shù)”拢操,所以锦亦,延時500毫秒之類的,也就不難了吧~

OBJECTIVE-C

當然第一步要包含頭文件

#import <pthread.h>

然后創(chuàng)建線程令境,并執(zhí)行任務

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
 pthread_t thread; //創(chuàng)建一個線程并自動執(zhí)行 pthread_create(&thread, NULL, start, NULL);
}
void *start(void *data) {
 NSLog(@"%@", [NSThread currentThread]); return NULL;
}

打印輸出:

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

看代碼就會發(fā)現(xiàn)他需要 c語言函數(shù)杠园,這是比較蛋疼的,更蛋疼的是你需要手動處理線程的各個狀態(tài)的轉換即管理生命周期舔庶,比如抛蚁,這段代碼雖然創(chuàng)建了一個線程陈醒,但并沒有銷毀。

.

SWIFT

很遺憾瞧甩,在我目前的swift1.2
中無法執(zhí)行這套方法钉跷,原因是這個函數(shù)需要傳入一個函數(shù)指針CFunctionPointer<T>
類型,但是目前 swift 無法將方法轉換成此類型亲配。聽說swift 2.0
引入一個新特性@convention(c)
, 可以完成 Swift 方法轉換成 c 語言指針的尘应。在這里可以看到
那么,Pthreads
方案的多線程我就介紹這么多吼虎,畢竟做 iOS 開發(fā)幾乎不可能用到。但是如果你感興趣的話苍鲜,或者說想要自己實現(xiàn)一套多線程方案思灰,從底層開始定制,那么可以去搜一下相關資料混滔。

NSThread

這套方案是經過蘋果封裝后的洒疚,并且完全面向對象的。所以你可以直接操控線程對象坯屿,非常直觀和方便油湖。但是,它的生命周期還是需要我們手動管理领跛,所以這套方案也是偶爾用用乏德,比如 [NSThread currentThread],它可以獲取當前線程類吠昭,你就可以知道當前線程的各種屬性喊括,用于調試十分方便。下面來看看它的一些用法矢棚。

創(chuàng)建并啟動
  • 先創(chuàng)建線程類郑什,再啟動
OBJECTIVE-C
 // 創(chuàng)建
  NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];

  // 啟動
  [thread start];
SWIFT
 //創(chuàng)建
  let thread = NSThread(target: self, selector: "run:", object: nil)

  //啟動
  thread.start()
  • 創(chuàng)建并自動啟動
OBJECTIVE-C
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
SWIFT
NSThread.detachNewThreadSelector("run:", toTarget: self, withObject: nil)
  • 使用 NSObject 的方法創(chuàng)建并自動啟動
OBJECTIVE-C
[self performSelectorInBackground:@selector(run:) withObject:nil];
SWIFT

很遺憾 too! 蘋果認為performSelector:
不安全,所以在 Swift 去掉了這個方法蒲肋。

Note: The performSelector: method and related selector-invoking methods are not imported in Swift because they are inherently unsafe.

其他方法

除了創(chuàng)建啟動外蘑拯,NSThread 還以很多方法,下面我列舉一些常見的方法兜粘,當然我列舉的并不完整申窘,更多方法大家可以去類的定義里去看。

OBJECTIVE-C
//取消線程
- (void)cancel;
//啟動線程
- (void)start;
//判斷某個線程的狀態(tài)的屬性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//設置和獲取線程名字
-(void)setName:(NSString *)n;
-(NSString *)name;
//獲取當前線程信息
+ (NSThread *)currentThread;
//獲取主線程信息+ (NSThread *)mainThread;
//使當前線程暫停一段時間妹沙,或者暫停到某個時刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;
SWIFT

Swift的方法名字和OC的方法名都一樣偶洋,我就不浪費空間列舉出來了。

其實距糖,NSThread 用起來也挺簡單的玄窝,因為它就那幾種方法牵寺。同時,我們也只有在一些非常簡單的場景才會用 NSThread, 畢竟它還不夠智能恩脂,不能優(yōu)雅地處理多線程中的其他高級概念帽氓。所以接下來要說的內容才是重點。

GCD

Grand Central Dispatch俩块,聽名字就霸氣黎休。它是蘋果為多核的并行運算提出的解決方案,所以會自動合理地利用更多的CPU內核(比如雙核玉凯、四核)势腮,最重要的是它會自動管理線程的生命周期(創(chuàng)建線程、調度任務漫仆、銷毀線程)捎拯,完全不需要我們管理,我們只需要告訴干什么就行盲厌。同時它使用的也是 c語言署照,不過由于使用了 Block(Swift里叫做閉包),使得使用起來更加方便吗浩,而且靈活建芙。

任務和隊列

在 GCD 中,加入了兩個非常重要的概念: 任務 和 隊列懂扼。

  • 任務:即操作禁荸,你想要干什么,說白了就是一段代碼微王,在 GCD 中就是一個 Block屡限,所以添加任務十分方便。任務有兩種執(zhí)行方式: 同步執(zhí)行異步執(zhí)行炕倘,他們之間的區(qū)別是:會不會阻塞當前線程钧大,直到** Block** 中的任務執(zhí)行完畢!
    罩旋。
    同步執(zhí)行:會阻塞當前線程并等待 Block 中的任務執(zhí)行完畢啊央,然后當前線程才會繼續(xù)往下運行。
    異步執(zhí)行:當前線程會直接往下執(zhí)行涨醋,它不會阻塞當前線程.

  • 隊列:用于存放任務瓜饥。一共有兩種隊列, 串行隊列 和 并行隊列浴骂。

串行隊列 中的任務會根據(jù)隊列的定義 FIFO 的執(zhí)行乓土,一個接一個的先進先出的進行執(zhí)行。放到串行隊列的任務,GCD 會 FIFO(先進先出) 地取出來一個趣苏,執(zhí)行一個狡相,然后取下一個,這樣一個一個的執(zhí)行食磕。

并行隊列 放到并行隊列的任務尽棕,GCD 也會 FIFO的取出來,但不同的是彬伦,它取出來一個就會放到別的線程滔悉,然后再取出來一個又放到另一個的線程。這樣由于取的動作很快单绑,忽略不計回官,看起來,所有的任務都是一起執(zhí)行的询张。不過需要注意孙乖,GCD 會根據(jù)系統(tǒng)資源控制并行的數(shù)量,所以如果任務很多份氧,它并不會讓所有任務同時執(zhí)行。

雖然很繞弯屈,但請看下表:

| 同步執(zhí)行 | 異步執(zhí)行
----|------------|-----
串行隊列 | 當前線程蜗帜,一個一個執(zhí)行 | 其他線程,一個一個執(zhí)行
并行隊列 | 當前線程资厉,一個一個執(zhí)行 | 開很多線程厅缺,一起執(zhí)行

創(chuàng)建隊列

  • 主隊列:這是一個特殊的 串行隊列。什么是主隊列宴偿,大家都知道吧湘捎,它用于刷新 UI,任何需要刷新 UI 的工作都要在主隊列執(zhí)行窄刘,所以一般耗時的任務都要放到別的線程執(zhí)行窥妇。
//OBJECTIVE-C
  dispatch_queue_t queue = ispatch_get_main_queue();

  //SWIFT
  let queue = ispatch_get_main_queue()
  • 自己創(chuàng)建的隊列:自己可以創(chuàng)建 串行隊列, 也可以創(chuàng)建 并行隊列。其中第一個參數(shù)是標識符娩践,用于 DEBUG 的時候標識唯一的隊列活翩,可以為空。第二個參數(shù)用來表示創(chuàng)建的隊列是串行的還是并行的翻伺,傳入 DISPATCH_QUEUE_SERIAL 或 NULL 表示創(chuàng)建串行隊列材泄。傳入 DISPATCH_QUEUE_CONCURRENT 表示創(chuàng)建并行隊列。大家可以看xcode的文檔查看參數(shù)意義吨岭。
//OBJECTIVE-C
  //串行隊列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
  //并行隊列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);

  //SWIFT
  //串行隊列
  let queue = dispatch_queue_create("tk.bourne.testQueue", nil);
  let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL)
  //并行隊列
  let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT)
  • 全局并行隊列:這應該是唯一一個并行隊列拉宗, 只要是并行任務一般都加入到這個隊列。這是系統(tǒng)提供的一個并發(fā)隊列。
//OBJECTIVE-C
  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

  //SWIFT
  let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

創(chuàng)建任務

同步任務: 改:會阻塞當前線程 (SYNC)

OBJECTIVE-C
dispatch_sync(<#queue#>, ^{ //code here NSLog(@"%@", [NSThread currentThread]); });
SWIFT
dispatch_sync(<#queue#>, { () -> Void in //code here println(NSThread.currentThread()) })

異步任務:不會阻塞當前線程 (ASYNC)

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

為了更好的理解同步和異步旦事,和各種隊列的使用魁巩,下面看兩個示例:
示例一:以下代碼在主線程調用,結果是什么族檬?

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

答案:只會打印第一句:之前 - <NSThread: 0x7fb3a9e16470>{number = 1, name = main}
歪赢,然后主線程就卡死了,你可以在界面上放一個按鈕单料,你就會發(fā)現(xiàn)點不了了埋凯。解釋:同步任務會阻塞當前線程,然后把 Block 中的任務放到指定的隊列中執(zhí)行扫尖,只有等到 Block 中的任務完成后才會讓當前線程繼續(xù)往下運行白对。那么這里的步驟就是:打印完第一句后,dispatch_sync
立即阻塞當前的主線程换怖,然后把 Block 中的任務放到main_queue
中甩恼,可是main_queue
中的任務會被取出來放到主線程中執(zhí)行,但主線程這個時候已經被阻塞了沉颂,所以 Block 中的任務就不能完成条摸,它不完成,dispatch_sync
就會一直阻塞主線程铸屉,這就是死鎖現(xiàn)象钉蒲。導致主線程一直卡死。
示例二:以下代碼會產生什么結果彻坛?

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())

答案:

2015-07-30 02:06:51.058 test[33329:8793087] 之前 - <NSThread: 0x7fe32050dbb0>{number = 1, name = main}
2015-07-30 02:06:51.059 test[33329:8793356] sync之前 - <NSThread: 0x7fe32062e9f0>{number = 2, name = (null)}
2015-07-30 02:06:51.059 test[33329:8793087] 之后 - <NSThread: 0x7fe32050dbb0>{number = 1, name = main}

很明顯sync - %@sync之后- %@沒有被打印出來顷啼!這是為什么呢?我們再來分析一下:
分析:我們按執(zhí)行順序一步步來哦:

  1. 使用DISPATCH_QUEUE_SERIAL這個參數(shù)昌屉,創(chuàng)建了一個 串行隊列钙蒙。
  2. 打印出之前 - %@這句。
  3. dispatch_async異步執(zhí)行间驮,所以當前線程不會被阻塞躬厌,于是有了兩條線程,一條當前線程繼續(xù)往下打印出之后 - %@
    這句, 另一臺執(zhí)行 Block 中的內容打印sync之前- %@這句蜻牢。因為這兩條是并行的烤咧,所以打印的先后順序無所謂。
  4. 注意抢呆,高潮來了≈笙樱現(xiàn)在的情況和上一個例子一樣了。dispatch_sync同步執(zhí)行抱虐,于是它所在的線程會被阻塞昌阿,一直等到sync里的任務執(zhí)行完才會繼續(xù)往下。于是sync就高興的把自己 Block 中的任務放到queue
    中,可誰想queue是一個串行隊列懦冰,一次執(zhí)行一個任務灶轰,所以syncBlock 必須等到前一個任務執(zhí)行完畢,可萬萬沒想到的是queue正在執(zhí)行的任務就是被sync阻塞了的那個刷钢。于是又發(fā)生了死鎖笋颤。所以sync所在的線程被卡死了。剩下的兩句代碼自然不會打印内地。

隊列組

隊列組可以將很多隊列添加到一個組里伴澄,這樣做的好處是,當這個組里所有的任務都執(zhí)行完了阱缓,隊列組會通過一個方法通知我們非凌。下面是使用方法,這是一個很實用的功能

OBJECTIVE-C
//1.創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
//2.創(chuàng)建隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.多次使用隊列組的方法執(zhí)行任務, 只有異步方法
//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.主隊列執(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.都完成后會自動通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
     NSLog(@"完成 - %@", [NSThread currentThread]);
});
SWIFT
//1.創(chuàng)建隊列組
let group = dispatch_group_create()
//2.創(chuàng)建隊列l(wèi)et queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
//3.多次使用隊列組的方法執(zhí)行任務, 只有異步方法
//3.1.執(zhí)行3次循環(huán)dispatch_group_async(group, queue) { () -> Void in 
    for _ in 0..<3 { 
        NSLog("group-01 - %@", NSThread.currentThread()) 
    }
}
//3.2.主隊列執(zhí)行8次循環(huán)
dispatch_group_async(group, dispatch_get_main_queue()) { () -> Void in
     for _ in 0..<8 {
         NSLog("group-02 - %@", NSThread.currentThread()) 
    }
}
//3.3.執(zhí)行5次循環(huán)
dispatch_group_async(group, queue) { () -> Void in 
    for _ in 0..<5 {
         NSLog("group-03 - %@", NSThread.currentThread()) 
    }
}
//4.都完成后會自動通知 
dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
        NSLog("完成 - %@", NSThread.currentThread())
}

打印結果

2015-07-28 03:40:34.277 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.277 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.277 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.277 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.277 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.278 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.278 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}

2015-07-28 03:40:34.279 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.279 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.279 test[12540:3319146] 完成 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

這些就是 GCD 的基本功能荆针,但是它的能力遠不止這些敞嗡,等講完 NSOperation 后,我們再來看看它的一些其他方面用途航背。而且喉悴,只要你想象力夠豐富,你可以組合出更好的用法玖媚。

關于GCD粥惧,還有兩個需要說的:

  • func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):這個方法重點是你傳入的 queue,當你傳入的 queue 是通過DISPATCH_QUEUE_CONCURRENT參數(shù)自己創(chuàng)建的 queue 時最盅,這個方法會阻塞這個 queue注意是阻塞 queue ,而不是阻塞當前線程)起惕,一直等到這個 queue 中排在它前面的任務都執(zhí)行完成后才會開始執(zhí)行自己涡贱,自己執(zhí)行完畢后,再會取消阻塞惹想,使這個 queue 中排在它后面的任務繼續(xù)執(zhí)行问词。如果你傳入的是其他的 queue, 那么它就和dispatch_async一樣了。
  • func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):這個方法的使用和上一個一樣嘀粱,傳入 自定義的并發(fā)隊列(DISPATCH_QUEUE_CONCURRENT)激挪,它和上一個方法一樣的阻塞 queue,不同的是 這個方法還會 阻塞當前線程锋叨。如果你傳入的是其他的 queue, 那么它就和dispatch_sync一樣了垄分。

關于更詳細的GCD基礎補充

NSOperation和NSOperationQueue

NSOperation 是蘋果公司對 GCD 的封裝,完全面向對象娃磺,所以使用起來更好理解薄湿。 大家可以看到 NSOperationNSOperationQueue 分別對應 GCD 的 任務 和 隊列 。操作步驟也很好理解:

將要執(zhí)行的任務封裝到一個 NSOperation 對象中。
將此任務添加到一個 NSOperationQueue 對象中豺瘤。

然后系統(tǒng)就會自動在執(zhí)行任務吆倦。至于同步還是異步、串行還是并行請繼續(xù)往下看:

添加任務

值得說明的是坐求,NSOperation只是一個抽象類蚕泽,所以不能封裝任務。但它有 2 個子類用于封裝任務桥嗤。分別是:NSInvocationOperationNSBlockOperation
须妻。創(chuàng)建一個 Operation 后,需要調用start
方法來啟動任務砸逊,它會 默認在當前隊列同步執(zhí)行璧南。當然你也可以在中途取消一個任務,只需要調用其cancel方法即可师逸。

  • NSInvocationOperation : 需要傳入一個方法名司倚。
OBJECTIVE-C
//1.創(chuàng)建NSInvocationOperation對象 
NSInvocationOperation *operation =[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
 //2.開始執(zhí)行 
[operation start];
SWIFT

在 Swift 構建的和諧社會里,是容不下NSInvocationOperation
這種不是類型安全的敗類的篓像。蘋果如是說动知。這里有相關解釋

  • NSBlockOperation
OBJECTIVE-C
//1.創(chuàng)建NSBlockOperation對象 
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ 
    NSLog(@"%@", [NSThread currentThread]);
 }];
 //2.開始任務 
[operation start];
SWIFT
//1.創(chuàng)建NSBlockOperation對象
 let operation = NSBlockOperation { () -> Void in
       println(NSThread.currentThread()) 
}
 //2.開始任務 
operation.start()

之前說過這樣的任務,默認會在當前線程執(zhí)行员辩。但是NSBlockOperation
還有一個方法:addExecutionBlock:
盒粮,通過這個方法可以給 Operation 添加多個執(zhí)行 Block。這樣 Operation 中的任務 會并發(fā)執(zhí)行奠滑,它會 在主線程和其它的多個線程 執(zhí)行這些任務丹皱,注意下面的打印結果:

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

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

      //2.開始任務
      [operation start];
SWIFT
 //1.創(chuàng)建NSBlockOperation對象
        let operation = NSBlockOperation { () -> Void in
            NSLog("%@", NSThread.currentThread())
        }

        //2.添加多個Block
        for i in 0..<5 {
            operation.addExecutionBlock { () -> Void in
                NSLog("第%ld次 - %@", i, NSThread.currentThread())
            }
        }

        //2.開始任務
        operation.start()

打印輸出

2015-07-28 17:50:16.585 test[17527:4095467] 第2次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}

2015-07-28 17:50:16.585 test[17527:4095666] 第1次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}

2015-07-28 17:50:16.585 test[17527:4095665] <NSThread: 0x7ff5c961b610>{number = 3, name = (null)}

2015-07-28 17:50:16.585 test[17527:4095662] 第0次 - <NSThread: 0x7ff5c948d310>{number = 2, name = (null)}

2015-07-28 17:50:16.586 test[17527:4095666] 第3次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}

2015-07-28 17:50:16.586 test[17527:4095467] 第4次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}

NOTEaddExecutionBlock方法必須在start()方法之前執(zhí)行,否則就會報錯:

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

NOTE:大家可能發(fā)現(xiàn)了一個問題宋税,為什么我在 Swift 里打印輸出使用NSLog()而不是println()呢摊崭?原因是使用print() / println()輸出的話,它會簡單地使用 流(stream) 的概念杰赛,學過 C++ 的都知道谎碍。它會把需要輸出的每個字符一個一個的輸出到控制臺梯嗽。普通使用并沒有問題,可是當多線程同步輸出的時候問題就來了,由于很多println()同時打印甫匹,就會導致控制臺上的字符混亂的堆在一起丽啡,而NSLog()就沒有這個問題耐薯。到底是什么樣子的呢蜓陌?你可以把上面NSLog()改為println(),然后一試便知伞芹。 更多NSLog()println() 的區(qū)別看這里

  • 自定義Operation

除了上面的兩種 Operation 以外忘苛,我們還可以自定義 Operation蝉娜。自定義 Operation 需要繼承 NSOperation類,并實現(xiàn)其 main()方法扎唾,因為在調用 start()方法的時候召川,內部會調用main()方法完成相關邏輯。所以如果以上的兩個類無法滿足你的欲望的時候胸遇,你就需要自定義了荧呐。你想要實現(xiàn)什么功能都可以寫在里面。除此之外纸镊,你還需要實現(xiàn)cancel()在內的各種方法倍阐。所以這個功能提供給高級玩家,我在這里就不說了逗威,等我需要用到時在研究它峰搪,到時候可能會再做更新。

創(chuàng)建隊列

看過上面的內容就知道凯旭,我們可以調用一個NSOperation
對象的start()方法來啟動這個任務概耻,但是這樣做他們默認是 同步執(zhí)行 的。就算是addExecutionBlock方法罐呼,也會在 當前線程和其他線程 中執(zhí)行鞠柄,也就是說還是會占用當前線程。這是就要用到隊列NSOperationQueue
了嫉柴。而且厌杜,按類型來說的話一共有兩種類型:主隊列、其他隊列计螺。只要添加到隊列夯尽,會自動調用任務的 start()方法

  • 主隊列

細心的同學就會發(fā)現(xiàn),每套多線程方案都會有一個主線程(當然啦登馒,說的是iOS中呐萌,像 pthread 這種多系統(tǒng)的方案并沒有,因為 UI線程 理論需要每種操作系統(tǒng)自己定制)谊娇。這是一個特殊的線程,必須串行罗晕。所以添加到主隊列的任務都會一個接一個地排著隊在主線程處理济欢。

//OBJECTIVE-C
NSOperationQueue *queue = [NSOperationQueue mainQueue];

//SWIFT
let queue = NSOperationQueue.mainQueue()
  • 其他隊列

因為主隊列比較特殊,所以會單獨有一個類方法來獲得主隊列小渊。那么通過初始化產生的隊列就是其他隊列了法褥,因為只有這兩種隊列,除了主隊列酬屉,其他隊列就不需要名字了半等。

注意:其他隊列的任務會在其他線程并行執(zhí)行揍愁。

OBJECTIVE-C
//1.創(chuàng)建一個其他隊列    
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

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

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

//4.隊列添加任務
[queue addOperation:operation];
SWIFT
//1.創(chuàng)建其他隊列
let queue = NSOperationQueue()

//2.創(chuàng)建NSBlockOperation對象
let operation = NSBlockOperation { () -> Void in
    NSLog("%@", NSThread.currentThread())
}

//3.添加多個Block
for i in 0..<5 {
    operation.addExecutionBlock { () -> Void in
        NSLog("第%ld次 - %@", i, NSThread.currentThread())
    }
}

//4.隊列添加任務
queue.addOperation(operation)

打印輸出

2015-07-28 20:26:28.463 test[18622:4443534] <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}

2015-07-28 20:26:28.463 test[18622:4443536] 第2次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}

2015-07-28 20:26:28.463 test[18622:4443535] 第0次 - <NSThread: 0x7fd022f237f0>{number = 4, name = (null)}

2015-07-28 20:26:28.463 test[18622:4443533] 第1次 - <NSThread: 0x7fd022d372b0>{number = 3, name = (null)}

2015-07-28 20:26:28.463 test[18622:4443534] 第3次 - <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}

2015-07-28 20:26:28.463 test[18622:4443536] 第4次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}

OK, 這時應該發(fā)問了,大家將NSOperationQueue與 GCD的隊列 相比較就會發(fā)現(xiàn)杀饵,這里沒有串行隊列莽囤,那如果我想要10個任務在其他線程串行的執(zhí)行怎么辦?

這就是蘋果封裝的妙處切距,你不用管串行朽缎、并行、同步谜悟、異步這些名詞话肖。NSOperationQueue 有一個參數(shù) maxConcurrentOperationCount 最大并發(fā)數(shù),用來設置最多可以讓多少個任務同時執(zhí)行葡幸。當你把它設置為 1 的時候最筒,他不就是串行了嘛!

NSOperationQueue還有一個添加任務的方法蔚叨,- (void)addOperationWithBlock:(void (^)(void))block; 床蜘,這是不是和 GCD 差不多?這樣就可以添加一個任務到隊列中了缅叠,十分方便悄泥。

NSOperation 有一個非常實用的功能,那就是添加依賴肤粱。比如有 3 個任務:A: 從服務器上下載一張圖片弹囚,B:給這張圖片加個水印,C:把圖片返回給服務器领曼。這時就可以用到依賴了:

OBJECTIVE-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.創(chuàng)建隊列并加入任務
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
SWIFT
//1.任務一:下載圖片
let operation1 = NSBlockOperation { () -> Void in
    NSLog("下載圖片 - %@", NSThread.currentThread())
    NSThread.sleepForTimeInterval(1.0)
}

//2.任務二:打水印
let operation2 = NSBlockOperation { () -> Void in
    NSLog("打水印   - %@", NSThread.currentThread())
    NSThread.sleepForTimeInterval(1.0)
}

//3.任務三:上傳圖片
let operation3 = NSBlockOperation { () -> Void in
    NSLog("上傳圖片 - %@", NSThread.currentThread())
    NSThread.sleepForTimeInterval(1.0)
}

//4.設置依賴
operation2.addDependency(operation1)    //任務二依賴任務一
operation3.addDependency(operation2)    //任務三依賴任務二

//5.創(chuàng)建隊列并加入任務
let queue = NSOperationQueue()
queue.addOperations([operation3, operation2, operation1], waitUntilFinished: false)

打印結果

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

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

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

注意:

  • 不能添加相互依賴鸥鹉,會死鎖,比如 A依賴B庶骄,B依賴A毁渗。
  • 可以使用 removeDependency 來解除依賴關系。
  • 可以在不同的隊列之間依賴单刁,反正就是這個依賴是添加到任務身上的灸异,和隊列沒關系。

其他方法

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

  • NSOperation

BOOL executing; //判斷任務是否正在執(zhí)行
BOOL finished; //判斷任務是否完成
-void (^completionBlock)(void); //用來設置完成后需要執(zhí)行的操作
-(void)cancel; //取消任務
-(void)waitUntilFinished; //阻塞當前線程直到此任務執(zhí)行完畢

  • NSOperationQueue

NSUInteger operationCount; //獲取隊列的任務數(shù)
-(void)cancelAllOperations; //取消隊列中所有的任務
-(void)waitUntilAllOperationsAreFinished; //阻塞當前線程直到此隊列中的所有任務執(zhí)行完畢
[queue setSuspended:YES]; // 暫停queue
[queue setSuspended:NO]; // 繼續(xù)queue

好啦羔飞,到這里差不多就講完了肺樟。當然,我講的并不完整逻淌,可能有一些知識我并沒有講到么伯,但作為常用方法,這些已經足夠了卡儒。不過我在這里只是告訴你了一些方法的功能田柔,只是怎么把他們用到合適的地方俐巴,就需要多多實踐了。下面我會說一些關于多線程的案例硬爆,是大家更加什么地了解欣舵。

其他用法

在這部分,我會說一些和多線程知識相關的案例摆屯,可能有些很簡單邻遏,大家早都知道的,不過因為這篇文章講的是多線程嘛虐骑,所以應該盡可能的全面嘛准验。還有就是,我會盡可能的使用多種方法實現(xiàn)廷没,讓大家看看其中的區(qū)別糊饱。

線程同步

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

  • 互斥鎖 :給需要同步的代碼塊加一個互斥鎖,就可以保證每次只有一個線程訪問此代碼塊狭归。

OBJECTIVE-C

@synchronized(self) { //需要執(zhí)行的代碼塊}
SWIFT
objc_sync_enter(self)//需要執(zhí)行的代碼塊objc_sync_exit(self)
  • 同步執(zhí)行 :我們可以使用多線程的知識夭坪,把多個線程都要執(zhí)行此段代碼添加到同一個串行隊列,這樣就實現(xiàn)了線程同步的概念过椎。當然這里可以使用GCD
    NSOperation兩種方案室梅,我都寫出來。
OBJECTIVE-C
 //GCD
  //需要一個全局變量queue疚宇,要讓所有線程的這個操作都加到一個queue中
  dispatch_sync(queue, ^{
      NSInteger ticket = lastTicket;
      [NSThread sleepForTimeInterval:0.1];
      NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
      ticket -= 1;
      lastTicket = ticket;
  });


  //NSOperation & NSOperationQueue
  //重點:1. 全局的 NSOperationQueue, 所有的操作添加到同一個queue中
  //       2. 設置 queue 的 maxConcurrentOperationCount 為 1
  //       3. 如果后續(xù)操作需要Block中的結果亡鼠,就需要調用每個操作的waitUntilFinished,阻塞當前線程敷待,一直等到當前操作完成间涵,才允許執(zhí)行后面的。waitUntilFinished 要在添加到隊列之后榜揖!

  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ù)要做的事
SWIFT

這里的 swift 代碼勾哩,我就不寫了,因為每句都一樣举哟,只是語法不同而已钳幅,照著 OC 的代碼就能寫出 Swift 的。這篇文章已經老長老長了炎滞,我就不浪費篇幅了,又不是高中寫作文诬乞。

延遲執(zhí)行

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

  • perform
OBJECTIVE-C
// 3秒后自動調用self的run:方法,并且傳遞參數(shù):@"abc" [self performSelector:@selector(run:) withObject:@"abc" afterDelay:3];
SWIFT

之前就已經說過森瘪,Swift 里去掉了這個方法牡属。

  • GCD
    可以使用 GCD 中的dispatch_after
    方法,OC 和 Swift 都可以使用扼睬,這里只寫 OC 的逮栅,Swift 的是一樣的。
OBJECTIVE-C
// 創(chuàng)建隊列
// 創(chuàng)建隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 設置延時窗宇,單位秒
double delay = 3; 

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{
    // 3秒后需要執(zhí)行的任務
});
  • NSTimer
    NSTimer 是iOS中的一個計時器類措伐,除了延遲執(zhí)行還有很多用法,不過這里直說延遲執(zhí)行的用法军俊。同樣只寫 OC 版的侥加,Swift 也是相同的。
OBJECTIVE-C
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc" repeats:NO];

單例模式

至于什么是單例模式粪躬,我也不多說担败,我只說說一般怎么實現(xiàn)。在 Objective-C 中镰官,實現(xià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

這里之所以將單例模式,是因為其中用到了 GCD 的 dispatch_once 方法警检。下面看 Swift 中的單例模式孙援,在Swift中單例模式非常簡單!想知道怎么從 OC 那么復雜的方法變成下面的寫法的扇雕,請看這里

SWIFT
class Tool: NSObject {
    static let sharedTool = Tool()

    // 私有化構造方法拓售,阻止其他對象使用這個類的默認的'()'構造方法
    private override init() {}
}

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

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

  • NSThread
//Objective-C
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];

//Swift
//swift 取消了 performSelector 方法哨苛。
  • GCD
//Objective-C
dispatch_async(dispatch_get_main_queue(), ^{

});

//Swift
dispatch_async(dispatch_get_main_queue(), { () -> Void in

})
  • NSOperationQueue
//Objective-C
[[NSOperationQueue mainQueue] addOperationWithBlock:^{

}];

//Swift
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in

}

其他

dispatch_suspend != 立即停止隊列的運行

dispatch_suspend鸽凶,dispatch_resume提供了“掛起、恢復”隊列的功能建峭,簡單來說玻侥,就是可以暫停、恢復隊列上的任務亿蒸。但是這里的“掛起”凑兰,并不能保證可以立即停止隊列上正在運行的block掌桩,看如下例子:

dispatch_queue_t queue = dispatch_queue_create(“me.tutuge.test.gcd”, DISPATCH_QUEUE_SERIAL);

//提交第一個block,延時5秒打印姑食。 
dispatch_async(queue, ^{ 
[NSThread sleepForTimeInterval:5]; 
NSLog(@”After 5 seconds…”); 
});

//提交第二個block波岛,也是延時5秒打印 
dispatch_async(queue, ^{ 
[NSThread sleepForTimeInterval:5]; 
NSLog(@”After 5 seconds again…”); 
});

//延時一秒 
NSLog(@”sleep 1 second…”); 
[NSThread sleepForTimeInterval:1];

//掛起隊列 
NSLog(@”suspend…”); 
dispatch_suspend(queue);

//延時10秒 
NSLog(@”sleep 10 second…”); 
[NSThread sleepForTimeInterval:10];

//恢復隊列 
NSLog(@”resume…”); 
dispatch_resume(queue); 

運行結果如下:

2015-04-01 00:32:09.903 GCDTest[47201:1883834] sleep 1 second… 
2015-04-01 00:32:10.910 GCDTest[47201:1883834] suspend… 
2015-04-01 00:32:10.910 GCDTest[47201:1883834] sleep 10 second… 
2015-04-01 00:32:14.908 GCDTest[47201:1883856] After 5 seconds… 
2015-04-01 00:32:20.911 GCDTest[47201:1883834] resume… 
2015-04-01 00:32:25.912 GCDTest[47201:1883856] After 5 seconds again… 

可知,在dispatch_suspend掛起隊列后音半,第一個block還是在運行则拷,并且正常輸出。
結合文檔曹鸠,我們可以得知煌茬,dispatch_suspend并不會立即暫停正在運行的block,而是在當前block執(zhí)行完成后物延,暫停后續(xù)的block執(zhí)行宣旱。

所以下次想暫停正在隊列上運行的block時,還是不要用dispatch_suspend了吧~

“同步”的dispatch_apply

dispatch_apply的作用是在一個隊列(串行或并行)上“運行”多次block叛薯,其實就是簡化了用循環(huán)去向隊列依次添加block任務浑吟。但是我個人覺得這個函數(shù)就是個“坑”,先看看如下代碼運行結果:

//創(chuàng)建異步串行隊列 
dispatch_queue_t queue = dispatch_queue_create(“me.tutuge.test.gcd”, DISPATCH_QUEUE_SERIAL);

//運行block3次 
dispatch_apply(3, queue, ^(size_t i) { 
NSLog(@”apply loop: %zu”, i); 
});

//打印信息 
NSLog(@”After apply”); 

運行的結果是:

2015-04-01 00:55:40.854 GCDTest[47402:1893289] apply loop: 0 
2015-04-01 00:55:40.856 GCDTest[47402:1893289] apply loop: 1 
2015-04-01 00:55:40.856 GCDTest[47402:1893289] apply loop: 2 
2015-04-01 00:55:40.856 GCDTest[47402:1893289] After apply 

看耗溜,明明是提交到異步的隊列去運行组力,但是“After apply”居然在apply后打印,也就是說抖拴,dispatch_apply將外面的線程(main線程)“阻塞”了燎字!

查看官方文檔,dispatch_apply確實會“等待”其所有的循環(huán)運行完畢才往下執(zhí)行=阿宅。=候衍,看來要小心使用了。

避免死鎖洒放!

dispatch_sync導致的死鎖

涉及到多線程的時候蛉鹿,不可避免的就會有“死鎖”這個問題,在使用GCD時往湿,往往一不小心妖异,就可能造成死鎖,看看下面的“死鎖”例子:

//在main線程使用“同步”方法提交Block领追,必定會死鎖他膳。 
dispatch_sync(dispatch_get_main_queue(), ^{ 
NSLog(@”I am block…”); 
}); 

你可能會說,這么低級的錯誤绒窑,我怎么會犯棕孙,那么,看看下面的:

(void)updateUI1 { 
dispatch_sync(dispatch_get_main_queue(), ^{ 
NSLog(@”Update ui 1”);

//死鎖!
[self updateUI2];
}); 
}

(void)updateUI2 { 
dispatch_sync(dispatch_get_main_queue(), ^{ 
NSLog(@”Update ui 2”); 
}); 
} 

在你不注意的時候蟀俊,嵌套調用可能就會造成死鎖分歇!所以為了“世界和平”=。=欧漱,我們還是少用dispatch_sync吧。

dispatch_apply導致的死鎖葬燎!

dispatch_apply導致的死鎖误甚?。谱净。窑邦。是的,前一節(jié)講到壕探,dispatch_apply會等循環(huán)執(zhí)行完成冈钦,這不就差不多是阻塞了嗎±钋耄看如下例子:

dispatch_queue_t queue = dispatch_queue_create(“me.tutuge.test.gcd”, DISPATCH_QUEUE_SERIAL);

dispatch_apply(3, queue, ^(size_t i) { 
NSLog(@”apply loop: %zu”, i);

//再來一個dispatch_apply瞧筛!死鎖!      
dispatch_apply(3, queue, ^(size_t j) {
    NSLog(@"apply loop inside %zu", j);
});
}); 

這段代碼只會輸出“apply loop: 1”导盅。较幌。。就沒有然后了=白翻。=

所以乍炉,一定要避免dispatch_apply的嵌套調用。

靈活使用dispatch_group

很多時候我們需要等待一系列任務(block)執(zhí)行完成滤馍,然后再做一些收尾的工作岛琼。如果是有序的任務,可以分步驟完成的巢株,直接使用串行隊列就行槐瑞。但是如果是一系列并行執(zhí)行的任務呢?這個時候纯续,就需要dispatch_group幫忙了~總的來說随珠,dispatch_group的使用分如下幾步:

  1. 創(chuàng)建dispatch_group_t
  1. 添加任務(block)
  2. 添加結束任務(如清理操作、通知UI等)

下面著重講講在后面兩步猬错。

1. 添加任務

添加任務可以分為以下兩種情況:
(1)自己創(chuàng)建隊列:使用dispatch_group_async窗看。
(2)無法直接使用隊列變量(如使用AFNetworking添加異步任務):使用dispatch_group_enter,dispatch_group_leave倦炒。
自己創(chuàng)建隊列時显沈,當然就用dispatch_group_async函數(shù),簡單有效,簡單例子如下:

//省去創(chuàng)建group拉讯、queue代碼涤浇。。魔慷。

dispatch_group_async(group, queue, ^{ 
//Do you work… 
}); 
當你無法直接使用隊列變量時只锭,就無法使用dispatch_group_async了,下面以使用AFNetworking時的情況:

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

//Enter group 
dispatch_group_enter(group); 
[manager GET:@”http://www.baidu.com” parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { 
//Deal with result…

//Leave group
dispatch_group_leave(group);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { 
//Deal with error…

//Leave group
dispatch_group_leave(group);
}];
//More request… 

使用dispatch_group_enter院尔,dispatch_group_leave就可以方便的將一系列網(wǎng)絡請求“打包”起來~

2. 添加結束任務

添加結束任務也可以分為兩種情況蜻展,如下:

(1)在當前線程阻塞的同步等待:dispatch_group_wait。
(2)添加一個異步執(zhí)行的任務作為結束任務:dispatch_group_notify

這兩個比較簡單邀摆,就不再貼代碼了=纵顾。=

使用dispatch_barrier_async,dispatch_barrier_sync的注意事項

dispatch_barrier_async的作用就是向某個隊列插入一個block,當目前正在執(zhí)行的block運行完成后栋盹,阻塞這個block后面添加的block施逾,只運行這個block直到完成,然后再繼續(xù)后續(xù)的任務例获,有點“唯我獨尊”的感覺=汉额。=
值得注意的是:
dispatchbarrier(a)sync只在自己創(chuàng)建的并發(fā)隊列上有效,在全局(Global)并發(fā)隊列躏敢、串行隊列上闷愤,效果跟dispatch_(a)sync效果一樣。
既然在串行隊列上跟dispatch_(a)sync效果一樣件余,那就要小心別死鎖讥脐!
dispatch_set_context與dispatch_set_finalizer_f的配合使用
dispatch_set_context可以為隊列添加上下文數(shù)據(jù),但是因為GCD是C語言接口形式的啼器,所以其context參數(shù)類型是“void *”旬渠。也就是說,我們創(chuàng)建context時有如下幾種選擇:

  1. 用C語言的malloc創(chuàng)建context數(shù)據(jù)端壳。
  1. 用C++的new創(chuàng)建類對象告丢。
  2. 用Objective-C的對象,但是要用__bridge等關鍵字轉為Core Foundation對象损谦。

以上所有創(chuàng)建context的方法都有一個必須的要求岖免,就是都要釋放內存!照捡,無論是用free颅湘、delete還是CF的CFRelease,我們都要確保在隊列不用的時候栗精,釋放context的內存闯参,否則就會造成內存泄露瞻鹏。

所以,使用dispatch_set_context的時候鹿寨,最好結合dispatch_set_finalizer_f使用新博,為隊列設置“析構函數(shù)”,在這個函數(shù)里面釋放內存脚草,大致如下:

void cleanStaff(void *context) { 
//釋放context的內存赫悄!

//CFRelease(context);
//free(context);
//delete context;
}

參考

Grand Central Dispatch (GCD) Reference
Concurrency Programming Guide
Using Dispatch Groups to Wait for Multiple Web Services

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市馏慨,隨后出現(xiàn)的幾起案子涩蜘,更是在濱河造成了極大的恐慌,老刑警劉巖熏纯,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異粤策,居然都是意外死亡樟澜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門叮盘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秩贰,“玉大人,你說我怎么就攤上這事柔吼《痉眩” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵愈魏,是天一觀的道長觅玻。 經常有香客問我,道長培漏,這世上最難降的妖魔是什么溪厘? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮牌柄,結果婚禮上畸悬,老公的妹妹穿的比我還像新娘。我一直安慰自己珊佣,他們只是感情好蹋宦,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咒锻,像睡著了一般冷冗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上虫碉,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天贾惦,我揣著相機與錄音,去河邊找鬼。 笑死须板,一個胖子當著我的面吹牛碰镜,可吹牛的內容都是我干的。 我是一名探鬼主播习瑰,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼绪颖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了甜奄?” 一聲冷哼從身側響起柠横,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎课兄,沒想到半個月后牍氛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡烟阐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年搬俊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜒茄。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡唉擂,死狀恐怖,靈堂內的尸體忽然破棺而出檀葛,到底是詐尸還是另有隱情玩祟,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布屿聋,位于F島的核電站空扎,受9級特大地震影響,放射性物質發(fā)生泄漏润讥。R本人自食惡果不足惜勺卢,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望象对。 院中可真熱鬧黑忱,春花似錦、人聲如沸勒魔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冠绢。三九已至抚吠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弟胀,已是汗流浹背楷力。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工喊式, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人萧朝。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓岔留,卻偏偏與公主長得像,于是被迫代替她去往敵國和親检柬。 傳聞我的和親對象是個殘疾皇子献联,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容

  • 在這篇文章中,我將為你整理一下 iOS 開發(fā)中幾種多線程方案何址,以及其使用方法和注意事項里逆。當然也會給出幾種多線程的案...
    張戰(zhàn)威ican閱讀 601評論 0 0
  • NSThread 第一種:通過NSThread的對象方法 NSThread *thread = [[NSThrea...
    攻城獅GG閱讀 789評論 0 3
  • 一、前言 上一篇文章iOS多線程淺匯-原理篇中整理了一些有關多線程的基本概念用爪。本篇博文介紹的是iOS中常用的幾個多...
    nuclear閱讀 2,046評論 6 18
  • 在這篇文章中偎血,我將為你整理一下 iOS 開發(fā)中幾種多線程方案班眯,以及其使用方法和注意事項。當然也會給出幾種多線程的案...
    伯恩的遺產閱讀 274,351評論 251 2,331
  • 喜歡撩你 不代表念你 和你聊天 不代表想你 主動找你 不代表愛你 但一個不理你 不發(fā)消息 不帶你出去的人 一定不愛你
    張夢妮閱讀 87評論 4 5