iOS - Threads 多線程

一、Threads

1.1 進程

  • 進程是指在系統中正在運行的一個應用程序。每一個進程之間是獨立的,每一個進程均運行在其專用且受保護的內存空間內。ios

    • 好比同時打開 QQ、Xcode,系統就會分別啓動兩個進程。經過 「活動監視器」 能夠查看 Mac 系統中所開啓的進程。程序員

      Threads1

    • 一個程序的一次運行,在執行過程當中擁有獨立的內存單元,而多個線程共享一塊內存。web

1.2 線程

  • 線程是進程中執行任務的基本執行單元。一個進程要執行任務,必須得有線程,一個進程(程序)的全部任務都在線程中執行。每個進程至少有一條線程,即主線程。一個進程能夠開啓多條線程,每條線程能夠併發(同時)執行不一樣的任務。數據庫

    • 好比使用酷狗播放音樂、使用迅雷下載電影,都須要在線程中執行。swift

      Threads2

  • 在程序中每個方法的執行,都是從上向下串行執行的。除非使用 block,不然在一個方法中,全部代碼的執行都在同一個線程上。安全

  • 進程與線程的聯繫:線程是進程的基本組成單位。服務器

  • 進程與線程的區別:
    • 1) 調度:線程做爲調度和分配的基本單位,進程做爲擁有資源的基本單位。
    • 2) 併發性:不只進程之間能夠併發執行,同一個進程的多個線程之間也可併發執行。
    • 3) 擁有資源:進程是擁有資源的一個獨立單位,線程不擁有系統資源,但能夠訪問隸屬於進程的資源。
    • 4) 系統開銷:在建立或撤消進程時,因爲系統都要爲之分配和回收資源,致使系統的開銷明顯大於建立或撤消線程時的開銷。

1.2.1 線程的串行

  • 一個線程中任務的執行是串行(順序執行)的。若是要在一個線程中執行多個任務,那麼只能一個一個的按順序執行這些任務,也就是說,在同一時間內,一個線程只能執行一個任務。網絡

    • 好比在一個線程中下載三個文件(分別是文件 A,文件 B,文件 C)。多線程

      Threads3

1.2.2 多線程原理

  • 同一時間,CPU 只能處理一條線程,只有一條線程在工做(執行)。多線程併發(同時)執行,實際上是 CPU 快速的在多條線程之間調度(切換)。若是 CPU 調度線程的時間足夠快,就形成了多條線程併發執行的假象。閉包

    Threads4

  • 多線程開發的複雜度相對較高,在開發時能夠按照如下套路編寫代碼:

    • 一、首先確保單個線程執行正確。
    • 二、添加線程。
  • 在多線程開發的時候,有幾點提示:

    • 一、不要相信一次運行的結果。
    • 二、不要去作不一樣線程之間執行的比較,線程內部的方法都是各自獨立執行的。
    • 三、多線程的目的是將耗時的操做放在後臺,不阻塞主線程和用戶的交互。
    • 四、多線程開發的原則是簡單,不要將簡單的事情搞複雜了。

1.2.3 多線程優勢

  • 使用多線程能夠將耗時的任務放到後臺去執行,不阻塞主線程和用戶的交互。
  • 使用多線程可以併發、同時執行多個任務,能夠適當提升程序的執行效率,適當提升資源的利用率(CPU、內存的利用率)。

1.2.4 多線程缺點

  • 每開一個線程都會形成系統額外的負擔,開啓線程須要佔用必定的內存空間(默認狀況下,每一條線程都會佔用 512KB),若是開啓大量的線程,會佔用大量的內存空間,下降程序的性能。
  • 線程越多,CPU 調度線程上開銷就越大。
  • 線程越多,程序設計就更加複雜,好比線程之間的通訊、多線程的數據共享就更加複雜。

  • 一般開啓 5 到 6 條線程便可,不能開啓的太多。

    • 棧區:保存局部變量和函數的參數,由下向上壓棧。線程執行完畢後,棧區會自動釋放,程序員不須要管理棧區的內存。

      Threads5

1.2.5 主線程

  • 一個 iOS 程序運行後,默認會開啓一條線程,稱爲主線程或 UI 線程。主線程主要用於顯示/刷新 UI 界面,處理 UI 事件(好比點擊事件、滾動事件、拖拽事件等)。

  • 注意別將比較耗時的操做放到主線程中,耗時的操做會卡住主線程,嚴重影響 UI 的流暢度,給用戶一種 「卡」 的壞體驗。

1.2.6 線程狀態

  • 新線程建立啓動後,線程對象被加入可調度線程池中,進入就緒狀態,等待 CPU 調度。在線程被調度運行時,若是線程調用了 sleep 方法或者等待同步鎖時,線程由運行狀態進入到阻塞狀態,線程被移出可調度線程池。sleep 到時或者獲得同步鎖時,線程從新進入可調度線程池,回到就緒狀態。線程任務執行完畢或者異常、強制退出時,線程由運行狀態進入到死亡狀態,線程結束,線程佔用內存空間被釋放。

    Threads6

    • 在整個線程狀態循環中,程序員沒法控制線程的運行狀態。線程是否運行由 CPU 調度完成。

1.2.7 線程安全

  • 若是一個屬性,在多個線程執行的狀況下,仍然可以獲得正確結果,被稱爲線程安全。
  • 要實現線程安全,就必需要用到鎖,用到鎖就會下降程序的性能。爲了獲得更佳的用戶體驗,UIKit 不是線程安全的,UI 線程約定全部更新 UI 的操做都必須主線程上執行,所以,主線程又被稱爲 UI 線程。

    • 1)iOS 開發建議:

      • 全部屬性都聲明爲 nonatomic。
      • 儘可能避免多線程搶奪同一塊資源。
      • 儘可能將加鎖、資源搶奪的業務邏輯交給服務器端處理,減少移動客戶端的壓力。
    • 2)使用互斥鎖:

      @synchronized (self) {
      
          }
      • 互斥鎖可以保證鎖定範圍的代碼,同一時間,只有一條線程可以執行。
      • 互斥鎖的鎖定範圍,應該儘可能小,只要鎖住資源讀寫部分的代碼便可。鎖定範圍越大,效率越差。使用鎖以後,程序執行性能都會受到影響,會影響併發的目的。
      • 在 iOS 開發中,使用鎖的機會極少,通常都是從服務器獲取到數據,直接顯示。

      • 參數:
        • self 表示當前的 UIViewController,爲 NSObject 的子類。本質上是任意一個 NSObject 子類對象均可以當成鎖。
        • 若是代碼中只有一個地方須要加鎖,大多都使用 self,這樣能夠避免單獨再建立一個鎖對象。
        • 鎖對象必定要保證全部的線程都可以訪問。必須爲全局變量。
    • 3)使用變量原子性:

      • 原子屬性(線程安全),是針對多線程設計的,是默認屬性。
      • 多個線程在寫入原子屬性時(調用 setter 方法),可以保證同一時間只有一個線程執行寫入操做,可是容許多個線程同時讀取屬性值。
      • 原子屬性是一種單(線程)寫多(線程)讀的多線程技術,在多線程讀取數據時,有可能出現「髒」數據 - 讀取的數據可能會不正確。
      • 原子屬性內部也有一把 "鎖",是自旋鎖,執行效率比互斥鎖高。
      • 在定義屬性時,若是不須要考慮線程安全,要顯示地指定爲 nonatomic。
      • 原子屬性,解決不了賣票問題,由於賣票的讀寫都須要鎖定。

      • 若是重寫了原子屬性的 setter 方法,至關於覆蓋了系統提供的 setter 方法,此時,系統要求,必須重寫 getter 方法。
      • 定義屬性時,系統默認提供 getter & setter 方法,而且生成一個 _成員變量,可是若是本身重寫了 getter & setter 方法,_成員變量,不會自動生成。

      • @synthesize 合成指令,能夠指定保存屬性數值的 成員變量。
      • 在 Xcode 4.5 以前,開發,程序員必須本身實現 @synthesize 指令。

    • 4)自旋鎖 & 互斥鎖:

      • 共同點:
        • 都可以保證同一時間,只有一條線程執行鎖定範圍的代碼。
      • 不一樣點:
        • 互斥鎖:若是發現有其餘線程正在執行鎖定的代碼,線程會進入休眠狀態,等待其餘線程執行完畢,打開鎖以後,線程會被喚醒。
        • 自旋鎖:若是發現有其餘線程正在執行鎖定的代碼,線程會以死循環的方式,一直等待鎖定代碼執行完成。
      • 結論:
        • 自旋鎖更適合執行很是短的代碼。
        • 不管什麼鎖,都是要付出代價。

