關於iOS多線程的總結

關於iOS多線程的總結

 

在這篇文章中,我將爲你整理一下 iOS 開發中幾種多線程方案,以及其使用方法和注意事項。固然也會給出幾種多線程的案例,在實際使用中感覺它們的區別。還有一點須要說明的是,這篇文章將會使用 Swift 和 Objective-c 兩種語言講解,雙語幼兒園。OK,let's begin!swift

概述

這篇文章中,我不會說多線程是什麼、線程和進程的區別、多線程有什麼用,固然我也不會說什麼是串行、什麼是並行等問題,這些咱們應該都知道的。api

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

  • Pthreads
  • NSThread
  • GCD
  • NSOperation & NSOperationQueue

因此接下來,我會一一講解這些方案的使用方法和一些案例。在將這些內容的時候,我也會順帶說一些多線程周邊產品。好比: 線程同步、 延時執行、 單例模式 等等。安全

Pthreads

其實這個方案不用說的,只是拿來充個數,爲了讓你們瞭解一下就行了。百度百科裏是這麼說的:服務器

POSIX線程(POSIX threads),簡稱Pthreads,是線程的POSIX標準。該標準定義了建立和操縱線程的一整套API。在類Unix操做系統(Unix、Linux、Mac OS X等)中,都使用Pthreads做爲操做系統的線程。多線程

簡單地說,這是一套在不少操做系統上都通用的多線程API,因此移植性很強(然並卵),固然在 iOS 中也是能夠的。不過這是基於 c語言 的框架,使用起來這酸爽!感覺一下:閉包

OBJECTIVE-C

固然第一步要包含頭文件併發

#import <pthread.h>框架

