多線程的重要性沒必要多言,現代操做系統不可能離開進程線程的抽象。具體到ios應用,咱們只能在一個進程中管理線程,主線程不該該去執行很是耗時間的後臺操做致使出現卡機現象,後臺的事情交給後臺線程來完成。ios
Grand Central Dispatch程序員
GCD組件包含兩大部分,第一個部分是提供了C語言線程同步互斥接口,第二個部分就是dispatch隊列。web
dispatch block的執行最終都會放進某個隊列中去進行,它相似NSOperationQueue但更復雜也更強大,而且能夠嵌套使用。因此說,結合block實現的GCD,把函數閉包(Closure)的特性發揮得淋漓盡致。數據庫
dispatch隊列的生成能夠有這幾種方式:編程
1. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_SERIAL); //生成一個串行隊列,隊列中的block按照先進先出(FIFO)的順序去執行,實際上爲單線程執行。第一個參數是隊列的名稱,在調試程序時會很是有用,全部儘可能不要重名了。安全
2. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT); //生成一個併發執行隊列,block被分發到多個線程去執行網絡
3. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //得到程序進程缺省產生的併發隊列,可設定優先級來選擇高、中、低三個優先級隊列。因爲是系統默認生成的,因此沒法調用dispatch_resume()和dispatch_suspend()來控制執行繼續或中斷。須要注意的是,三個隊列不表明三個線程,可能會有更多的線程。併發隊列能夠根據實際狀況來自動產生合理的線程數,也可理解爲dispatch隊列實現了一個線程池的管理,對於程序邏輯是透明的。多線程
官網文檔解釋說共有三個併發隊列,但實際還有一個更低優先級的隊列,設置優先級爲DISPATCH_QUEUE_PRIORITY_BACKGROUND。Xcode調試時能夠觀察到正在使用的各個dispatch隊列。閉包
4. dispatch_queue_t queue = dispatch_get_main_queue(); //得到主線程的dispatch隊列,注意這個隊列中的任務須要被主線程完成。一樣沒法控制主線程dispatch隊列的執行繼續或中斷。併發
接下來咱們可使用dispatch_async或dispatch_sync函數來加載須要運行的block。
dispatch_async(queue, ^{
//block具體代碼
}); //異步執行block,函數當即返回
dispatch_sync(queue, ^{
//block具體代碼
}); //同步執行block,函數不返回,一直等到block執行完畢。編譯器會根據實際狀況優化代碼,因此有時候你會發現block其實還在當前線程上執行,並沒用產生新線程。
實際編程經驗告訴咱們,儘量避免使用dispatch_sync,嵌套使用時還容易引發程序死鎖。
若是queue1是一個串行隊列的話,這段代碼當即產生死鎖:
dispatch_sync(queue1, ^{
dispatch_sync(queue1, ^{
......
});
......
});
在主線程當中,爲何下面代碼也確定死鎖:
dispatch_sync(dispatch_get_main_queue(), ^{
......
});
那實際運用中,通常能夠用dispatch這樣來寫,常見的網絡請求數據多線程執行模型:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//子線程中開始網絡請求數據
//更新數據模型
dispatch_sync(dispatch_get_main_queue(), ^{
//在主線程中更新UI代碼
});
});
程序的後臺運行和UI更新代碼緊湊,代碼邏輯一目瞭然。
dispatch隊列是線程安全的,能夠利用串行隊列實現鎖的功能。好比多線程寫同一數據庫,須要保持寫入的順序和每次寫入的完整性,簡單地利用串行隊列便可實現:
dispatch_queue_t queue1 = dispatch_queue_create("com.dispatch.writedb", DISPATCH_QUEUE_SERIAL);
- (void)writeDB:(NSData *)data
{
dispatch_async(queue1, ^{
//write database
});
}
下一次調用writeDB:必須等到上次調用完成後才能進行,保證writeDB:方法是線程安全的。
dispatch隊列還實現其它一些經常使用函數,包括:
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t)); //重複執行block,須要注意的是這個方法是同步返回,也就是說等到全部block執行完畢才返回,如需異步返回則嵌套在dispatch_async中來使用。多個block的運行是否併發或串行執行也依賴queue的是否併發或串行。
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); //這個函數能夠設置同步執行的block,它會等到在它加入隊列以前的block執行完畢後,纔開始執行。在它以後加入隊列的block,則等到這個block執行完畢後纔開始執行。
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); //同上,除了它是同步返回函數
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); //延遲執行block
最後再來看看dispatch隊列的一個頗有特點的函數:
void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
它會把須要執行的任務對象指定到不一樣的隊列中去處理,這個任務對象能夠是dispatch隊列,也能夠是dispatch源。並且這個過程能夠是動態的,能夠實現隊列的動態調度管理等等。好比說有兩個隊列dispatchA和dispatchB,這時把dispatchA指派到dispatchB:
dispatch_set_target_queue(dispatchA, dispatchB);
那麼dispatchA上還未運行的block會在dispatchB上運行。這時若是暫停dispatchA運行:
dispatch_suspend(dispatchA);
則只會暫停dispatchA上原來的block的執行,dispatchB的block則不受影響。而若是暫停dispatchB的運行,則會暫停dispatchA的運行。
這裏只簡單舉個例子,說明dispatch隊列運行的靈活性,在實際應用中你會逐步發掘出它的潛力。
優勢:簡單方便,直接捕獲變量進入block(只讀訪問,除非用__block聲明)。
缺點:dispatch隊列暫時不支持cancel(取消),沒有實現dispatch_cancel()函數,不像NSOperationQueue。
ps: GCD所包含的方法並不限於任務隊列,還提供了 dispatch_semaphore_signal/dispatch_semaphore_wait 這樣的C語言接口(全部C語言代碼都能用OC編譯器編譯),並不會強制要求程序員必定要面向對象,避免了不少繁瑣步驟。
ps: !!! 重要的一點:block並非被主線程所執行,若是其它的線程在執行這個block是,調用了相似 _webView = [[UIWebView alloc] init]這樣的語句,那麼就會Crash:
Tried to obtain the web lock from a thread other than the main thread or the web thread.
This may be a result of calling to UIKit from a secondary thread. Crashing now...
解決辦法就是,經過performSelectorOnMainThread或者GCD dispatch_的方式強制讓某些語句被主線程執行,或者經過信號量實現同步的方式來發送網絡請求,從而保證由主線程來調用全部涉及UI的語句。
NSObject
全部繼承於NSObject類的類實例,均可以調用啓動多線程的方法。
如下3個函數是多線程的方法:
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg,建立一個真正的新線程,不過沒法引用到這個線程
[self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES]; 通知主線程執行操做,通常用於更新界面
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES]; 等待selector指定的函數被某個線程執行完成後,當前線程再繼續當前任務
如下2個函數實際上不是多線程:
[self performSelector:@selector(run) withObject:nil];等待selector指定的函數被當前線程執行完成後,當前線程再繼續當前任務
performSelector:withObject:afterDelay:當前線程執行完成後,再啓動線程去執行selector所選擇的方法
Objective-C中調用函數的方法是「消息傳遞」,這個和普通的函數調用的區別是,你能夠隨時對一個對象傳遞任何消息,而不須要在編譯的時候聲明這些方法。因此Objective-C能夠在runtime的時候傳遞人和消息。
優勢:簡單方便,而且容許在運行時動態調用一個對象的任意方法
缺點:若是須要傳遞參數,不如GCD
NSThread
NSThread類是輕量級的多線程開發的類,使用起來也並不複雜,可是使用NSThread須要本身管理線程生命週期。NSThread中封裝了方法:
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument 建立一個真實的新線程,不過沒法引用到這個線程
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 建立一個線程對象,能夠引用到一個新線程,調用start方法建立真實的線程。
[thread start]; 建立一個真實的線程,而且把線程對象thread做爲這個線程的引用
[thread cancel]; 取消對象thread所引用的那個線程
這個類命名空間下還提供了一些實用的類方法:
NSThread *current = [NSThread currentThread]; 獲取當前線程
NSThread *main = [NSThread mainThread]; 獲取主線程
[NSThread sleepForTimeInterval:2]; 或 [NSThread sleepUntilDate:date]; 暫停正在執行這段代碼的進程,也就是當前進程
優勢:以面對對象的觀點引用線程,方便線程生命週期的管理和多線程之間的同步;這個類能夠配合performselector方法使用;提供了一些實用的類方法,如獲取當前線程的引用、暫停當前線程等。
缺點:須要定義單獨的線程類來表明新的線程,較爲繁瑣,不夠直接
NSOperation
咱們先直接經過代碼來領會一個任務隊列。
invocation pperation(經過selector指定任務)
-(void)loadImageWithMultiThread{ /*建立一個調用操做 object:調用方法參數 */ NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil]; //進程經過start方法後才能啓動操做,可是注意若是直接調用start方法,則此操做會在主線程中調用,通常不會這麼操做,而是添加到NSOperationQueue中 // [invocationOperation start]; //建立操做隊列 NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init]; //注意添加到操做隊後,隊列會開啓一個線程執行此操做 [operationQueue addOperation:invocationOperation]; }
block operation(經過block指定任務)
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init]; operationQueue.maxConcurrentOperationCount=5;//設置最大併發線程數 [operationQueue addOperationWithBlock:^{ [self loadImage:[NSNumber numberWithInt:i]]; }]; //更新UI界面,此處調用了主線程隊列的方法(mainQueue是UI主線程) [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [self updateImageWithData:data andIndex:i]; }];
概念上,一個任務隊列operation queue至關於一個由不少線程和任務組成的池。在一些應用場景之下,好比當須要下載不少張圖片,能夠把下載任務和相應的線程統一交給operation queue來管理,方便了操做。
一個NSOperation的實例定義了一個任務,能夠經過調用start方法在當前線程啓動它對應的任務,不過通常不這麼作,而是把operation加入Operation queue中統一管理。對於invocation operation和block operation,兩種方式本質沒有區別,可是是後者使用Block形式進行代碼組織,傳參容易,使用相對方便。
優勢:統一管理大量的任務和線程,適用於下載大量圖片等場景。
多線程的應用
一、操做數據模型
多個任務同時操做數據模型,線程安全須要仔細思考,對於數據模型對象的操做須要用@synchronized。
二、網絡請求
網絡請求是多線程的重要應用,下一篇博客會講。