1.2.8 線程間通訊

  • 在子線程中執行比較耗時的操做(以下載圖片等),子線程執行完畢後通知主線程更新 UI 等的操做。

  • UIKit 中幾乎全部控件都不是線程安全的,所以須要在主線程上更新 UI。在子線程中可使用如下方法讓主線程執行 UI 操做:

    // waitUntilDone: YES 等待主線程執行完成後子線程再繼續執行
        [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
    
        dispatch_async(dispatch_get_main_queue(), ^{
    
            [self updateUI:image];
        });
    
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    
            [self updateUI:image];
        }];

1.3 iOS 中多線程實現方案

Threads7

1.3.1 pthread

  • pthread 是 POSIX 的多線程開發框架,因爲是跨平臺的 C 語言框架,在蘋果的頭文件中並無詳細的註釋。Xcode 中所用跨平臺的文件一般都在 usr/include 目錄下。對於全部跨平臺的框架(如:pthread & socket),能夠訪問一個網站: baike.baidu.com

  • iOS 中 C 語言框架:

    • 在 C 語言中,沒有對象的概念,對象是以結構體的方式來實現的。
    • 一般,在 C 語言框架中,「對象」 類型以 _t/Ref 結尾,並且定義時不須要使用 * 。

    • 內存管理:

      • 在 OC 中,若是是 ARC 開發,編譯器會在編譯時,根據代碼結構,自動添加 retain/release/autorelease。
      • 可是,ARC 只負責管理 OC 部分的內存管理,而不負責 C/C++ 語言部分代碼的內存管理。
      • 若是開發的時候,涉及到混合語言開發,若是使用的 C 語言框架出現 retain/create/copy/new 等字樣的函數,大多都須要程序員手動 release,不然會出現內存泄漏。
    • 參數橋接:

      • 在混合開發時,若是在 C 和 OC 之間傳遞數據,須要使用 __bridge 進行橋接,告訴編譯器如何管理內存。__bridge 就是保留原有的管理方式。
      • 橋接的添加能夠藉助 Xcode 的輔助功能添加。
      • MRC 中不須要使用橋接。MRC 中全部內存都是程序員負責的。
      • 管理的是堆區的內存。alloc/copy/retain 等字樣的函數都是和堆區有關的。
    • void *:

      • C 語言中的 void * 和 OC 中的 id 是等價的。

      • 例如:

        C     : void *(*)(void *)
            OC    : id (函數名) (id)       即 返回值類型(函數名)(參數)
            block : 返回值 (^) (參數)       block 匿名的函數指針

1.3.2 NSThread

  • 優勢:NSThread 輕量級。
  • 缺點:須要本身管理線程的生命週期,線程同步。線程同步對數據的加鎖會有必定的系統開銷。

1.3.3 GCD

  • GCD 是 Grand Central Dispatch(譯爲 「中樞調度器」)的簡稱,它是基於 C 語言編寫的,是蘋果公司爲多核的並行運算提出的解決方案。GCD 在工做時會自動利用更多的處理器核心,以充分利用更強大的機器。若是使用 GCD,徹底由系統管理線程,咱們不須要編寫線程代碼,只需定義想要執行的任務,而後添加到適當的調度隊列(dispatch queue),GCD 會負責建立線程和調度你的任務。它首次發佈在 Mac OS X 10.6 ,iOS 4 上。在 iOS 全部實現多線程的方案中,GCD 應該是最有魅力的,GCD 是一個替代諸如 NSThread, NSOperationQueue,NSInvocationOperation 等技術的很高效和強大的技術。

  • 1) 工做原理:

    • 將長期運行的任務拆分紅多個工做單元,並將這些單元添加到 dispath queue 中,系統會爲咱們管理這些 dispath queue,根據可用的處理資源,安排他們在任何可用的處理器核心上執行任務。咱們不須要直接啓動和管理後臺線程。一個任務能夠是一個函數(function)或者是一個 block。GCD 的底層依然是用線程實現,不過這樣可讓程序員不用關注實現的細節。

    • 將任務添加到隊列,而且指定執行任務的函數,執行任務。任務使用 block 封裝,任務的 block 沒有參數也沒有返回值。

  • 2) 執行任務的函數:

    • 異步 dispatch_async

      • 不用等待當前語句執行完畢,就能夠執行下一條語句。
      • 會開啓線程執行 block 的任務。
      • 異步是多線程的代名詞。
    • 同步 dispatch_sync

      • 必須等待當前語句執行完畢,纔會執行下一條語句。
      • 不會開啓線程。
      • 在當前線程執行 block 的任務。

      • 同步任務的做用:同步任務,可讓其餘異步執行的任務,"依賴" 某一個同步任務。例如:在用戶登陸以後,再異步下載文件!

  • 3) 任務調度隊列:

    • GCD 中的 FIFO 隊列稱爲 dispatch queue(調度隊列)。系統提供了許多預約義的 dispatch queue,包括能夠保證始終在主線程上執行工做的 dispatch queue。也能夠建立本身的 dispatch queue,並且能夠建立任意多個。GCD 的 dispatch queue 嚴格遵循 FIFO(先進先出) 原則,添加到 dispatch queue 的工做單元將始終按照加入 dispatch queue 的順序啓動。dispatch queue 按先進先出的順序,串行或併發地調度任務在線程上實行。

    • dispatch queue 分爲下面三種:

      • Serial:

        • 串行隊列,又稱爲 private dispatch queues,一次只能 "調度" 一個任務, 當前任務完成纔開始出列並啓動下一個任務。Serial queue 一般用於同步訪問特定的資源或數據。當你建立多個 Serial queue 時,雖然它們各自是同步執行的,但 Serial queue 與 Serial queue 之間是併發執行的。
      • Concurrent:

        • 併發隊列,又稱爲 global dispatch queues,一次能夠 "調度" 多個任務,儘量多地啓動任務併發執行,任務執行完成的順序是隨機的。

        • 系統給每個應用程序提供了三個 concurrent dispatch queues。這三個併發調度隊列是全局的,它們只有優先級的不一樣。由於是全局的,咱們不須要去建立。咱們只須要經過使用函數 dispath_get_global_queue 去獲得隊列。

      • Main dispatch queue:

        • 主隊列,它是全局可用的 serial queue,專門用來在主線程上調度任務的隊列。不會開啓線程,主隊列異步執行時若是主線程上正在有代碼執行,就不會調度隊列中的任務,等待主線程空閒以後再調度任務。主線程中主隊列同步執行時,主隊列和主線程相互等待會形成死鎖。
  • 4) 各類隊列的執行效果:

    Type           |     全局併發隊列     |   手動建立串行隊列    |       主隊列

    -----------------|--------------------|--------------------|-------------------
    同步 (sync) | 沒有開啓新線程 | 沒有開啓新線程 | 沒有開啓新線程
    ~ | 串行執行任務 | 串行執行任務 | 串行執行任務
    異步 (async) | 有開啓新線程 | 有開啓新線程 | 沒有開啓新線程
    ~ | 併發執行任務 | 串行執行任務 | 串行執行任務

    • 開不開線程由執行任務的函數決定:

      • 異步開,異步是多線程的代名詞。
      • 同步不開。
    • 開幾條線程由隊列決定:

      • 串行隊列開一條線程。
      • 併發隊列開多條線程。

      • 主隊列不會開啓線程。

  • 5) 隊列的選擇:

    • 多線程的目的:將耗時的操做放在後臺執行!

    • 串行隊列,只開一條線程,全部任務順序執行

      • 若是任務有前後執行順序的要求。
      • 效率低 -> 執行慢 -> "省電"。
      • 有的時候,用戶其實不但願太快!例如使用 3G 流量,"省錢"。
    • 併發隊列,會開啓多條線程,全部任務不按照順序執行

      • 若是任務沒有前後執行順序的要求。
      • 效率高 -> 執行快 -> "費電"。
      • WIFI,包月。
    • 實際開發中

      • WIFI 線程數 6 條。
      • 3G / 4G 移動開發的時候,2~3 條,再多會費電費錢。
  • 6) 全局隊列 & 併發隊列的區別:

    • 全局隊列:

      • 沒有名稱。
      • 不管 MRC & ARC 都不須要考慮釋放。
      • 平常開發中,建議使用 "全局隊列"。
    • 併發隊列:

      • 有名字,和 NSThread 的 name 屬性做用相似。
      • 若是在 MRC 開發時,須要使用 dispatch_release(q); 釋放相應的對象。
      • 開發第三方框架時,建議使用併發隊列。
  • 7) GCD & NSThread 對比:

    • GCD 全部的代碼寫在一塊兒的,讓代碼更加簡單,易於閱讀和維護。
      • NSThread 經過 @selector 指定要執行的方法,代碼分散。
      • GCD 經過 block 指定要執行的代碼,代碼集中。
    • 使用 GCD 不須要管理線程的建立/銷燬/複用的過程。程序員不用關心線程的生命週期。
      • NSThread 須要本身建立線程對象,而且指定 selector 方法,而後 start。
      • GCD 只須要將任務添加給隊列,而且指定執行的函數
    • 若是要開多個線程 NSThread 必須實例化多個線程對象。
    • NSThread 靠 NSObject 的分類方法實現的線程間通信,GCD 靠 block。