而後建立線程,並執行任務異步

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { pthread_t thread; //建立一個線程並自動執行 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)}

看代碼就會發現他須要 c語言函數,這是比較蛋疼的,更蛋疼的是你須要手動處理線程的各個狀態的轉換即管理生命週期,好比,這段代碼雖然建立了一個線程,但並無銷燬。

SWIFT

很遺憾,在我目前的 swift1.2 中沒法執行這套方法,緣由是這個函數須要傳入一個函數指針 CFunctionPointer<T> 類型,可是目前 swift 沒法將方法轉換成此類型。據說 swift 2.0 引入一個新特性 @convention(c), 能夠完成 Swift 方法轉換成 c 語言指針的。在這裏能夠看到

那麼,Pthreads 方案的多線程我就介紹這麼多,畢竟作 iOS 開發幾乎不可能用到。可是若是你感興趣的話,或者說想要本身實現一套多線程方案,從底層開始定製,那麼能夠去搜一下相關資料。

NSThread

這套方案是通過蘋果封裝後的,而且徹底面向對象的。因此你能夠直接操控線程對象,很是直觀和方便。可是,它的生命週期仍是須要咱們手動管理,因此這套方案也是偶爾用用,好比 [NSThread currentThread],它能夠獲取當前線程類,你就能夠知道當前線程的各類屬性,用於調試十分方便。下面來看看它的一些用法。

建立並啓動

  • 先建立線程類,再啓動

    OBJECTIVE-C
    // 建立 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil]; // 啓動 [thread start];
    SWIFT
    //建立 let thread = NSThread(target: self, selector: "run:", object: nil) //啓動 thread.start()
  • 建立並自動啓動

    OBJECTIVE-C
    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
    SWIFT
    NSThread.detachNewThreadSelector("run:", toTarget: self, withObject: nil)
  • 使用 NSObject 的方法建立並自動啓動

    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.

其餘方法

除了建立啓動外,NSThread 還以不少方法,下面我列舉一些常見的方法,固然我列舉的並不完整,更多方法你們能夠去類的定義裏去看。

OBJECTIVE-C
//取消線程 - (void)cancel; //啓動線程 - (void)start; //判斷某個線程的狀態的屬性 @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, 畢竟它還不夠智能,不能優雅地處理多線程中的其餘高級概念。因此接下來要說的內容纔是重點。

GCD

Grand Central Dispatch,聽名字就霸氣。它是蘋果爲多核的並行運算提出的解決方案,因此會自動合理地利用更多的CPU內核(好比雙核、四核),最重要的是它會自動管理線程的生命週期(建立線程、調度任務、銷燬線程),徹底不須要咱們管理,咱們只須要告訴幹什麼就行。同時它使用的也是 c語言,不過因爲使用了 Block(Swift裏叫作閉包),使得使用起來更加方便,並且靈活。因此基本上你們都使用 GCD 這套方案,老小咸宜,實在是居家旅行、殺人滅口,必備良藥。很差意思,有點中二,我們繼續。

任務和隊列

在 GCD 中,加入了兩個很是重要的概念: 任務 和 隊列

  • 任務:即操做,你想要幹什麼,說白了就是一段代碼,在 GCD 中就是一個 Block,因此添加任務十分方便。任務有兩種執行方式: 同步執行 和 異步執行,他們之間的區別是 是否會建立新的線程

    同步執行只要是同步執行的任務,都會在當前線程執行,不會另開線程。

    異步執行只要是異步執行的任務,都會另開線程,在別的線程執行。

    更新
    這裏說的並不許確,同步(sync) 和 異步(async) 的主要區別在於會不會阻塞當前線程,直到 Block 中的任務執行完畢!
    若是是 同步(sync) 操做,它會阻塞當前線程並等待 Block 中的任務執行完畢,而後當前線程纔會繼續往下運行。
    若是是 異步(async)操做,當前線程會直接往下執行,它不會阻塞當前線程。

  • 隊列:用於存聽任務。一共有兩種隊列, 串行隊列 和 並行隊列

    串行隊列 中的任務會根據隊列的定義 FIFO 的執行,一個接一個的先進先出的進行執行。

    更新:放到串行隊列的任務,GCD 會 FIFO(先進先出) 地取出來一個,執行一個,而後取下一個,這樣一個一個的執行。

    並行隊列 中的任務 根據同步或異步有不一樣的執行方式。

    更新:放到並行隊列的任務,GCD 也會 FIFO的取出來,但不一樣的是,它取出來一個就會放到別的線程,而後再取出來一個又放到另外一個的線程。這樣因爲取的動做很快,忽略不計,看起來,全部的任務都是一塊兒執行的。不過須要注意,GCD 會根據系統資源控制並行的數量,因此若是任務不少,它並不會讓全部任務同時執行。

雖然很繞,但請看下錶:

  同步執行 異步執行
串行隊列 當前線程,一個一個執行 其餘線程,一個一個執行
並行隊列 當前線程,一個一個執行 開不少線程,一塊兒執行

建立隊列

  • 主隊列:這是一個特殊的 串行隊列。什麼是主隊列,你們都知道吧,它用於刷新 UI,任何須要刷新 UI 的工做都要在主隊列執行,因此通常耗時的任務都要放到別的線程執行。

    //OBJECTIVE-C dispatch_queue_t queue = ispatch_get_main_queue(); //SWIFT let queue = ispatch_get_main_queue()
  • 本身建立的隊列凡是本身建立的隊列都是 串行隊列 其中第一個參數是標識符,用於 DEBUG 的時候標識惟一的隊列,能夠爲空。你們能夠看xcode的文檔查看參數意義。

    更新:本身能夠建立 串行隊列, 也能夠建立 並行隊列。看下面的代碼(代碼已更新),它有兩個參數,第一個上面已經說了,第二個纔是最重要的。
    第二個參數用來表示建立的隊列是串行的仍是並行的,傳入 DISPATCH_QUEUE_SERIAL或 NULL 表示建立串行隊列。傳入 DISPATCH_QUEUE_CONCURRENT 表示建立並行隊列。

    //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)
  • 全局並行隊列這應該是惟一一個並行隊列, 只要是並行任務通常都加入到這個隊列。這是系統提供的一個併發隊列。

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

建立任務

  • 同步任務: 不會另開線程 改:會阻塞當前線程 (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} ,而後主線程就卡死了,你能夠在界面上放一個按鈕,你就會發現點不了了。
解釋:
同步任務會阻塞當前線程,而後把 Block 中的任務放到指定的隊列中執行,只有等到 Block 中的任務完成後纔會讓當前線程繼續往下運行。
那麼這裏的步驟就是:打印完第一句後,dispatch_sync 當即阻塞當前的主線程,而後把 Block 中的任務放到 main_queue 中,但是 main_queue 中的任務會被取出來放到主線程中執行,但主線程這個時候已經被阻塞了,因此 Block 中的任務就不能完成,它不完成,dispatch_sync 就會一直阻塞主線程,這就是死鎖現象。致使主線程一直卡死。

示例二:
如下代碼會產生什麼結果?

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以後 - %@ 沒有被打印出來!這是爲何呢?咱們再來分析一下:

分析:
咱們按執行順序一步步來哦:

  1. 使用 DISPATCH_QUEUE_SERIAL 這個參數,建立了一個 串行隊列
  2. 打印出 以前 - %@ 這句。
  3. dispatch_async 異步執行,因此當前線程不會被阻塞,因而有了兩條線程,一條當前線程繼續往下打印出 以後 - %@這句, 另外一臺執行 Block 中的內容打印 sync以前 - %@ 這句。由於這兩條是並行的,因此打印的前後順序無所謂。
  4. 注意,高潮來了。如今的狀況和上一個例子同樣了。dispatch_sync同步執行,因而它所在的線程會被阻塞,一直等到 sync 裏的任務執行完纔會繼續往下。因而 sync就高興的把本身 Block 中的任務放到 queue 中,可誰想 queue 是一個串行隊列,一次執行一個任務,因此 sync 的 Block 必須等到前一個任務執行完畢,可萬萬沒想到的是 queue 正在執行的任務就是被 sync 阻塞了的那個。因而又發生了死鎖。因此 sync 所在的線程被卡死了。剩下的兩句代碼天然不會打印。

隊列組

隊列組能夠將不少隊列添加到一個組裏,這樣作的好處是,當這個組裏全部的任務都執行完了,隊列組會經過一個方法通知咱們。下面是使用方法,這是一個很實用的功能。

OBJECTIVE-C
//1.建立隊列組 dispatch_group_t group = dispatch_group_create(); //2.建立隊列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //3.屢次使用隊列組的方法執行任務, 只有異步方法 //3.1.執行3次循環 dispatch_group_async(group, queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@"group-01 - %@", [NSThread currentThread]); } }); //3.2.主隊列執行8次循環 dispatch_group_async(group, dispatch_get_main_queue(), ^{ for (NSInteger i = 0; i < 8; i++) { NSLog(@"group-02 - %@", [NSThread currentThread]); } }); //3.3.執行5次循環 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.建立隊列組 let group = dispatch_group_create() //2.建立隊列 let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) //3.屢次使用隊列組的方法執行任務, 只有異步方法 //3.1.執行3次循環 dispatch_group_async(group, queue) { () -> Void in for _ in 0..<3 { NSLog("group-01 - %@", NSThread.currentThread()) } } //3.2.主隊列執行8次循環 dispatch_group_async(group, dispatch_get_main_queue()) { () -> Void in for _ in 0..<8 { NSLog("group-02 - %@", NSThread.currentThread()) } } //3.3.執行5次循環 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 參數本身建立的 queue 時,這個方法會阻塞這個queue注意是阻塞 queue ,而不是阻塞當前線程),一直等到這個 queue 中排在它前面的任務都執行完成後纔會開始執行本身,本身執行完畢後,再會取消阻塞,使這個 queue 中排在它後面的任務繼續執行。
    若是你傳入的是其餘的 queue, 那麼它就和 dispatch_async 同樣了。

  • func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):
    這個方法的使用和上一個同樣,傳入 自定義的併發隊列(DISPATCH_QUEUE_CONCURRENT),它和上一個方法同樣的阻塞 queue,不一樣的是 這個方法還會 阻塞當前線程
    若是你傳入的是其餘的 queue, 那麼它就和 dispatch_sync 同樣了。

