1. 程式人生 > >【iOS沉思錄】NSThread、GCD、NSOperation多執行緒程式設計總結

【iOS沉思錄】NSThread、GCD、NSOperation多執行緒程式設計總結

OC中的多執行緒

OC中多執行緒根據封裝程度可以分為三個層次:NSThreadGCDNSOperation,另外由於OC相容C語言,因此仍然可以使用C語言的POSIX介面來實現多執行緒,只需引入相應的標頭檔案:#include <pthread.h>

NSThread

NSThread是封裝程度最小最輕量級的,使用更靈活,但要手動管理執行緒的生命週期、執行緒同步和執行緒加鎖等,開銷較大;

NSThread的基本使用比較簡單,可以動態建立初始化NSThread物件,對其進行設定然後啟動;也可以通過NSThread的靜態方法快速建立並啟動新執行緒;此外NSObject基類物件還提供了隱式快速建立NSThread執行緒的performSelector系列類別擴充套件工具方法;NSThread還提供了一些靜態工具介面來控制當前執行緒以及獲取當前執行緒的一些資訊。

下面以在一個UIViewController中為例展示NSThread的使用方法:

- (void)viewDidLoad {
    [super viewDidLoad];

    /** NSThread靜態工具方法 **/
    /* 1 是否開啟了多執行緒 */
    BOOL isMultiThreaded = [NSThread isMultiThreaded];
    /* 2 獲取當前執行緒 */
    NSThread *currentThread = [NSThread currentThread];
    /* 3 獲取主執行緒 */
    NSThread *mainThread = [NSThread
mainThread]; NSLog(@"main thread"); /* 4 睡眠當前執行緒 */ /* 4.1 執行緒睡眠5s鍾 */ [NSThread sleepForTimeInterval:5]; /* 4.2 執行緒睡眠到指定時間,效果同上 */ [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]]; /* 5 退出當前執行緒,注意不要在主執行緒呼叫,防止主執行緒被kill掉 */ //[NSThread exit]; NSLog(@"main thread"
); /** NSThread執行緒物件基本建立,target為入口函式所在的物件,selector為執行緒入口函式 **/ /* 1 執行緒例項物件建立與設定 */ NSThread *newThread= [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; /* 設定執行緒優先順序threadPriority(0~1.0),即將被拋棄,將使用qualityOfService代替 */ newThread.threadPriority = 1.0; newThread.qualityOfService = NSQualityOfServiceUserInteractive; /* 開啟執行緒 */ [newThread start]; /* 2 靜態方法快速建立並開啟新執行緒 */ [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; [NSThread detachNewThreadWithBlock:^{ NSLog(@"block run..."); }]; /** NSObejct基類隱式建立執行緒的一些靜態工具方法 **/ /* 1 在當前執行緒上執行方法,延遲2s */ [self performSelector:@selector(run) withObject:nil afterDelay:2.0]; /* 2 在指定執行緒上執行方法,不等待當前執行緒 */ [self performSelector:@selector(run) onThread:newThread withObject:nil waitUntilDone:NO]; /* 3 後臺非同步執行函式 */ [self performSelectorInBackground:@selector(run) withObject:nil]; /* 4 在主執行緒上執行函式 */ [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO]; } - (void)run { NSLog(@"run..."); }

GCD大中央排程

GCD(Grand Central Dispatch),又叫大中央排程,對執行緒操作進行了封裝,加入了很多新的特性,內部進行了效率優化,提供了簡潔的C語言介面,使用更加簡單高效,也是蘋果推薦的方式。

  • 同步dispatch_sync與非同步dispatch_async任務派發
  • 序列佇列與併發佇列dispatch_queue_t
  • dispatch_once_t只執行一次
  • dispatch_after延後執行
  • dispatch_group_t組排程

兩個關鍵概念

序列與併發(Serial和Concurrent):

這個概念在建立操作佇列的時候有巨集定義引數,用來指定建立的是序列佇列還是並行佇列。

序列指的是佇列內任務一個接一個的執行,任務之間要依次等待不可重合,且新增的任務按照先進先出FIFO的順序執行,但並不是指這就是單執行緒,只是同一個序列佇列內的任務需要依次等待排隊執行避免出現競態條件,但仍然可以建立多個序列佇列並行的執行任務,也就是說,序列佇列內是序列的,序列佇列之間仍然是可以並行的,同一個序列佇列內的任務的執行順序是確定的(FIFO),且可以建立任意多個序列佇列;

並行指的是同一個佇列先後新增的多個任務可以同時並列執行,任務之間不會相互等待,且這些任務的執行順序執行過程不可預測。

同步和非同步任務派發(Synchronous和Asynchronous):

GCD多執行緒程式設計時經常會使用dispatch_async和dispatch_sync函式往指定佇列中新增任務塊,區別就是同步和非同步。同步指的是阻塞當前執行緒,要等新增的耗時任務塊block完成後,函式才能返回,後面的程式碼才可以繼續執行。如果在主線上,則會發生阻塞,使用者會感覺應用不響應,這是要避免的。而有時需要使用同步任務的原因是想保證先後新增的任務要按照編寫的邏輯順序依次執行;非同步指的是將任務新增到佇列後函式立刻返回,後面的程式碼不用等待新增的任務完成返回即可繼續執行。

dispatch_syncdispatch_async

通過下面的程式碼比較非同步和同步任務的區別:

/* 1. 提交非同步任務 */
NSLog(@"開始提交非同步任務:");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    /* 耗時任務... */
    [NSThread sleepForTimeInterval:10];
});
NSLog(@"非同步任務提交成功!");