1.3.4 NSOperation

  • NSOperation 也是蘋果公司推出的 「併發」 技術。是基於 OC 語言的,iOS 2.0 推出。GCD 推出以後,蘋果對 NSOperation 底層從新編寫過,是對 GCD 的封裝。Cocoa operation 相關的類是 NSOperation,NSOperationQueue。NSOperation 是個抽象類,使用它必須用它的子類,能夠實現它或者使用它定義好的兩個子類:NSInvocationOperation 和 NSBlockOperation。

  • 優勢:不須要關心線程管理,數據同步的事情,能夠把精力放在本身須要執行的操做上。

  • 1)NSOperation 與 GCD 對比:

    • NSOperation:

      • 核心概念:把 "操做(異步執行的任務)" 添加到隊列(全局的併發隊列)。即建立 NSOperation 子類的對象,把對象添加到 NSOperationQueue 隊列裏執行。

      • OC 的框架,更加面向對象,是對 GCD 的封裝

      • 高級功能:
        • 最大操做併發數(GCD 很差作)
          • 在 iOS 7.0 以前,使用 GCD & NSOperation 可以開啓的線程數都很少,最大的線程數通常只有 5~6 條
          • 從 iOS 8.0 開始,可以開不少個線程,若是不控制,會形成資源浪費
        • 繼續/暫停/所有取消
        • 可以指定任務的 "依賴" 關係(GCD 中,同步任務是來指定依賴關係)
    • GCD:

      • 核心概念:將 "任務(block)" 添加到隊列(串行/併發/全局/主隊列),而且指定任務執行的函數(同步/異步)

      • C 語言的框架,dispatch_xxx 函數

      • 高級功能:
        • 一次性 once
        • 延遲操做 after
        • 調度組 (op 能夠作,可是作不了太複雜)
  • 2)自定義 NSOperation 操做流程:

    Threads8

二、pthread 的使用

2.1 pthread 線程建立

  • Objective-C

    // 添加頭文件
    
            #import <pthread.h>
    
        // 建立 pthread 子線程
        /*
            int pthread_create(pthread_t * __restrict, 
                    const pthread_attr_t * __restrict, 
                                    void *(*)(void *), 
                                    void * __restrict);
    
            返回值:
    
                線程建立成功,返回 0。線程建立失敗,返回出錯編號。
                成功的緣由只有一個,失敗的緣由能夠有不少,在不少 C 語言框架中,都會採用這個套路。
    
            參數:
    
                pthread_t *       :第一個參數爲指向線程標識符的指針。
                pthread_attr_t *  :第二個參數用來設置線程屬性。
                void *(*)(void *) :第三個參數是線程運行函數的起始地址。
                void *            :第四個參數是運行函數的參數。
        */
    
            pthread_t threadId = NULL;
            NSString *str = @"pthread";
    
            int result = pthread_create(&threadId, NULL, pthreadDemo, (__bridge void *)(str));
    
            if (result) {
                NSLog(@"線程建立失敗 %d", result);
            } else {
                NSLog(@"線程建立成功");
            }
    
        // 子線程執行方法
    
            void * pthreadDemo(void * param) {
    
                NSString *str = (__bridge NSString *)(param);
    
                NSLog(@"%@ --- %@", [NSThread currentThread], str);
    
                return NULL;
            }

三、NSThread 的使用

3.1 NSThread 線程建立

- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument;
    + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
    - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;

    public convenience init(target: AnyObject, selector: Selector, object argument: AnyObject?)
    public class func detachNewThreadSelector(selector: Selector, toTarget target: AnyObject, withObject argument: AnyObject?)
    public func performSelectorInBackground(aSelector: Selector, withObject arg: AnyObject?)

    參數的意義:

        selector :線程執行的方法,這個 selector 只能有一個參數,並且不能有返回值。
        target   :selector 消息發送的對象。
        argument :傳輸給 target 的惟一參數,也能夠是 nil。

    第一種方式是先建立線程對象,而後再運行線程操做,在運行線程操做前能夠設置線程的優先級等線程信息。
    第二種和第三種方式會直接建立線程而且開始運行線程。
    第三種方式是 "NSObject" 的一個分類方法,能夠由任何繼承自 "NSObject" 的類對象調用該方法隱式建立並啓動一個子線程。
  • Objective-C

    // 1. 建立一個子線程
    
            NSThread *myThread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage:) object:imageUrlPath];
            [myThread start];                                                                                                           
    
        // 2. 建立並啓動一個子線程
    
            [NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:imageUrlPath];
    
        // 3. 隱式建立並啓動一個子線程
    
            [self performSelectorInBackground:@selector(downloadImage:) withObject:imageUrlPath];
    
        // 子線程執行方法
    
            - (void)downloadImage:(NSString *) urlPath {
    
            }
  • Swift

    // 1. 建立一個子線程
    
            let myThread = NSThread(target: self, selector: #selector(ViewController.downloadImage(_:)), object: imageUrlPath)
            myThread.start()                                                                                                        
    
        // 2. 建立並啓動一個子線程
    
            NSThread.detachNewThreadSelector(#selector(ViewController.downloadImage(_:)), toTarget: self, withObject: imageUrlPath)
    
        // 3. 隱式建立並啓動一個子線程
    
            self.performSelectorInBackground(#selector(ViewController.downloadImage(_:)), withObject: imageUrlPath)
    
        // 子線程執行方法
    
            func downloadImage(urlPath: String) {
    
            }

3.2 NSThread 線程設置

  • Objective-C

    // 啓動子線程
        /*
            將線程對象加入可調度線程池等待 CPU 調度,線程執行完畢後,因爲內存空間已經被釋放,不能再次啓動
        */
        [myThread start];
    
        // 通知線程取消
        /*
            能夠在外部終止線程執行,在線程執行方法中須要增長 isCancelled == YES,若是成立直接返回
        */
        [myThread cancel];
    
        // 獲取線程名字
        NSString *threadName = myThread.name;
    
        // 設置線程名字
        /*
            在多個線程開發時,能夠用來判斷究竟是誰在執行任務
            在大的商業項目中,一般須要在程序崩潰時,獲取程序準確執行所在的線程
        */
        myThread.name = @"downloadImage";
    
        // 設置線程的優先級
        /*
            範圍 0.0 到 1.0,默認爲 0.5,優先級高表示 CPU 調度的頻率相對較高。開發時儘可能不要修改
        */
        myThread.threadPriority = 1;
    
        // 判斷線程是否正在執行 readonly
        BOOL isExecuting = myThread.isExecuting;
    
        // 判斷線程是否完成 readonly
        BOOL isFinished = myThread.isFinished;
    
        // 判斷線程是否被取消 readonly
        BOOL isCancelled = myThread.isCancelled;
    
        // 獲取當前線程
        NSThread *currentThread = [NSThread currentThread];
    
        // 判斷當前線程是否爲主線程
        /*
            能夠在全部的多線程技術中使用
        */
        BOOL isMainThread = [[NSThread currentThread] isMainThread];
        BOOL isMainThread = [NSThread isMainThread];
    
        // 判斷是否爲多線程操做
        BOOL isMultiThreaded = [NSThread isMultiThreaded];
    
        // 引用主線程
        /*
            返回主線程對象
        */
        NSThread *mainThread = [NSThread mainThread];
    
        // 獲取棧區大小
        /*
            線程執行前,主線程和子線程默認棧區大小都爲 512K,線程完成後,棧區大小 0K,內存空間被釋放
            在之前的 iOS 版本,主線程棧區 1M,子線程是 512K,並且不能修改
        */
        NSUInteger stackSize = [NSThread currentThread].stackSize;
    
        // 設置棧區大小
        /*
            即 256 * 1024 = 256K,只有在當前線程中設置纔有效
        */
        [NSThread currentThread].stackSize = 256 * 1024;
    
        // 退出當前線程的執行
        /*
            使線程進入死亡狀態,線程被終止後,後續代碼都不會執行,不能在主線程中調用此方法
            在須要手動內存管理的代碼中,在終止線程以前,應該注意釋放以前分配的對象
        */
        [NSThread exit];
    
        // 休眠到指定的時間
        /*
            使線程進入阻塞狀態,線程暫時被移出可調度線程池
        */
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
    
        // 休眠指定的時長
        /*
            使線程進入阻塞狀態,線程暫時被移出可調度線程池
        */
        [NSThread sleepForTimeInterval:10];
  • Swift

    // 啓動子線程
        /*
            將線程對象加入可調度線程池等待 CPU 調度,線程執行完畢後,因爲內存空間已經被釋放,不能再次啓動
        */
        myThread.start()
    
        // 通知線程取消
        /*
            能夠在外部終止線程執行,在線程執行方法中須要增長 isCancelled == YES,若是成立直接返回
        */
        myThread.cancel()
    
        // 獲取線程名字
        let threadName:String? = myThread.name
    
        // 設置線程名字
        /*
            在多個線程開發時,能夠用來判斷究竟是誰在執行任務
            在大的商業項目中,一般須要在程序崩潰時,獲取程序準確執行所在的線程
        */
        myThread.name = "downloadImage"
    
        // 設置線程的優先級
        /*
            範圍 0.0 到 1.0,默認爲 0.5,優先級高表示 CPU 調度的頻率相對較高。開發時儘可能不要修改
        */
        myThread.threadPriority = 1
    
        // 判斷線程是否正在執行 readonly
        let isExecuting:Bool = myThread.executing
    
        // 判斷線程是否完成 readonly
        let isFinished:Bool = myThread.finished
    
        // 判斷線程是否被取消 readonly
        let isCancelled:Bool = myThread.cancelled
    
        // 獲取當前線程
        let currentThread:NSThread = NSThread.currentThread()
    
        // 判斷當前線程是否爲主線程
        /*
            能夠在全部的多線程技術中使用
        */
        let isMainThread:Bool = NSThread.currentThread().isMainThread
        let isMainThread:Bool = NSThread.isMainThread()
    
        // 判斷是否爲多線程操做
        let isMultiThreaded:Bool = NSThread.isMultiThreaded()
    
        // 引用主線程
        /*
            返回主線程對象
        */
        let mainThread:NSThread = NSThread.mainThread()
    
        // 獲取棧區大小
        /*
            線程執行前,主線程和子線程默認棧區大小都爲 512K,線程完成後,棧區大小 0K,內存空間被釋放
            在之前的 iOS 版本,主線程棧區 1M,子線程是 512K,並且不能修改
        */
        let stackSize:Int = NSThread.currentThread().stackSize
    
        // 設置棧區大小
        /*
            即 256 * 1024 = 256K,只有在當前線程中設置纔有效
        */
        NSThread.currentThread().stackSize = 256 * 1024
    
        // 退出當前線程的執行
        /*
            使線程進入死亡狀態,線程被終止後,後續代碼都不會執行,不能在主線程中調用此方法
            在須要手動內存管理的代碼中,在終止線程以前,應該注意釋放以前分配的對象
        */
        NSThread.exit()
    
        // 休眠到指定的時間
        /*
            使線程進入阻塞狀態,線程暫時被移出可調度線程池
        */
        NSThread.sleepUntilDate(NSDate(timeIntervalSinceNow: 10))
    
        // 休眠指定的時長
        /*
            使線程進入阻塞狀態,線程暫時被移出可調度線程池
        */
        NSThread.sleepForTimeInterval(10)

3.3 NSThread 線程間通訊

  • 子線程裏不容許操做 UI。
  • 在子線程中向 self 發送消息,讓主線程執行某個方法。waitUntilDone: YES 等待主線程執行完成後子線程再繼續,NO 主線程在執行方法的時候,子線程也同時運行。

  • Objective-C

    // 在主線程中執行操做
        [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
    
        // 在另外一個線程中執行操做
        NSThread *mainThread = [NSThread mainThread];
        [self performSelector:@selector(updateUI:) onThread:mainThread withObject:image waitUntilDone:YES];
  • Swift

    // 在主線程中執行操做
        self.performSelectorOnMainThread(#selector(ViewController.updateUI(_:)), withObject: image, waitUntilDone: true)
    
        // 在另外一個線程中執行操做
        let mainThread = NSThread.mainThread()
        self.performSelector(#selector(ViewController.updateUI(_:)), onThread: mainThread, withObject: image, waitUntilDone: true)

3.4 NSThread 線程系統通知

NSWillBecomeMultiThreadedNotification   // 將要變成多線程
    NSDidBecomeSingleThreadedNotification   // 已經變成單線程
    NSThreadWillExitNotification            // 將要退出子線程
  • Objective-C

    // 添加線程系統通知
    
            [[NSNotificationCenter defaultCenter] addObserver:self 
                                                     selector:@selector(downloadEnd:) 
                                                         name:NSThreadWillExitNotification 
                                                       object:nil];  
    
        // 系統通知響應觸發事件
    
            - (void)downloadEnd:(NSNotification *)notification {
    
                NSThread *thread = notification.object;
    
                NSLog(@"%@ 線程結束了", thread.name);
            }
  • Swift

    // 添加線程系統通知
    
            NSNotificationCenter.defaultCenter().addObserver(self, 
                                                    selector: #selector(ViewController.downloadEnd(_:)), 
                                                        name: NSThreadWillExitNotification, 
                                                      object: nil)
    
        // 系統通知響應觸發事件
    
            func downloadEnd(notification: NSNotification){
    
                let thread = notification.object as! NSThread
    
                print("\(thread.name) 線程結束了");
            }

3.5 NSThread 線程安全

  • Objective-C

    // 對線程加鎖
    
            // 操做變量時須要加線程鎖,保證同時只有一個線程在訪問該變量。
    
            // 實例化線程鎖
            NSLock *threadLock;
            _threadLock = [[NSLock alloc] init];
    
            // 打開線程鎖,開始對變量寫操做
            [_threadLock lock];
    
            // 關閉線程鎖,中止對變量寫操做
            [_threadLock unlock];
    
        // 使用互斥鎖
    
            @synchronized (self) {
    
                // 對變量寫操做
            }
    
        // 聲明變量爲原子性 
    
            // 聲明變量爲原子性(默認)
            @property(assign) NSInteger page;
  • Swift

    // 對線程加鎖
    
            // 操做變量時須要加線程鎖,保證同時只有一個線程在訪問該變量。
    
            // 實例化線程鎖
            var threadLock:NSLock!
            threadLock = NSLock()
    
            // 打開線程鎖,開始對變量寫操做
            threadLock.lock()
    
            // 關閉線程鎖,中止對變量寫操做
            threadLock.unlock()

3.6 自旋鎖&互斥鎖比較

  • 若是重寫了原子屬性的 setter 方法,至關於覆蓋了系統提供的 setter 方法,此時,系統要求,必須重寫 getter 方法。
  • 定義屬性時,系統默認提供 getter & setter 方法,而且生成一個 _成員變量,可是若是本身重寫了 getter & setter 方法,_成員變量,不會自動生成。

  • @synthesize 合成指令,能夠指定保存屬性數值的 成員變量。
  • 在 Xcode 4.5 以前,開發,程序員必須本身實現 @synthesize 指令。

  • Objective-C

    @property (atomic, strong) NSObject *obj1;
        @property (atomic, strong) NSObject *obj2;
    • 原子屬性模擬

      @synthesize obj1 = _obj1;
      
          // obj1 - getter
      
              - (NSObject *)obj1 {
      
                  return _obj1;
              }
      
          // obj1 - setter
      
              - (void)setObj1:(NSObject *)obj1 {
      
                  // 使用互斥鎖
                  @synchronized(self) {
      
                      _obj1 = obj1;
                  }
              }
    • 自旋鎖&互斥鎖性能測試

      long largeNumber = 1000 * 1000;
      
          // 互斥鎖測試
      
              // 2001-01-01 00:00:00 到如今的秒數
              CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();                              
      
              for (int i = 0; i < largeNumber; ++i) {
                  self.obj1 = [[NSObject alloc] init];
              }
      
              NSLog(@"互斥鎖: %f", CFAbsoluteTimeGetCurrent() - start);
      
          // 自旋鎖測試
      
              start = CFAbsoluteTimeGetCurrent();
      
              for (int i = 0; i < largeNumber; ++i) {
                  self.obj2 = [[NSObject alloc] init];
              }
      
              NSLog(@"自旋鎖: %f", CFAbsoluteTimeGetCurrent() - start);

四、GCD 的使用

4.1 GCD 線程建立

  • 一、dispatch_async

    • 經常使用的方法 dispatch_async,爲了不界面在處理耗時的操做時卡死,好比讀取網絡數據,IO,數據庫讀寫等,咱們會在另一個線程中處理這些操做,而後通知主線程更新界面。

      dispatch_async(dispatch_get_global_queue(0, 0), ^{
      
              // 耗時的操做 Code
      
              dispatch_async(dispatch_get_main_queue(), ^{
      
                  // 更新界面 Code
              });
          });
  • 二、dispatch_barrier_async

    • dispatch_barrier_async 是在前面的任務執行結束後它才執行,並且它後面的任務等它執行完成以後纔會執行,而且全部的任務都不能使用全局的 queue 序列。
  • 三、dispatch_group_async

    • dispatch_group_async 能夠實現監聽一組任務是否完成,等到 group 中的全部任務執行完畢後,由隊列調度 dispatch_group_notify block 中的任務異步執行。須要在全部異步任務執行完畢後,統一得到一個通知。group 負責監控任務,queue 負責調度任務。

    • dispatch_group_async 是異步的方法,任務的執行順序不肯定,與任務添加的順序無關。全部 dispatch_group_async 添加的任務都執行完後,再執行 dispatch_group_notify 添加的任務,但 dispatch_group_notify 添加的任務需最後添加。這個方法頗有用,好比你執行兩個下載任務,當兩個任務都下載完成後你才通知界面說完成的了。

  • 四、dispatch_apply

    • dispatch_apply 執行某個代碼片斷 N 次。任務 同步執行。
  • Objective-C

    • 全局隊列同步執行任務

      // 全局隊列,負責調度任務
          dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
      
          // 任務,使用 block 來包裝任務,block 沒有參數,沒有返回值
          void (^task)() = ^ {
      
          };
      
          // 指定執行任務的函數,不會開啓線程,就在當前線程執行 block
          dispatch_sync(globalQueue, task);
    • 全局隊列異步執行任務

      // 全局隊列,負責調度任務
          dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
      
          // 任務,使用 block 來包裝任務,block 沒有參數,沒有返回值
          void (^task)() = ^ {
      
          };
      
          // 指定執行任務的函數,會開啓線程,在其餘線程執行 block
          dispatch_async(globalQueue, task);
    • 建立 dispatch_async 線程

      dispatch_async(dispatch_get_global_queue(0, 0), ^{
      
              // 耗時的操做 Code
              NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:imageUrlPath]];
              UIImage *image = [[UIImage alloc] initWithData:data];
      
              dispatch_async(dispatch_get_main_queue(), ^{
      
                  // 更新界面,刷新主線程 Code
                  self.imageView.image = image;
              });
          });
    • 順序操做

      // myQueue 不能使用全局的對列
          dispatch_queue_t myQueue = dispatch_queue_create("qq", DISPATCH_QUEUE_CONCURRENT);                      
      
          dispatch_async(myQueue, ^{
      
              NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:imageUrlPath]];
              _backImage = [[UIImage alloc] initWithData:data];
          });
      
          dispatch_async(myQueue, ^{
      
              NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:iconImageUrlPath]];
              _iconImage = [[UIImage alloc] initWithData:data];
          });
      
          dispatch_barrier_async(myQueue, ^{
      
              dispatch_async(dispatch_get_main_queue(), ^{
      
                  self.iconImageView.image = _iconImage;
              }); 
          });
      
          dispatch_async(myQueue, ^{
      
              dispatch_async(dispatch_get_main_queue(), ^{
      
                  self.imageView.image = _backImage;
              }); 
          });
    • 羣組操做,線程通知

      // queue 負責調度任務
          dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
      
          // group 負責監控任務
          dispatch_group_t group = dispatch_group_create();
      
          // 第一個任務
          dispatch_group_async(group, globalQueue, ^{
      
              NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:imageUrlPath]];
              _backImage = [[UIImage alloc] initWithData:data];
          });
      
          // 第二個任務
          dispatch_group_async(group, globalQueue, ^{
      
              NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:iconImageUrlPath]];
              _iconImage = [[UIImage alloc] initWithData:data];
          });
      
          // 其它全部添加的任務都執行完後,再執行該任務,但該任務需最後添加
          dispatch_group_notify(group, dispatch_get_main_queue(), ^{
      
              self.imageView.image = _backImage;
              self.iconImageView.image = _iconImage;
          });
    • 羣組操做實現原理

      /*
              The dispatch_group_async() convenience function behaves like so:
      
              void
              dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block)
              {
                  dispatch_retain(group);
                  dispatch_group_enter(group);
                  dispatch_async(queue, ^{
                      block();
                      dispatch_group_leave(group);
                      dispatch_release(group);
                  });
              }
          */
      
          // queue 負責調度任務
          dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
      
          // group 負責監控任務
          dispatch_group_t group = dispatch_group_create();
      
          // 入組,以後的 block 會被 group 監聽
          dispatch_group_enter(group);
          dispatch_async(globalQueue, ^{
      
              NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:imageUrlPath]];
              _backImage = [[UIImage alloc] initWithData:data];
      
              // 出組,block 的末尾,全部任務執行完畢後,添加一個出組,dispatch_group_enter & dispatch_group_leave 必須成對出現
              dispatch_group_leave(group);
          });
      
          // 入組,以後的 block 會被 group 監聽
          dispatch_group_enter(group);
          dispatch_async(globalQueue, ^{
      
              NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:iconImageUrlPath]];
              _iconImage = [[UIImage alloc] initWithData:data];
      
              // 出組,block 的末尾,全部任務執行完畢後,添加一個出組,dispatch_group_enter & dispatch_group_leave 必須成對出現
              dispatch_group_leave(group);
          });
      
          // 阻塞式等待調度組中全部任務執行完畢
          dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
      
          // 羣組結束
          dispatch_group_notify(group, dispatch_get_main_queue(), ^{
      
              self.imageView.image = _backImage;
              self.iconImageView.image = _iconImage;  
          });
    • 延遲操做

      // 從如今開始 n 納秒後,1.0 * NSEC_PER_SEC = 1 秒
          dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
      
          // 延遲指定時間(納秒)後在指定的隊列中調度執行,異步執行
          dispatch_after(when, dispatch_get_global_queue(0, 0), ^{
      
              NSLog(@"%@", [NSThread currentThread]);
          });
    • 循環操做

      // 循環執行設定的次數(5 次),同步執行
          dispatch_apply(5, dispatch_get_global_queue(0, 0), ^(size_t index) {                            
      
              NSLog(@"dispatch_apply: %zu --- %@", index, [NSThread currentThread]);
          });
    • 一次性操做

      /*
              有的時候,在程序開發中,有些代碼只想從程序啓動就只執行一次,典型的應用場景就是 「單例」。
      
              dispatch_once 可以保證 block 中的代碼,只會被執行一次,onceToken == 0 時就會執行 block 的代碼,執行後 變爲 -1,
              dispatch 內部也有一把鎖,是可以保證 "線程安全" 的,並且是蘋果公司推薦使用的。
      
              block 同步執行,可以保證後續的代碼直接使用 block 執行後的結果。
          */
      
              // 同步執行
              static dispatch_once_t onceToken;
              dispatch_once(&onceToken, ^{
      
              });
    • 同步任務的做用

      // 同步任務,可讓其餘異步執行的任務,"依賴" 某一個同步任務。例如:在用戶登陸以後,再異步下載文件。
      
          // 隊列
          dispatch_queue_t q = dispatch_queue_create("qq", DISPATCH_QUEUE_CONCURRENT);
      
          // 異步執行 task
          dispatch_async(q, ^{
      
              dispatch_sync(q, ^{
                  NSLog(@"Login %@", [NSThread currentThread]);
              });
      
              dispatch_async(q, ^{
                  NSLog(@"Download A %@", [NSThread currentThread]);
              });
      
              dispatch_async(q, ^{
                  NSLog(@"Download B %@", [NSThread currentThread]);
              });
          });
    • 主隊列同步任務不死鎖

      // 主線程中主隊列同步執行時,主隊列和主線程相互等待會形成死鎖。在子線程中執行不會死鎖。
      
          // 併發隊列
          dispatch_queue_t q = dispatch_queue_create("qq", DISPATCH_QUEUE_CONCURRENT);
      
          // 任務
          void (^task)() = ^ {
      
              dispatch_sync(dispatch_get_main_queue(), ^{
                  NSLog(@"come here %@", [NSThread currentThread]);
              });
          };
      
          // 異步執行任務
          dispatch_async(q, task);
  • Swift

    • 全局隊列同步執行任務

      // 全局隊列,負責調度任務
          let q:dispatch_queue_t = dispatch_get_global_queue(0, 0);
      
          // 任務,使用 閉包 來包裝任務,閉包 沒有參數,沒有返回值
          let task:(() -> Void) = {
      
          }
      
          // 指定執行任務的函數,不會開啓線程,就在當前線程執行 閉包
          dispatch_sync(q, task);
    • 全局隊列異步執行任務

      // 全局隊列,負責調度任務
          let q:dispatch_queue_t = dispatch_get_global_queue(0, 0);
      
          // 任務,使用 閉包 來包裝任務,閉包 沒有參數,沒有返回值
          let task:(() -> Void) = {
      
          }
      
          // 指定執行任務的函數,會開啓線程,在其餘線程執行 閉包
          dispatch_async(q, task);
    • 建立 dispatch_async 線程

      dispatch_async(dispatch_get_global_queue(0, 0)) {
      
              // 耗時的操做 Code
      
              let data = NSData(contentsOfURL: NSURL(string: self.imageUrlPath)!)
              let image = UIImage(data: data!)
      
              dispatch_async(dispatch_get_main_queue()) {
      
                  // 更新界面,刷新主線程 Code                  
                  self.imageView.image = image
              }
          }
    • 順序操做

      // myQueue 不能使用全局的序列
          let myQueue:dispatch_queue_t = dispatch_queue_create("qq", DISPATCH_QUEUE_CONCURRENT)
      
          dispatch_async(myQueue) {
      
              let data = NSData(contentsOfURL: NSURL(string: self.imageUrlPath)!)
              self.backImage = UIImage(data: data!)
          }
      
          dispatch_async(myQueue) {
      
              let data = NSData(contentsOfURL: NSURL(string: self.iconImageUrlPath)!)
              self.iconImage = UIImage(data: data!)
          }
      
          dispatch_barrier_async(myQueue) {
      
              dispatch_async(dispatch_get_main_queue(), {
      
                  self.iconImageView.image = self.iconImage
              })
      
              NSThread.sleepForTimeInterval(4)
          }
      
          dispatch_async(myQueue) {
      
              dispatch_async(dispatch_get_main_queue(), {
      
                  self.imageView.image = self.backImage
              })
          }
    • 羣組操做

      // queue 負責調度任務
          let globalQueue = dispatch_get_global_queue(0, 0)
      
          // group 負責監控任務
          let group = dispatch_group_create()
      
          // 第一個任務
          dispatch_group_async(group, globalQueue) {
      
              let data = NSData(contentsOfURL: NSURL(string: self.imageUrlPath)!)
              self.backImage = UIImage(data: data!)
      
              print("第一個任務完成")
          }
      
          // 第二個任務
          dispatch_group_async(group, globalQueue) {
      
              let data = NSData(contentsOfURL: NSURL(string: self.iconImageUrlPath)!)
              self.iconImage = UIImage(data: data!)
      
              print("第二個任務完成")
          }
      
          // 其它全部添加的任務都執行完後,再執行該任務,但該任務需最後添加
          dispatch_group_notify(group, dispatch_get_main_queue()) {
      
              // 異步執行
      
              self.imageView.image = self.backImage
              self.iconImageView.image = self.iconImage
      
              print("更新界面")
          }
    • 羣組操做實現原理

      /*
              The dispatch_group_async() convenience function behaves like so:
      
              void
              dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block)
              {
                  dispatch_retain(group);
                  dispatch_group_enter(group);
                  dispatch_async(queue, ^{
                  block();
                  dispatch_group_leave(group);
                  dispatch_release(group);
                  });
              }
          */
      
          // queue 負責調度任務
          let globalQueue = dispatch_get_global_queue(0, 0)
      
          // group 負責監控任務
          let group = dispatch_group_create()
      
          // 入組,以後的 block 會被 group 監聽
          dispatch_group_enter(group)
          dispatch_async(globalQueue) { 
      
              let data = NSData(contentsOfURL: NSURL(string: self.imageUrlPath)!)
              self.backImage = UIImage(data: data!)
      
              // 出組,block 的末尾,全部任務執行完畢後,添加一個出,dispatch_group_enter & dispatch_group_leave 必須成對出現
              dispatch_group_leave(group)
          }
      
          // 入組,以後的 block 會被 group 監聽
          dispatch_group_enter(group)
          dispatch_async(globalQueue) {
      
              let data = NSData(contentsOfURL: NSURL(string: self.iconImageUrlPath)!)
              self.iconImage = UIImage(data: data!)
      
              // 出組,block 的末尾,全部任務執行完畢後,添加一個出組,dispatch_group_enter & dispatch_group_leave 必須成對出現
              dispatch_group_leave(group)
          }
      
          // 阻塞式等待調度組中全部任務執行完畢
          dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
      
          // 羣組結束
          dispatch_group_notify(group, dispatch_get_main_queue()) {
      
              // 異步執行
      
              self.imageView.image = self.backImage
              self.iconImageView.image = self.iconImage   
          }
    • 延遲操做

      // 從如今開始 n 納秒後,1 * NSEC_PER_SEC = 1 秒
          let when:dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, (Int64)(1 * NSEC_PER_SEC))
      
          // 延遲指定時間(納秒)後在指定的隊列中調度執行,異步執行
          dispatch_after(when, dispatch_get_global_queue(0, 0)) {
      
          }
    • 循環操做

      // 循環執行設定的次數(5 次),同步執行
          dispatch_apply(5, dispatch_get_global_queue(0, 0)) { (index:Int) in
      
          }
    • 一次性操做

      /*
              有的時候,在程序開發中,有些代碼只想從程序啓動就只執行一次,典型的應用場景就是 「單例」。
      
              dispatch_once 可以保證 block 中的代碼,只會被執行一次,onceToken == 0 時就會執行 block 的代碼,執行後 變爲 -1,
              dispatch 內部也有一把鎖,是可以保證 "線程安全" 的,並且是蘋果公司推薦使用的。
      
              block 同步執行,可以保證後續的代碼直接使用 block 執行後的結果。
          */
      
          // 同步執行
          struct Static {
              static var onceToken: dispatch_once_t = 0
          }
          dispatch_once(&Static.onceToken, {
      
          })
    • 同步任務的做用

      // 同步任務,可讓其餘異步執行的任務,"依賴" 某一個同步任務。例如:在用戶登陸以後,再異步下載文件。
      
          // 隊列
          let q:dispatch_queue_t = dispatch_queue_create("qq", DISPATCH_QUEUE_CONCURRENT)
      
          dispatch_async(q) { 
      
              // 異步執行 task
              dispatch_sync(q, {
      
              })
      
              dispatch_async(q, { 
      
              })
      
              dispatch_async(q, {
      
              })
          }
    • 主隊列同步任務不死鎖

      // 主線程中主隊列同步執行時,主隊列和主線程相互等待會形成死鎖。在子線程中執行不會死鎖。
      
          // 隊列
          let q:dispatch_queue_t = dispatch_queue_create("qq", DISPATCH_QUEUE_CONCURRENT)
      
          // 任務
          let task:(() -> Void) = {
      
              dispatch_async(dispatch_get_main_queue(), { 
      
              })
          }
      
          // 異步執行任務
          dispatch_async(q, task)