NSOperation和NSOperationQueue

NSOperation 是蘋果公司對 GCD 的封裝,徹底面向對象,因此使用起來更好理解。 你們能夠看到 NSOperation 和 NSOperationQueue 分別對應 GCD 的 任務 和 隊列 。操做步驟也很好理解:

  1. 將要執行的任務封裝到一個 NSOperation 對象中。
  2. 將此任務添加到一個 NSOperationQueue 對象中。

而後系統就會自動在執行任務。至於同步仍是異步、串行仍是並行請繼續往下看:

添加任務

值得說明的是,NSOperation 只是一個抽象類,因此不能封裝任務。但它有 2 個子類用於封裝任務。分別是:NSInvocationOperation 和 NSBlockOperation 。建立一個 Operation 後,須要調用 start 方法來啓動任務,它會 默認在當前隊列同步執行。固然你也能夠在中途取消一個任務,只須要調用其 cancel 方法便可。

  • NSInvocationOperation : 須要傳入一個方法名。

    OBJECTIVE-C
    //1.建立NSInvocationOperation對象 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil]; //2.開始執行 [operation start];
    SWIFT

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

  • NSBlockOperation

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

    以前說過這樣的任務,默認會在當前線程執行。可是 NSBlockOperation 還有一個方法:addExecutionBlock: ,經過這個方法能夠給 Operation 添加多個執行 Block。這樣 Operation 中的任務 會併發執行,它會 在主線程和其它的多個線程 執行這些任務,注意下面的打印結果:

    OBJECTIVE-C
    //1.建立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.建立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() 方法以前執行,不然就會報錯:

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

    NOTE:你們可能發現了一個問題,爲何我在 Swift 裏打印輸出使用 NSLog() 而不是 println() 呢?緣由是使用 print() / println() 輸出的話,它會簡單地使用 流(stream) 的概念,學過 C++ 的都知道。它會把須要輸出的每一個字符一個一個的輸出到控制檯。普通使用並無問題,但是當多線程同步輸出的時候問題就來了,因爲不少 println() 同時打印,就會致使控制檯上的字符混亂的堆在一塊兒,而NSLog() 就沒有這個問題。究竟是什麼樣子的呢?你能夠把上面 NSLog() 改成 println() ,而後一試便知。 更多 NSLog() 與 println() 的區別看這裏

  • 自定義Operation

    除了上面的兩種 Operation 之外,咱們還能夠自定義 Operation。自定義 Operation 須要繼承 NSOperation 類,並實現其 main() 方法,由於在調用 start() 方法的時候,內部會調用 main() 方法完成相關邏輯。因此若是以上的兩個類沒法知足你的慾望的時候,你就須要自定義了。你想要實現什麼功能均可以寫在裏面。除此以外,你還須要實現 cancel() 在內的各類方法。因此這個功能提供給高級玩家,我在這裏就不說了,等我須要用到時在研究它,到時候可能會再作更新。