/* 2. 提交同步任務 */
NSLog(@"開始提交同步任務:");
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    /* 耗時任務... */
    [NSThread sleepForTimeInterval:10];
});
NSLog(@"同步任務提交成功!");

列印結果:

2017-02-28 16:01:44.643 SingleView[19100:708069] 開始提交非同步任務:
2017-02-28 16:01:44.643 SingleView[19100:708069] 非同步任務提交成功!
2017-02-28 16:01:44.644 SingleView[19100:708069] 開始提交同步任務:
2017-02-28 16:01:54.715 SingleView[19100:708069] 同步任務提交成功!

通過列印結果的時間可以看出,非同步任務提交後立即就執行下一步列印提交成功了,不會阻礙當前執行緒,提交的任務會在後臺去執行;而提交同步任務要等到提交的後臺任務結束後才可以繼續執行當前執行緒的下一步。此處在主執行緒上新增的同步任務就會阻塞主執行緒,導致後面介面的顯示要延遲,影響使用者體驗。

dispatch_queue_t
操作佇列主要有兩種,併發佇列和序列佇列,它們的區別上面已經提到,具體建立的方法很簡單,要提供兩個引數,一個是標記該自定義佇列的唯一字串,另一個是指定序列佇列還是併發佇列的巨集引數:

/* 建立一個併發佇列 */
dispatch_queue_t concurrent_queue = dispatch_queue_create("demo.gcd.concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
/* 建立一個序列佇列 */
dispatch_queue_t serial_queue = dispatch_queue_create("demo.gcd.serial_queue", DISPATCH_QUEUE_SERIAL);

另外GCD還提供了幾個常用的全域性佇列以及主佇列,獲取方法如下:

// 獲取主佇列(在主執行緒上執行)
dispatch_queue_t main_queue = dispatch_get_main_queue();
// 獲取不同優先順序的全域性佇列(優先順序從高到低)
dispatch_queue_t queue_high = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t queue_default = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue_low = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t queue_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

dispatch_once_t
這個函式控制指定程式碼只會被執行一次,常用來實現單例模式,這裡以單例模式實現的模板程式碼為例展示dispatch_once_t的用法,其中的例項化語句只會被執行一次:

+ (instancetype *)sharedInstance {
    static dispatch_once_t once = 0;
    static id sharedInstance = nil;
    dispatch_once(&once, ^{
        // 只例項化一次
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

dispatch_after
通過該函式可以讓要提交的任務在從提交開始後的指定時間後執行,也就是定時延遲執行提交的任務,使用方法很簡單:

    // 定義延遲時間:3s
    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
    dispatch_after(delay, dispatch_get_main_queue(), ^{
        // 要執行的任務...
    });

dispatch_group_t

組排程可以實現等待一組操作都完成後執行後續操作,典型的例子是大圖片的下載,例如可以將大圖片分成幾塊同時下載,等各部分都下載完後再後續將圖片拼接起來,提高下載的效率。使用方法如下:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*操作1 */ });
dispatch_group_async(group, queue, ^{ /*操作2 */ });
dispatch_group_async(group, queue, ^{ /*操作3 */ }); 
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 後續操作...
});