4.2 GCD 線程設置

  • Objective-C

    • 調度組的建立

      // 建立調度組
          dispatch_group_t group = dispatch_group_create();
    • 線程隊列的建立

      // 獲取全局隊列
          dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
      
          // 獲取主線程隊列
          dispatch_queue_t mainQueue = dispatch_get_main_queue();
      
          // 建立串行隊列
          dispatch_queue_t mySerialQueue = dispatch_queue_create("mySerialQueue", NULL);
      
          // 建立併發隊列
          dispatch_queue_t myConcurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    • 全局隊列的獲取

      /*
              dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
      
              全局隊列,是 GCD 爲了方便程序員的多線程開發提供的 dispatch_get_global_queue,自己就是一個併發隊列。
      
              參數:
      
                  1. identifier:服務質量(隊列對任務調度的優先級)/iOS 7.0 以前,是優先級
      
                  iOS 8.0+ 告訴隊列執行任務的 "服務質量 quality of service":
      
                          QOS_CLASS_USER_INTERACTIVE 0x21,               用戶交互(但願儘快完成,用戶對結果很指望,不要放太耗時操做)
                          QOS_CLASS_USER_INITIATED 0x19,                 用戶指望(但願快,不要放太耗時操做)
                          QOS_CLASS_DEFAULT 0x15,                        默認(不是給程序員使用的,用來重置對列使用的)
                          QOS_CLASS_UTILITY 0x11,                        實用工具(耗時操做,專門用來處理耗時操做)
                          QOS_CLASS_BACKGROUND 0x09,                     後臺
                          QOS_CLASS_UNSPECIFIED 0x00,                    未指定,能夠和 iOS 7.0 適配
      
                  iOS 7.0 及以前 優先級:
      
                          DISPATCH_QUEUE_PRIORITY_HIGH 2                 高優先級
                          DISPATCH_QUEUE_PRIORITY_DEFAULT 0              默認優先級
                          DISPATCH_QUEUE_PRIORITY_LOW (-2)               低優先級
                          DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN   後臺優先級
      
                  提示:不要選擇 BACKGROUND 的選項,蘋果認爲:BACKGROUND 表示用戶不須要知道任務何時完成。選擇這個選項,速度慢的使人髮指!不利於調試。
      
                          關於優先級,不要搞太負責,就用最簡單的。
      
                  結論:若是要作 iOS 8.0 & iOS 7.0 的適配,使用如下代碼:dispatch_get_global_queue(0, 0);
      
                          若是要作 iOS 8.0 & iOS 9.0 的適配,應該選擇 QOS_CLASS_UTILITY
      
                  2. flags:保留
      
                  標記是爲了將來使用保留的。這個參數應該永遠指定爲 0。
          */
      
          // 獲取全局隊列
          dispatch_queue_t globalQueue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
  • Swift

    • 調度組的建立

      // 建立調度組
          let group:dispatch_group_t = dispatch_group_create()
    • 線程隊列的建立

      // 獲取全局隊列
          let globalQueue:dispatch_queue_t = dispatch_get_global_queue(0, 0)
      
          // 獲取主線程隊列
          let mainQueue:dispatch_queue_t = dispatch_get_main_queue()
      
          // 建立串行隊列
          let mySerialQueue:dispatch_queue_t = dispatch_queue_create("mySerialQueue", nil);
      
          // 建立併發隊列
          let myConcurrentQueue:dispatch_queue_t = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT)