建立隊列

看過上面的內容就知道,咱們能夠調用一個 NSOperation 對象的 start() 方法來啓動這個任務,可是這樣作他們默認是 同步執行 的。就算是 addExecutionBlock 方法,也會在當前線程和其餘線程 中執行,也就是說仍是會佔用當前線程。這是就要用到隊列 NSOperationQueue 了。並且,按類型來講的話一共有兩種類型:主隊列、其餘隊列。只要添加到隊列,會自動調用任務的 start() 方法

  • 主隊列

    細心的同窗就會發現,每套多線程方案都會有一個主線程(固然啦,說的是iOS中,像 pthread 這種多系統的方案並無,由於 UI線程 理論須要每種操做系統本身定製)。這是一個特殊的線程,必須串行。因此添加到主隊列的任務都會一個接一個地排着隊在主線程處理。

    //OBJECTIVE-C NSOperationQueue *queue = [NSOperationQueue mainQueue]; //SWIFT let queue = NSOperationQueue.mainQueue()
  • 其餘隊列

    由於主隊列比較特殊,因此會單獨有一個類方法來得到主隊列。那麼經過初始化產生的隊列就是其餘隊列了,由於只有這兩種隊列,除了主隊列,其餘隊列就不須要名字了。

    注意:其餘隊列的任務會在其餘線程並行執行。

    OBJECTIVE-C
    //1.建立一個其餘隊列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //2.建立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.建立其餘隊列 let queue = NSOperationQueue() //2.建立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, 這時應該發問了,你們將 NSOperationQueue 與 GCD的隊列 相比較就會發現,這裏沒有串行隊列,那若是我想要10個任務在其餘線程串行的執行怎麼辦?