同步程式碼到主執行緒

對於UI的更新程式碼,必須要在主執行緒上執行才會及時有效,噹噹前程式碼不在主執行緒時,需要將UI更新的部分程式碼單獨同步到主執行緒,同步的方法有三種,可以使用NSThread類的performSelectorOnMainThread方法或者NSOperationQueue類的mainQueue主佇列來進行同步,但推薦直接使用GCD方法:

dispatch_async(dispatch_get_main_queue(), ^{
        // UI更新程式碼...
    });

NSOperation

NSOperation是基於GCD的一個抽象基類,將執行緒封裝成要執行的操作,不需要管理執行緒的生命週期和同步,但比GCD可控性更強,例如可以加入操作依賴(addDependency)、設定操作佇列最大可併發執行的操作個數(setMaxConcurrentOperationCount)、取消操作(cancel)等。NSOperation作為抽象基類不具備封裝我們的操作的功能,需要使用兩個它的實體子類:NSBlockOperation和NSInvocationOperation,或者繼承NSOperation自定義子類。

NSBlockOperation和NSInvocationOperation用法的主要區別是:前者執行指定的方法,後者執行程式碼塊,相對來說後者更加靈活易用。NSOperation操作配置完成後便可呼叫start函式在當前執行緒執行,如果要非同步執行避免阻塞當前執行緒則可以加入NSOperationQueue中非同步執行。他們的簡單用法如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    /* NSInvocationOperation初始化 */
    NSInvocationOperation *invoOpertion = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    [invoOpertion start];

    /* NSBlockOperation初始化 */
    NSBlockOperation *blkOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperation");
    }];
    [blkOperation start];
}

- (void)run {
    NSLog(@"NSInvocationOperation");
}

另外NSBlockOperation可以後續繼續新增block執行塊,操作啟動後會在不同執行緒併發的執行這些執行快:

/* NSBlockOperation初始化 */
NSBlockOperation *blkOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"NSBlockOperationA");
}];
[blkOperation addExecutionBlock:^{
    NSLog(@"NSBlockOperationB");
}];
[blkOperation addExecutionBlock:^{
    NSLog(@"NSBlockOperationC");
}];
[blkOperation start];
2017-04-04 11:27:02.805 SingleView[12008:3666657] NSBlockOperationB
2017-04-04 11:27:02.805 SingleView[12008:3666742] NSBlockOperationC
2017-04-04 11:27:02.805 SingleView[12008:3666745] NSBlockOperationA

另外說了NSOperation的可控性比GCD要強,其中一個非常重要的特性是可以設定各操作之間的依賴,即強行規定操作A要在操作B完成之後才能開始執行,成為操作A依賴於操作B:

/* 獲取主佇列(主執行緒) */
NSOperationQueue *queue = [NSOperationQueue mainQueue];
/* 建立a、b、c操作 */
NSOperation *c = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"OperationC");
}];
NSOperation *a = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"OperationA");
}];
NSOperation *b = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"OperationB");
}];
/* 新增操作依賴,c依賴於a和b,這樣c一定會在a和b完成後才執行,即順序為:A、B、C */
[c addDependency:a];
[c addDependency:b];
/* 新增操作a、b、c到操作佇列queue(特意將c在a和b之前新增) */
[queue addOperation:c];
[queue addOperation:a];
[queue addOperation:b];