4.3 GCD 線程間通訊

  • 子線程裏不容許操做 UI。
  • 在子線程中調用主線程隊列,執行刷新 UI 等操做。

  • Objective-C

    dispatch_async(dispatch_get_main_queue(), ^{
    
            // 更新界面,刷新主線程 Code
        });
  • Swift

    dispatch_async(dispatch_get_main_queue()) {
    
            // 更新界面,刷新主線程 Code
        }

五、NSOperation 的使用

5.1 NSOperation 線程建立

  • NSOpeartion 是對 GCD 的封裝,是 OC 的,比 GCD 的使用簡單。即將 「操做」 添加到隊列。

    • 隊列:全局隊列(併發隊列)。
    • 操做:異步執行的任務。
  • 使用 NSOperation 的方式有兩種:

    • 一、用定義好的兩個子類:NSInvocationOperation 和 NSBlockOperation。
    • 二、繼承 NSOperation,NSOperation 也是設計用來擴展的,只需繼承 NSOperation 在 .m 文件中實現 main 方法,main 方法編寫要執行的代碼便可。而後把 NSOperation 子類的對象放入NSOperationQueue 隊列中,該隊列就會啓動並開始處理它。
  • Objective-C

    • 建立一個 block 風格的任務

      // 建立任務操做隊列
          NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
      
          NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      
              [self downloadImage1:imageUrlPath];
          }];
      
          // 將一個操做任務加到隊列中,若是隊列中的任務數小於最大併發數,會當即執行,不然任務排隊
          [operationQueue addOperation:operation];
    • 直接向隊列添加 block

      // 建立任務操做隊列
          NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
      
          // 直接向操做隊列添加操做 block
          [operationQueue addOperationWithBlock:^{
      
              [self downloadImage1:imageUrlPath];
          }];
    • 含主隊列的任務

      [[[NSOperationQueue alloc] init] addOperationWithBlock:^{
      
              // 耗時的操做 Code
              NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:imageUrlPath]];
              UIImage *image = [[UIImage alloc] initWithData:data];
      
              [[NSOperationQueue mainQueue] addOperationWithBlock:^{
      
                  // 更新界面,刷新主線程 Code
                  self.imageView.image = image;
              }];
          }];
    • 建立一個普通的任務

      // 建立任務操做隊列
          NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
      
          // 建立一個普通的操做任務
          NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self 
                                                                                  selector:@selector(downloadImage1:) 
                                                                                    object:imageUrlPath];
      
          // 將一個操做任務加到隊列中,若是隊列中的任務數小於最大併發數,會當即執行,不然任務排隊
          [operationQueue addOperation:operation];
    • 子線程執行方法

      - (void)downloadImage:(NSString *) urlPath {
      
          }
  • Swift

    • 建立一個 block 風格的任務

      // 建立任務操做隊列
          let operationQueue = NSOperationQueue()
      
          let operation = NSBlockOperation {
      
              self.downloadImage1(self.imageUrlPath)
          }
      
          // 將一個操做任務加到隊列中,若是隊列中的任務數小於最大併發數,會當即執行,不然任務排隊
          operationQueue.addOperation(operation)
    • 直接向隊列添加 block

      // 建立任務操做隊列
          let operationQueue = NSOperationQueue()
      
          // 直接向操做隊列添加操做 block
          operationQueue.addOperationWithBlock {
      
              self.downloadImage1(self.imageUrlPath)
          }
    • 含主隊列的任務

      NSOperationQueue().addOperationWithBlock { 
      
              // 耗時的操做 Code
              let data = NSData(contentsOfURL: NSURL(string: self.imageUrlPath)!)
      
              let image = UIImage(data: data!)
      
              NSOperationQueue.mainQueue().addOperationWithBlock({ 
      
                  // 更新界面,刷新主線程 Code
                  self.imageView.image = image
              })
          }
    • 子線程執行方法

      func downloadImage(urlPath: String) {
      
          }

