關於iOS多執行緒,你看我就夠了

發表於2015-08-02

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

概述

這篇文章中,我不會說多執行緒是什麼、執行緒和程式的區別、多執行緒有什麼用,當然我也不會說什麼是序列、什麼是並行等問題,這些我們應該都知道的。

在 iOS 中其實目前有 4 套多執行緒方案,他們分別是:

  • 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>

然後建立執行緒,並執行任務

列印輸出:

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

    SWIFT

  • 建立並自動啟動
    OBJECTIVE-C

    SWIFT

使用 NSObject 的方法建立並自動啟動

OBJECTIVE-C

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

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 的工作都要在主佇列執行,所以一般耗時的任務都要放到別的執行緒執行。
  • 自己建立的佇列凡是自己建立的佇列都是 序列佇列 其中第一個引數是識別符號,用於 DEBUG 的時候標識唯一的佇列,可以為空。大家可以看xcode的文件檢視引數意義。

    更新:自己可以建立 序列佇列, 也可以建立 並行佇列。看下面的程式碼(程式碼已更新),它有兩個引數,第一個上面已經說了,第二個才是最重要的。
    第二個引數用來表示建立的佇列是序列的還是並行的,傳入 DISPATCH_QUEUE_SERIAL或 NULL 表示建立序列佇列。傳入 DISPATCH_QUEUE_CONCURRENT 表示建立並行佇列。

  • 全域性並行佇列這應該是唯一一個並行佇列, 只要是並行任務一般都加入到這個佇列。這是系統提供的一個併發佇列。

建立任務

  • 同步任務: 不會另開執行緒 改:會阻塞當前執行緒 (SYNC)
    OBJECTIVE-C

    SWIFT

  • 非同步任務:會另開執行緒 改:不會阻塞當前執行緒 (ASYNC)
    OBJECTIVE-C

    SWIFT

更新
為了更好的理解同步和非同步,和各種佇列的使用,下面看兩個示例:

示例一:
以下程式碼在主執行緒呼叫,結果是什麼?

答案:
只會列印第一句:之前 - <NSThread: 0x7fb3a9e16470>{number = 1, name = main} ,然後主執行緒就卡死了,你可以在介面上放一個按鈕,你就會發現點不了了。
解釋:
同步任務會阻塞當前執行緒,然後把 Block 中的任務放到指定的佇列中執行,只有等到 Block 中的任務完成後才會讓當前執行緒繼續往下執行。
那麼這裡的步驟就是:列印完第一句後,dispatch_sync 立即阻塞當前的主執行緒,然後把 Block 中的任務放到 main_queue 中,可以 main_queue 中的任務會被取出來放到主執行緒中執行,但主執行緒這個時候已經被阻塞了,所以 Block 中的任務就不能完成,它不完成,dispatch_sync 就會一直阻塞主執行緒,這就是死鎖現象。導致主執行緒一直卡死。

示例二:
以下程式碼會產生什麼結果?

答案:
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

SWIFT

列印結果

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

    SWIFT

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

  • NSBlockOperation
    OBJECTIVE-C

    SWIFT

    之前說過這樣的任務,預設會在當前執行緒執行。但是 NSBlockOperation 還有一個方法:addExecutionBlock: ,通過這個方法可以給 Operation 新增多個執行 Block。這樣 Operation 中的任務 會併發執行,它會 在主執行緒和其它的多個執行緒 執行這些任務,注意下面的列印結果:

    OBJECTIVE-C

    SWIFT

    列印輸出

    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

    SWIFT

    列印輸出

    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

SWIFT

列印結果

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

    SWIFT

  • 同步執行 :我們可以使用多執行緒的知識,把多個執行緒都要執行此段程式碼新增到同一個序列佇列,這樣就實現了執行緒同步的概念。當然這裡可以使用 GCD 和 NSOperation兩種方案,我都寫出來。
    OBJECTIVE-C

SWIFT

這裡的 swift 程式碼,我就不寫了,因為每句都一樣,只是語法不同而已,照著 OC 的程式碼就能寫出 Swift 的。這篇文章已經老長老長了,我就不浪費篇幅了,又不是高中寫作文。

延遲執行

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

  • perform
    OBJECTIVE-C

    SWIFT

  • GCD可以使用 GCD 中的 dispatch_after 方法,OC 和 Swift 都可以使用,這裡只寫 OC 的,Swift 的是一樣的。
    OBJECTIVE-C

  • NSTimerNSTimer 是iOS中的一個計時器類,除了延遲執行還有很多用法,不過這裡直說延遲執行的用法。同樣只寫 OC 版的,Swift 也是相同的。
    OBJECTIVE-C

單例模式

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

OBJECTIVE-C

這裡之所以將單例模式,是因為其中用到了 GCD 的 dispatch_once 方法。下面看 Swift 中的單例模式,在Swift中單例模式非常簡單!想知道怎麼從 OC 那麼複雜的方法變成下面的寫法的,請看這裡

SWIFT

從其他執行緒回到主執行緒的方法

我們都知道在其他執行緒操作完成後必須到主執行緒更新UI。所以,介紹完所有的多執行緒方案後,我們來看看有哪些方法可以回到主執行緒。

  • NSThread
  • GCD
  • NSOperationQueue

總結

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

相關文章