NSBlockOperation和NSInvocationOperation可以滿足多數情況下的程式設計需求,如果需求特殊則需要繼承NSOperation類自定義子類來更加靈活的實現。

常見面試題

問題:什麼是執行緒?它與程序有什麼區別?為什麼要使用多執行緒?

執行緒是指程式在執行過程中,能夠執行程式程式碼的一個執行單元。執行緒主要有四種狀態:執行、就緒、掛起、結束。

程序是指一段正在執行的程式。而執行緒有時候也被稱為輕量級程序,是程式執行的最小單元,一個程序可以擁有多個執行緒,各個執行緒之間共享程式的記憶體空間(程式碼段、資料段和堆空間)及一些程序級的資源(例如開啟的檔案),但是各個執行緒擁有自己的棧空間,程序與執行緒的關係如下圖所示:

這裡寫圖片描述

在作業系統級別上,程式的執行都是以程序為單位的,而每個程序中通常都會有多個執行緒互不影響地併發執行,那麼為什麼要使用多執行緒呢?其實,多執行緒的使用為程式研發帶來了巨大的便利,具體而言,有以下幾個方面的內容:

  1. 使用多執行緒可以減少程式的響應時間。在單執行緒(單執行緒指的是程式執行過程中只有一個有效操作的序列,不同操作之間都有明確的執行先後順序)的情況下,如果某個操作很耗時,或者陷入長時間的等待(如等待網路響應),此時程式將不會響應滑鼠和鍵盤等操作,使用多執行緒後,可以把這個耗時的執行緒分配到一個單獨的執行緒去執行,使得程式具備了更好的互動性。
  2. 與程序相比,執行緒的建立和切換開銷更小。由於啟動一個新的執行緒必須給這個執行緒分配獨立的地址空間,建立許多資料結構來維護執行緒程式碼段、資料段等資訊,而運行於同一程序內的執行緒共享程式碼段、資料段,執行緒的啟動或切換的開銷比程序要少很多。同時多執行緒在資料共享方面效率非常高。
  3. 多CPU或多核計算機本身就具有執行多執行緒的能力,如果使用單個執行緒,將無法重複利用計算機資源,造成資源的巨大浪費。因此在多CPU計算機上使用多執行緒能提高CPU的利用率。
  4. 使用多執行緒能簡化程式的結構,使程式便於理解和維護。一個非常複雜的程序可以分成多個執行緒來執行。

問題: 列舉Cocoa中常見的幾種多執行緒的實現,並談談多執行緒安全的幾種解決辦法,一般什麼地方會用到多執行緒?

問的是三個層次的多執行緒程式設計實現;執行緒鎖的使用;

  • 只在主執行緒重新整理訪問UI
  • 如果要防止資源搶奪,得用synchronized進行加鎖保護
  • 如果非同步操作要保證執行緒安全等問題, 儘量使用GCD(有些函式預設 就是安全的)

問題: 在iphone上有兩件事情要做,請問是在一個執行緒裡按順序做效率高還是兩個執行緒裡做效率高?為什麼?

這裡的效率高指的是時間上效率高,也就是希望在最短的時間內完成所有任務,兩件事情按順序做意味著序列執行,第二件事情要等待第一件事情結束後才可開始,效率相對很低。但如果利用兩個執行緒讓兩件事情能夠併發執行,則時間上效率會大大提高。

問題: 對比在OS X和iOS中實現併發性的不同方法。

在iOS中有三種方法可以實現併發性:threads、dispatch queues和operation queues。

threads的缺點是開發者要自己建立合適的併發解決方案,要決定建立多少執行緒並且要根據情況動態調整執行緒的數量。並且應用還要為建立和維護執行緒承擔主要代價, 因此OS X和IOS系統並不是依靠執行緒來解決併發問題的,而是採用了非同步設計的途徑。