5.2 NSOperation 線程設置

  • Objective-C

    • 隊列設置

      // 建立全局併發隊列
          NSOperationQueue *globalQueue = [[NSOperationQueue alloc] init];
      
          // 獲取當前隊列
          NSOperationQueue *currentQueue = [NSOperationQueue currentQueue];
      
          // 獲取主隊列
          NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
      
          // 向隊列添加操做
          [operationQueue addOperation:operation];
      
          // 向隊列添加多個操做
          /*
              NO 異步的,YES 同步的
          */
          [operationQueue addOperations:@[operation1, operation2] waitUntilFinished:NO];
      
          // 向隊列添加操做 block
          [operationQueue addOperationWithBlock:^{
      
          }];
      
          // 設置最大併發數
          /*
              默認狀況下是 -1,-1 表示沒有限制,會同時運行隊列中的所有的操做
          */
          operationQueue.maxConcurrentOperationCount = 4;
      
          // 設置隊列名稱
          operationQueue.name = @"myOperationQueue";
      
          // 設置隊列服務質量
          operationQueue.qualityOfService = NSQualityOfServiceUtility;
      
          // 設置隊列是否掛起
          /*
              YES 掛起,NO 繼續執行
              隊列掛起,當前 "沒有完成的操做" 是包含在隊列的操做數中的隊列掛起,不會影響已經執行操做的執行狀態
              對列一旦被掛起,再添加的操做不會被調度
          */
          operationQueue.suspended = YES;
      
          // 判斷隊列是否掛起
          BOOL isSuspended = operationQueue.isSuspended;
      
          // 給隊列中全部操做發送取消(cancel)消息
          /*
              在隊列取消完成以前,操做計數並不會發生變化
              系統的全部方法都沒有對 isCancelled 作判斷
              正在執行的操做不會響應 cancel 消息,不會取消正在執行中的操做,不會影響隊列的掛起狀態
          */
          [operationQueue cancelAllOperations];
      
          // 獲取隊列中操做的數量
          NSUInteger operationCount = operationQueue.operationCount;
      
          // 獲取隊列中的全部操做
          NSArray *operations = operationQueue.operations;
      
          // 等待全部操做完成
          [operationQueue waitUntilAllOperationsAreFinished];
    • 操做設置

      // 設置操做間依賴關係
          /*
              operation2 依賴於 operation1
          */
          [operation2 addDependency:operation1];
      
          // 移除操做間依賴關係 
          [operation2 removeDependency:operation1];
      
          // 獲取依賴的全部操做,readonly
          NSArray *dependencies = operation2.dependencies;
      
          // 取消操做
          [operation cancel];
      
          // 判斷操做是否被取消,readonly
          BOOL isCancelled = operation.isCancelled;
      
          // 判斷操做是否正在被執行,readonly
          BOOL isExecuting = operation.isExecuting;
      
          // 判斷操做是否執行完成,readonly
          BOOL isFinished = operation.isFinished;
      
          // 判斷操做是不是異步執行的,readonly
          BOOL isAsynchronous = operation.isAsynchronous;
      
          // 判斷操做是否準備好執行,readonly
          BOOL isReady = operation.isReady;
      
          // 設置操做名稱
          operation.name = @"myOperation";
      
          // 設置操做隊列優先級
          operation.queuePriority = NSOperationQueuePriorityNormal;
      
          // 設置操做服務質量
          operation.qualityOfService = NSQualityOfServiceUtility;
      
          // 等待操做完成
          [operation waitUntilFinished];
      
          // 在當前線程中開始執行操做
          [operation start];
      
          // 設置操做完成的回調
          [operation setCompletionBlock:^{
      
              NSLog(@"任務一完成了");
          }];
      
          // 設置操做完成的回調
          [operationQueue.operations[0] setCompletionBlock:^{
      
              NSLog(@"任務一完成了");
          }];
  • Swift

    • 隊列設置

      // 建立全局併發隊列
          let globalQueue:NSOperationQueue = NSOperationQueue()
      
          // 獲取當前隊列
          let currentQueue:NSOperationQueue? = NSOperationQueue.currentQueue()
      
          // 獲取住隊列
          let mainQueue:NSOperationQueue = NSOperationQueue.mainQueue()
      
          // 向隊列添加操做
          operationQueue.addOperation(operation)
      
          // 向隊列添加多個操做
          /*
              NO 異步的,YES 同步的
          */
          operationQueue.addOperations([operation1, operation2], waitUntilFinished: false)    
          // 向隊列添加操做 block
          operationQueue.addOperationWithBlock {
      
          }
      
          // 設置最大併發數
          /*
              默認狀況下是 -1,-1 表示沒有限制,會同時運行隊列中的所有的操做
          */
          operationQueue.maxConcurrentOperationCount = 4
      
          // 設置隊列名稱
          operationQueue.name = "myOperationQueue"
      
          // 設置隊列服務質量
          operationQueue.qualityOfService = .Utility
      
          // 設置隊列是否掛起
          /*
              YES 掛起,NO 繼續執行
              隊列掛起,當前 "沒有完成的操做" 是包含在隊列的操做數中的隊列掛起,不會影響已經執行操做的執行狀態
              對列一旦被掛起,再添加的操做不會被調度
          */
          operationQueue.suspended = true
      
          // 判斷隊列是否掛起
          let isSuspended:Bool = operationQueue.suspended
      
          // 給隊列中全部操做發送取消(cancel)消息
          /*
              在隊列取消完成以前,操做計數並不會發生變化
              系統的全部方法都沒有對 isCancelled 作判斷
              正在執行的操做不迴響應 cancel 消息,不會取消正在執行中的操做,不會影響隊列的掛起狀態
          */
          operationQueue.cancelAllOperations()
      
          // 獲取隊列中操做的數量
          let operationCount:Int = operationQueue.operationCount
      
          // 獲取隊列中的全部操做
          let operations:[NSOperation] = operationQueue.operations
      
          // 等待全部操做完成
          operationQueue.waitUntilAllOperationsAreFinished()
    • 操做設置

      // 設置操做間依賴關係
          /*
              operation2 依賴於 operation1
          */
          operation2.addDependency(operation1)
      
          // 移除操做間依賴關係
          operation2.removeDependency(operation1)
      
          // 獲取依賴的全部操做,readonly
          let dependencies:[NSOperation] = operation2.dependencies
      
          // 取消操做
          operation.cancel()
      
          // 判斷操做是否被取消,readonly
          let isCancelled:Bool = operation.cancelled
      
          // 判斷操做是否正在被執行,readonly
          let isExecuting:Bool = operation.executing
      
          // 判斷操做是否執行完成,readonly
          let isFinished:Bool = operation.finished
      
          // 判斷操做是不是異步執行的,readonly
          let isAsynchronous:Bool = operation.asynchronous
      
          // 判斷操做是否準備好執行,readonly
          let isReady:Bool = operation.ready
      
          // 設置操做名稱
          operation.name = "myOperation"
      
          // 設置操做隊列優先級
          operation.queuePriority = .Normal
      
          // 設置操做服務質量
          operation.qualityOfService = .Utility
      
          // 等待操做完成
          operation.waitUntilFinished()
      
          // 在當前線程中開始執行操做
          operation.start()
      
          // 設置操做完成的回調
          operation.completionBlock = {
      
              print("任務一完成了")
          }
      
          // 設置操做完成的回調
          operationQueue.operations[0].completionBlock = {
      
              print("任務一完成了")
          }