這就是蘋果封裝的妙處,你不用管串行、並行、同步、異步這些名詞。NSOperationQueue有一個參數 maxConcurrentOperationCount 最大併發數,用來設置最多可讓多少個任務同時執行。當你把它設置爲 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.建立隊列並加入任務 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.建立隊列並加入任務 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; //判斷任務是否正在執行

    BOOL finished; //判斷任務是否完成

    void (^completionBlock)(void); //用來設置完成後須要執行的操做

    - (void)cancel; //取消任務

    - (void)waitUntilFinished; //阻塞當前線程直到此任務執行完畢

  • NSOperationQueue

    NSUInteger operationCount; //獲取隊列的任務數

    - (void)cancelAllOperations; //取消隊列中全部的任務

    - (void)waitUntilAllOperationsAreFinished; //阻塞當前線程直到此隊列中的全部任務執行完畢

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

    [queue setSuspended:NO]; // 繼續queue

好啦,到這裏差很少就講完了。固然,我講的並不完整,可能有一些知識我並無講到,但做爲經常使用方法,這些已經足夠了。不過我在這裏只是告訴你了一些方法的功能,只是怎麼把他們用到合適的地方,就須要多多實踐了。下面我會說一些關於多線程的案例,是你們更加什麼地瞭解。

其餘用法

在這部分,我會說一些和多線程知識相關的案例,可能有些很簡單,你們早都知道的,不過由於這篇文章講的是多線程嘛,因此應該儘量的全面嘛。還有就是,我會盡量的使用多種方法實現,讓你們看看其中的區別。

線程同步

所謂線程同步就是爲了防止多個線程搶奪同一個資源形成的數據安全問題,所採起的一種措施。固然也有不少實現方法,請往下看:

  • 互斥鎖 :給須要同步的代碼塊加一個互斥鎖,就能夠保證每次只有一個線程訪問此代碼塊。

    OBJECTIVE-C
    @synchronized(self) { //須要執行的代碼塊 }
    SWIFT
    objc_sync_enter(self) //須要執行的代碼塊 objc_sync_exit(self)
  • 同步執行 :咱們可使用多線程的知識,把多個線程都要執行此段代碼添加到同一個串行隊列,這樣就實現了線程同步的概念。固然這裏可使用 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. 若是後續操做須要Block中的結果,就須要調用每一個操做的waitUntilFinished,阻塞當前線程,一直等到當前操做完成,才容許執行後面的。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]; //後續要作的事
SWIFT

這裏的 swift 代碼,我就不寫了,由於每句都同樣,只是語法不一樣而已,照着 OC 的代碼就能寫出 Swift 的。這篇文章已經老長老長了,我就不浪費篇幅了,又不是高中寫做文。

延遲執行

所謂延遲執行就是延時一段時間再執行某段代碼。下面說一些經常使用方法。

  • perform

    OBJECTIVE-C
    // 3秒後自動調用self的run:方法,而且傳遞參數:@"abc" [self performSelector:@selector(run:) withObject:@"abc" afterDelay:3];
    SWIFT
    以前就已經說過,Swift 裏去掉了這個方法。
  • GCD

    可使用 GCD 中的 dispatch_after 方法,OC 和 Swift 均可以使用,這裏只寫 OC 的,Swift 的是同樣的。

    OBJECTIVE-C
    // 建立隊列 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秒後須要執行的任務 });
  • NSTimer

    NSTimer 是iOS中的一個計時器類,除了延遲執行還有不少用法,不過這裏直說延遲執行的用法。一樣只寫 OC 版的,Swift 也是相同的。

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

單例模式

至於什麼是單例模式,我也很少說,我只說說通常怎麼實現。在 Objective-C 中,實現單例的方法已經很具體了,雖然有別的方法,可是通常都是用一個標準的方法了,下面來看看。

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 }

總結

好的吧,總算寫完了,純手敲6k多字,感動死我了。花了兩天,時間跨度有點大,因此可能有些地方上段不接下段或者有的地方不完整,若是你看着比較費力或者有什麼地方有問題,均可以在評論區告訴我,我會及時修改的。固然啦,多線程的東西也不止這些,題目也就只是個題目,不要當真。想要了解更多的東西,還得本身去網上挖掘相關資料。多看看官方文檔。實在是編不下去了,你們好好看~。對了,看我寫的這麼賣力,不打賞的話得點個喜歡也是極好的。

相關文章
相關標籤/搜索