其中一個開啟非同步任務的技術是GCD(Grand Central Dispatch),讓系統層次來對執行緒進行管理。開發者要做的就是定義要執行的任務並將之新增到合適的dispatch分派佇列。GCD會負責建立需要的執行緒並安排任務在這些執行緒上執行。所有的dispatch佇列都是先進先出(FIFO)佇列結構,所以任務的執行順序是和新增順序是一致的。

operation queues和併發dispatch佇列一樣都是通過Cocoa框架的NSOperationQueue類實現的,不過它並不一定是按照先進先出的順序執行任務的,而是支援建立更復雜的執行順序圖來管理任務的執行順序。

問題: 使用者下載一個圖片,圖片很大,需要分成很多份進行下載,使用GCD應該如何實現?使用什麼佇列?

使用Dispatch Group追加block到Global Group Queue,這些block如果全部執行完畢,就會執行Main Dispatch Queue中的結束處理的block。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*載入圖片1 */ });
dispatch_group_async(group, queue, ^{ /*載入圖片2 */ });
dispatch_group_async(group, queue, ^{ /*載入圖片3 */ }); 
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 合併圖片
});

問題:Dispatch_barrier_(a)sync的作用?

通過Dispatch_barrier_async新增的操作會暫時阻塞當前佇列,即等待前面的併發操作都完成後執行該阻塞操作,待其完成後後面的併發操作才可繼續。可以將其比喻為一座霸道的獨木橋,是併發佇列中的一個併發障礙點,臨時阻塞並獨佔。

可見使用Dispatch_barrier_async可以實現類似dispatch_group_t組排程的效果,同時主要的作用是避免資料競爭,高效訪問資料。