5.3 NSOperation 線程間通訊

  • 子線程裏不容許操做 UI

    • Objective-C

      [[[NSOperationQueue alloc] init] addOperationWithBlock:^{                                               
      
              // 在子隊列中執行耗時的操做
      
              [[NSOperationQueue mainQueue] addOperationWithBlock:^{                                              
      
                  // 在主隊列中執行刷新 UI 等操做
              }];
          }];
    • Swift

      NSOperationQueue().addOperationWithBlock {                                              
      
              // 在子隊列中執行耗時的操做
      
              NSOperationQueue.mainQueue().addOperationWithBlock({                                
      
                  // 在主隊列中執行刷新 UI 等操做
              })
          }

5.4 NSOperation 指定依賴關係

  • Objective-C

    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"用戶登陸 %@", [NSThread currentThread]);
        }];
    
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"付費 %@", [NSThread currentThread]);
        }];
    
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            [NSThread sleepForTimeInterval:1.0f];
            NSLog(@"下載 %@", [NSThread currentThread]);
        }];
    
        NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"更新UI %@", [NSThread currentThread]);
        }];
    
        // 付費以前須要登陸,在指定依賴關係時,不要出現循環依賴
        [op2 addDependency:op1];
    
        // 下載以前須要付費
        [op3 addDependency:op2];
    
        // 更新UI以前須要完成下載
        [op4 addDependency:op3];
    
        // NO 異步的,YES 同步的
        [[[NSOperationQueue alloc] init] addOperations:@[op1, op2, op3] waitUntilFinished:NO];
    
        // 更新 UI 的操做,應該由主隊列來調度
        [[NSOperationQueue mainQueue] addOperation:op4];
  • Swift

    let op1 = NSBlockOperation.init { 
            print("用戶登陸 \(NSThread.currentThread())")
        }
    
        let op2 = NSBlockOperation.init {
            print("付費 \(NSThread.currentThread())")
        }
    
        let op3 = NSBlockOperation.init {
            print("下載 \(NSThread.currentThread())")
        }
    
        let op4 = NSBlockOperation.init {
            print("更新UI \(NSThread.currentThread())")
        }
    
        // 付費以前須要登陸,在指定依賴關係時,不要出現循環依賴
        op2.addDependency(op1)
    
        // 下載以前須要付費
        op3.addDependency(op2)
    
        // 更新UI以前須要完成下載
        op4.addDependency(op3)
    
        // NO 異步的,YES 同步的
        NSOperationQueue().addOperations([op1, op2, op3], waitUntilFinished: false)
    
        // 更新 UI 的操做,應該由主隊列來調度
        NSOperationQueue.mainQueue().addOperation(op4)

5.5 自定義 NSOperation 操做

  • Objective-C

    • WebImageOperation.h

      @interface WebImageOperation : NSOperation
      
          ///  實例化 web 圖像操做
          + (instancetype)webImageOperationWithURLString:(NSString *)urlString 
                                              completion:(void (^)(UIImage *image))completion;
      
          @end
    • WebImageOperation.m

      /// 下載圖片的 URL
          @property (nonatomic, copy) NSString *urlStr;
      
          /// 下載完成的回調
          @property (nonatomic, copy) void (^completion) (UIImage *image);
      
          + (instancetype)webImageOperationWithURLString:(NSString *)urlString completion:(void (^)(UIImage *))completion {
      
              WebImageOperation *imageOperation = [[self alloc] init];
      
              imageOperation.urlStr= urlString;
              imageOperation.completion = completion;
      
              return imageOperation;
          }
      
          // 操做加入隊列後會自動執行該方法
          - (void)main {
              @autoreleasepool {
      
                  if (self.isCancelled) return;
      
                  NSURL *url = [NSURL URLWithString:self.urlStr];
                  NSData *data = [NSData dataWithContentsOfURL:url];
      
                  if (self.isCancelled) return;
      
                  if (self.completion && data != nil) {
      
                      [[NSOperationQueue mainQueue] addOperationWithBlock:^{
      
                          self.completion([UIImage imageWithData:data]);  
                      }];
                  }
              }
          }
    • ViewController.m

      // 自定義 NSOperation 操做
          WebImageOperation *downloadOperation = [WebImageOperation webImageOperationWithURLString:imageUrlPath 
                                                                                        completion:^(UIImage *image) {
      
              self.imageView.image = image;
          }];
      
          // 將操做添加到隊列
          [[[NSOperationQueue alloc] init] addOperation:downloadOperation];
  • Swift

    • WebImageOperation.swift

      class WebImageOperation: NSOperation
      
          /// 下載圖片的 URL
          var urlStr:String!
      
          /// 下載完成的回調
          var completion:((image:UIImage) -> Void)!
      
          class func webImageOperationWithURLString(urlString:String, 
                                         completion:((image:UIImage) -> Void)) -> WebImageOperation {
      
              let imageOperation:WebImageOperation = WebImageOperation()
      
              imageOperation.urlStr = urlString
              imageOperation.completion = completion
      
              return imageOperation
          }
      
          // 操做加入隊列後會自動執行該方法
          override func main() {
      
              if self.cancelled == true {
                  return
              }
      
              let url:NSURL = NSURL(string: self.urlStr)!
              let data:NSData? = NSData(contentsOfURL: url)
      
              if self.cancelled == true {
                  return
              }
      
              if (self.completion != nil) && (data != nil) {
      
                  NSOperationQueue.mainQueue().addOperationWithBlock({
      
                      self.completion(image: UIImage(data: data!)!)
                  })
              }
          }
    • ViewController.swift

      // 自定義 NSOperation 操做
          let downloadOperation:WebImageOperation = WebImageOperation.webImageOperationWithURLString(imageUrlPath) 
                                                                     { (image:UIImage) in
      
              self.imageView.image = image
          }
      
          // 將操做添加到隊列
          NSOperationQueue().addOperation(downloadOperation)
相關文章
相關標籤/搜索