/* 建立併發佇列 */
dispatch_queue_t concurrentQueue = dispatch_queue_create("test.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
/* 新增兩個併發操作A和B,即A和B會併發執行 */
dispatch_async(concurrentQueue, ^(){
    NSLog(@"OperationA");
});
dispatch_async(concurrentQueue, ^(){
    NSLog(@"OperationB");
});
/* 新增barrier障礙操作,會等待前面的併發操作結束,並暫時阻塞後面的併發操作直到其完成 */
dispatch_barrier_async(concurrentQueue, ^(){
    NSLog(@"OperationBarrier!");
});
/* 繼續新增併發操作C和D,要等待barrier障礙操作結束才能開始 */
dispatch_async(concurrentQueue, ^(){
    NSLog(@"OperationC");
});
dispatch_async(concurrentQueue, ^(){
    NSLog(@"OperationD");
});
2017-04-04 12:25:02.344 SingleView[12818:3694480] OperationB
2017-04-04 12:25:02.344 SingleView[12818:3694482] OperationA
2017-04-04 12:25:02.345 SingleView[12818:3694482] OperationBarrier!
2017-04-04 12:25:02.345 SingleView[12818:3694482] OperationD
2017-04-04 12:25:02.345 SingleView[12818:3694480] OperationC

問題: 用過NSOperationQueue嗎?如果用過或者瞭解的話,為什麼要使用 NSOperationQueue?實現了什麼?簡述它和GCD的區別和類似的地方(提示:可以從兩者的實現機制和適用範圍來述)。

使用NSOperationQueue用來管理子類化的NSOperation物件,控制其執行緒併發數目。GCD和NSOperation都可以實現對執行緒的管理,區別是NSOperation和NSOperationQueue是多執行緒的面向物件抽象。專案中使用NSOperation的優點是 NSOperation是對執行緒的高度抽象,在專案中使用它,會使專案的程式結構更好,子類化 NSOperation的設計思路,是具有面向物件的優點(複用、封裝),使得實現是多執行緒支援,而介面簡單,建議在複雜專案中使用。專案中使用GCD的優點是GCD本身非常簡單、易用,對於不復雜的多執行緒操作,會節省程式碼量,而Block引數的使用,會使程式碼更為易讀,建議在簡單專案中使用。

  • GCD是純C語言的API,NSOperationQueue是基於GCD的OC版本封裝

  • GCD只支援FIFO的佇列,NSOperationQueue可以很方便地調整執行順 序、設定最大併發數量

  • NSOperationQueue可以在輕鬆在Operation間設定依賴關係,而GCD 需要寫很多的程式碼才能實現

  • NSOperationQueue支援KVO,可以監測operation是否正在執行 (isExecuted)、是否結束(isFinished),是否取消(isCanceld) 5> GCD的執行速度比NSOperationQueue快 任務之間不太互相依賴:GCD 任務之間有依賴\或者要監聽任務的執行情況:NSOperationQueue

問題: 在使用GCD以及block時要注意些什麼?它們兩是一回事兒麼?block在ARC中和傳統的MRC中的行為和用法有沒有什麼區別,需要注意些什麼?

使用block是要注意,若將block做函式引數時,需要把它放到最後,GCD是Grand Central Dispatch,是一個對執行緒開源類庫,而Block是閉包,是能夠讀取其他函式內部變數的函式。

問題: 在專案什麼時候選擇使用GCD,什麼時候選擇 NSOperation?

專案中使用NSOperation的優點是NSOperation是對執行緒的高度抽象,在專案中使用它,會使專案的程式結構更好,子類化NSOperation的設計思路,是具有面向物件的優點(複用、封裝),使得實現多執行緒支援,而介面簡單,建議在複雜專案中使用。

專案中使用GCD的優點是GCD本身非常簡單、易用,對於不復雜的多執行緒操作,會節省程式碼量,而Block引數的使用,會是程式碼更為易讀,建議在簡單專案中使用。

問題:對於a,b,c三個執行緒,如何使用用NSOpertion和NSOpertionQueue實現執行完a,b後再執行c?

可以通過NSOpertion的依賴特性實現該需求,即讓c依賴於a和b,這樣只有a和b都執行完後,c才可以開始執行:

/* 獲取主佇列(主執行緒) */
NSOperationQueue *queue = [NSOperationQueue mainQueue];
/* 建立a、b、c操作 */
NSOperation *c = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Operation C Start!");
    // ... ...
}];
NSOperation *a = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Operation A Start!");
    [NSThread sleepForTimeInterval:3.0];
    NSLog(@"Operation A Done!");
}];
NSOperation *b = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Operation B Start!");
    [NSThread sleepForTimeInterval:3.0];
    NSLog(@"Operation B Done!");
}];
/* 新增操作依賴,c依賴於a和b */
[c addDependency:a];
[c addDependency:b];
/* 新增操作a、b、c到操作佇列queue(特意將c在a和b之前新增) */
[queue addOperation:c];
[queue addOperation:a];
[queue addOperation:b];

列印結果:

2017-03-18 13:51:37.770 SingleView[15073:531745] Operation A Start!
2017-03-18 13:51:40.772 SingleView[15073:531745] Operation A Done!
2017-03-18 13:51:40.775 SingleView[15073:531745] Operation B Start!
2017-03-18 13:51:43.799 SingleView[15073:531745] Operation B Done!
2017-03-18 13:51:43.800 SingleView[15073:531745] Operation C Start!

**問題:**GCD內部是怎麼實現的?

  • iOS和OS X的核心是XNU核心,GCD是基於XNU核心實現的

  • GCD的API全部在libdispatch庫中

  • GCD的底層實現主要有Dispatch Queue和Dispatch Source

  • Dispatch Queue :管理block(操作)

  • Dispatch Source :處理事件

問題:既然提到GCD,那麼問一下在使用GCD以及 block 時要注意些什麼?它們兩是一回事兒麼? block 在 ARC 中和傳統的 MRC 中的行為和用法有 沒有什麼區別,需要注意些什麼?

Block的使用注意:

  1. block的記憶體管理
    • 防止迴圈retian
    • 非ARC(MRC):__block
    • ARC:__weak__unsafe_unretained

問題:下面程式碼有什麼問題?

int main(int argc, const char * argv[]) {
    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
    return 0;
}

main函式中第二句程式碼在主執行緒上使用了dispatch_sync同步向主執行緒派發任務,而同步派發要等到任務完成後才能返回,阻塞當前執行緒。也就是說執行到此處,主執行緒被阻塞,同時又要等主執行緒執行完成該任務,造成主執行緒自身的等待迴圈,也就是死鎖。程式執行到此處會崩潰。將dispatch_sync改為dispatch_async非同步派發任務即可避免死鎖,或者將任務派發到其他佇列上而不是主佇列。

問題: 簡單介紹下NSURLConnection類,以及+sendSynchronousRequest:returningResponse:error:與– initWithRequest:delegate:兩個方法的區別?

NSURLConnection主要用於網路連結請求,提供了非同步和同步兩種請求方式,非同步請求會新建立一個執行緒單獨用於之後的下載,不會阻塞當前呼叫的執行緒;同步請求會阻塞當前呼叫執行緒,等待它下載結束,如果在主執行緒上進行同步請求則會阻塞主執行緒,造成介面卡頓。

+sendSynchronousRequest:returningResponse:error:是同步請求資料,會阻塞當前的執行緒,直到request返回response;

–initWithRequest:delegate:是非同步請求資料,不會足阻塞當前執行緒,當資料請求結束後會通過代理回到主執行緒,並通知它委託的物件。

問題:
UIKit類要在哪一個應用執行緒上使用?

UIKit的介面類只能在主執行緒上使用,對介面進行更新,多執行緒環境中要對介面進行更新必須要切換到主執行緒上。

問題: 以下程式碼有什麼問題?如何修復?

@interface TTWaitController : UIViewController

@property (strong, nonatomic) UILabel *alert;

@end

@implementation TTWaitController

- (void)viewDidLoad
{
    CGRect frame = CGRectMake(20, 200, 200, 20);
    self.alert = [[UILabel alloc] initWithFrame:frame];
    self.alert.text = @"Please wait 10 seconds...";
    self.alert.textColor = [UIColor whiteColor];
    [self.view addSubview:self.alert];

    NSOperationQueue *waitQueue = [[NSOperationQueue alloc] init];
    [waitQueue addOperationWithBlock:^{
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
        self.alert.text = @"Thanks!";
    }];
}

@end

@implementation TTAppDelegate

- (BOOL)application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.rootViewController = [[TTWaitController alloc] init];
    [self.window makeKeyAndVisible];
    return YES;
}

這段程式碼是想提醒使用者等待10s,10s後在標籤上顯示“Thanks”,但多執行緒程式碼部分NSOperationQueue的addOperationWithBlock函式不能保證block裡面的語句是在主執行緒中執行的,UILabel顯示文字屬於UI更新,必須要在主執行緒進行,否則會有未知的操作,無法在介面上及時正常顯示。

解決方法是將UI更新的程式碼寫在主執行緒上即可,程式碼同步到主執行緒上主要有三種方法:NSThread、NSOperationQueue和GCD,三個層次的多執行緒都可以獲取主執行緒並同步。

NSThread級主執行緒同步:

NSOperationQueue *waitQueue = [[NSOperationQueue alloc] init];
[waitQueue addOperationWithBlock:^{
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
    // 同步到主執行緒
    [self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO];
}];

/**
 * UI更新函式
 */
- (void)updateUI {
    self.alert.text = @"Thanks!";
}

NSOperationQueue級主執行緒同步:

NSOperationQueue *waitQueue = [[NSOperationQueue alloc] init];
[waitQueue addOperationWithBlock:^{
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
    // 同步到主執行緒
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        self.alert.text = @"Thanks!";
    }];
}];

GCD級主執行緒同步:

NSOperationQueue *waitQueue = [[NSOperationQueue alloc] init];
[waitQueue addOperationWithBlock:^{
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
    // 同步到主執行緒
    dispatch_async(dispatch_get_main_queue(), ^{
        self.alert.text = @"Thanks!";
    });
}];