簡介html
在軟件開發中,多線程編程技術被普遍應用,相信多線程任務對咱們來講已經再也不陌生了。有了多線程技術,咱們能夠同作多個事情,而不是一個一個任務地進行。好比:前端和後臺做交互、大任務(須要耗費必定的時間和資源)等等。也就是說,咱們可使用線程把佔據時間長的任務放到後臺中處理,而不影響到用戶的使用。前端
線程間通信編程
有一個很是重要的隊列,就是主隊列。在這個隊列中處理多點觸控及全部與UI相關操做等等。它很是特殊,緣由有兩點。一是咱們絕對不想它阻塞,咱們不會將須要執行很長時間的任務放在主隊列上執行。二是咱們將其用於全部與UI相關的同步,也就是線程間通信須要注意的地方。全部有可能會使屏幕UI發生變化的,都應放在主隊列上執行。api
線程的定義:數組
每一個正在系統上運行的程序都是一個進程。每一個進程包含一到多個線程。進程也多是整個程序或者是部分程序的動態執行。線程是一組指令的集合,或者是程序的特殊段,它能夠在程序裏獨立執行。也能夠把它理解爲代碼運行的上下文。因此線程基本上是輕量級的進程,它負責在單個程序裏執行多任務。一般由操做系統負責多個線程的調度和執行。
轉自百度百科:多線程緩存
若是熟悉多線程編程技術這一塊的朋友們,能夠去看關於多線程安全的文章,是我寫的另外一篇文章」iOS開發-多線程開發之線程安全篇「;安全
IOS支持的多線程技術:多線程
1、Thread:併發
2、Cocoa operations:
NSOperation類是一個抽象類,由於咱們必須使用它的兩個子類。
————————————————————————————
3)NSOperationQueue(繼承於NSObject)
3、Grand Central Dispatch (GCD):
2)重複執行線程及一次性執行:dispatch_apply & dispatch_once
3)操做(串行)隊列:dispatch_queue_create
1、Thread
咱們可使用NSTherad或NSObject類去調用:
建立NSThread有兩個辦法
1.1)建立以後須要使用start方法,纔會執行方法:
NSThread *threadAlloc = [[NSThread alloc] initWithTarget:self selector:@selector(threadAlloc) object:nil]; [threadAlloc start];
1.2)建立並立刻執行方法:
[NSThread detachNewThreadSelector:@selector(threadAlloc:) toTarget:self withObject:nil];
咱們也可使用NSObject類的方法直接調用方法
[self performSelectorInBackground:@selector(threadAlloc) withObject:nil];
取消線程的方法:
實際上並無真正提供取消線程的API。蘋果提供了一個cancel的api,但它不能做用於取消線程,它只能改變線程的運行狀態。咱們可使用它來進行條件判斷。
- (void)threadCancel { NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadCancelNow) object:nil]; [thread start]; } - (void)threadCancelNow { int a = 0; while (![[NSThread currentThread] isCancelled]) { NSLog(@"a - %d", a); a++; if (a == 5000) { NSLog(@"終止循環"); [[NSThread currentThread] cancel]; break; } } }
程序效果:循環輸出5000次,線程就會被終止。
NSThread線程間通信-調用主線程修改UI:
只須要傳遞一個selector和它的參數,withObject參數能夠爲nil,waitUntilDone表明是否要等待調用它的這個線程執行以後再將它從主隊列調出,並在主隊列上運行,一般設爲NO,不須要等待。
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
NSThread相關屬性及方法:
// 獲取/設置線程的名字 @property (copy) NSString *name NS_AVAILABLE(10_5, 2_0); /** * 獲取當前線程的線程對象 * * 經過這個屬性能夠查看當前線程是第幾條線程,主線程爲1。 * 能夠看到當前線程的序號及名字,主線程的序號爲1,依次疊加。 */ + (NSThread *)currentThread; // 線程休眠(秒) + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 線程休眠,指定具體什麼時間休眠 + (void)sleepUntilDate:(NSDate *)date; // 退出線程 // 注意:這裏會把線程對象銷燬!銷燬後就不能再次啓動線程,不然程序會崩潰。 + (void)exit;
2、Cocoa operations
建立NSInvocationOperation線程,附帶一個NSString參數:
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationAction:) object:@"abc"]; // 須要啓動線程,默認是不啓動的。 [operation start];
如在建立時定義了參數,那麼接收的時候,能夠對sender進行轉換,如字符串、數組等:
- (void)invocationAction:(NSInvocationOperation *)sender { NSLog(@"sender - %@", sender); // 輸出params NSString *str = (NSString *)sender; NSLog(@"str - %@e", str); // params }
附帶一提,線程的普通建立通常爲併發執行的,由於串行隊列是須要顯式建立的,如沒看見此類代碼,那麼便是併發隊列線程,所以,上述代碼也就是併發線程。關於併發和串行隊列(線程),我將會在下面詳細說明,咱們繼續往下看。
你也可使用NSOperationQueue來建立一個線程隊列,用來添加子線程:
NSOperationQueue *invocationQueue = [[NSOperationQueue alloc] init]; // 線程A NSInvocationOperation *invocationQ1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationAction:) object:@"invocationQ1"]; // 線程B NSInvocationOperation *invocationQ2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationAction:) object:@"invocationQ2"]; // 往invocationQueue添加子線程 [invocationQueue addOperations:@[invocationQ1, invocationQ2] waitUntilFinished:YES];
必須使用addOperations:方法把線程添加至隊列,否則線程不會執行,隊列是並行執行。或者,你也可使用addOperation:方法添加單個線程。
建立NSBlockOperation
// 建立線程任務 NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ [NSThread sleepForTimeInterval:2]; NSLog(@"one - %@", [NSThread currentThread]); }];;// 執行線程任務 [blockOperation start];
注意:這會在當前的線程中執行,由於它是根據調用的線程所決定的。
比方說你在主線程中運行它,那麼它就是在主線程中執行任務。若是你是在子線程中運行它,那麼它就是在子線程中執行任務。
作個簡單的實驗,咱們新建一條子線程,而後在子線程裏調用NSBlockOperation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ [NSThread sleepForTimeInterval:2]; NSLog(@"one - %@", [NSThread currentThread]); // print: one - <NSThread: 0x7f8ac2e1d0b0>{number = 2, name = (null)} }];; [blockOperation start]; });
它將打印:one - <NSThread: 0x7f8ac2e1d0b0>{number = 2, name = (null)},所以這個理論是正確的
咱們也可使它併發執行,經過使用addExecutionBlock方法添加多個Block,這樣就能使它在主線程和其它子線程中工做。
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"one - %@", [NSThread currentThread]); }];; [blockOperation addExecutionBlock:^{ NSLog(@"two - %@", [NSThread currentThread]); }]; [blockOperation addExecutionBlock:^{ NSLog(@"three - %@", [NSThread currentThread]); }]; [blockOperation addExecutionBlock:^{ NSLog(@"four - %@", [NSThread currentThread]); }]; [blockOperation start];
它將打印:
two - <NSThread: 0x7fea8a70b000>{number = 3, name = (null)} one - <NSThread: 0x7fea8a558a40>{number = 4, name = (null)} four - <NSThread: 0x7fea8a406b90>{number = 1, name = main} three - <NSThread: 0x7fea8a436e40>{number = 2, name = (null)}
你們都看到,即便咱們經過使用addExecutionBlock方法使它併發執行任務,可是它也依舊會在主線程執行,所以咱們就須要使用NSOperationQueue了。
這裏介紹一下NSOperation的依賴關係,依賴關係會影響線程的執行順序:
// 建立操做隊列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 線程A NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op1"); [NSThread sleepForTimeInterval:2]; }]; // 線程B NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op2"); }]; // 線程C NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op3"); [NSThread sleepForTimeInterval:2]; }]; // 線程B依賴線程C,也就是等線程C執行完以後纔會執行線程B [op2 addDependency:op3]; // 線程C依賴線程A,同上,只不過依賴對象改爲了線程A [op3 addDependency:op1]; // 爲隊列添加線程 [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3];
當你沒添加依賴時,隊列是並行執行的。
注意:依賴關係能夠多重依賴,但不要創建循環依賴。
Cocoa operations線程間通訊-調用主線程修改UI:
// 建立線程對象(併發) NSBlockOperation *blockOperation = [[NSBlockOperation alloc] init]; // 添加新的操做 [blockOperation addExecutionBlock:^{ NSLog(@"two - %@", [NSThread currentThread]); }]; // 添加新的操做 [blockOperation addExecutionBlock:^{ NSLog(@"three - %@", [NSThread currentThread]); // 在主線程修改UI NSOperationQueue *queue = [NSOperationQueue mainQueue]; [queue addOperationWithBlock:^{ [self editUINow]; }]; }]; [blockOperation start];
NSOperation方法及屬性:
// 設置線程的最大併發數 @property NSInteger maxConcurrentOperationCount; // 線程完成後調用的Block @property (copy) void (^completionBlock)(void); // 取消線程 - (void)cancel;
只列舉上面那些,其它的方法就不全列出來了。
注意:在NSOperationQueue類中,咱們可使用cancelAllOperations方法取消全部的線程。這裏須要說明一下,不是執行cancelAllOperations方法時就會立刻取消,是等當前隊列執行完,下面的隊列不會再執行。
3、Grand Central Dispatch (GCD)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"線程 - %@", [NSThread currentThread]); });
GCD也能夠建立同步的線程,只須要把async改爲sync便可。
如下代碼會執行4次:
dispatch_apply(4, DISPATCH_QUEUE_PRIORITY_DEFAULT, ^(size_t index) { // index則爲執行的次數 0開始遞增 NSLog(@"one - %ld", index); });
index參數爲執行的次數,從0開始遞增。
其中須要注意的是,每次執行都會新開闢一條子線程,由於是異步的緣由,它們不會是順序的。
[657:159159] one - 0, thread - <NSThread: 0x100110b50>{number = 1, name = main} [657:159191] one - 2, thread - <NSThread: 0x103800000>{number = 2, name = (null)} [657:159192] one - 3, thread - <NSThread: 0x100112b90>{number = 3, name = (null)} [657:159190] one - 1, thread - <NSThread: 0x100501180>{number = 4, name = (null)}
然而,GCD還有一次性執行的方法:
dispatch_once_t once; dispatch_once(&once, ^{ NSLog(@"once - %@", [NSThread currentThread]); // 主線程 });
它一般用於建立單例。
使用GCD也能建立串行隊列,具體代碼以下:
/** * GCD建立串行隊列 * * @param "com.GarveyCalvin.queue" 隊列字符串標識 * @param DISPATCH_QUEUE_CONCURRENT 可選的,能夠是NULL * * @return dispatch_queue_t */ dispatch_queue_t queue = dispatch_queue_create("com.GarveyCalvin.queue", DISPATCH_QUEUE_CONCURRENT); // 線程A dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"sleep async - %@", [NSThread currentThread]); }); // 線程B dispatch_barrier_async(queue, ^{ [NSThread sleepForTimeInterval:3]; NSLog(@"sleep barrier2 - %@", [NSThread currentThread]); }); // 線程C dispatch_async(queue, ^{ NSLog(@"async"); });
運行效果:以上會先執行 線程A-》線程B-》線程C,它是一個串行隊列。
dispatch_queue_create的第二個參數:
1)DISPATCH_QUEUE_SERIAL(串行)
2)DISPATCH_QUEUE_CONCURRENT(併發)
GCD的高級用法,等全部線程都完成工做後,再做通知。
// 建立羣組 dispatch_group_t group = dispatch_group_create(); // 線程A dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"group1"); [NSThread sleepForTimeInterval:2]; }); // 線程B dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"group2"); }); // 待羣組裏的線程都完成以後調用的通知 dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"group success"); });
羣組裏的線程也是並行隊列。線程A和線程B都執行完以後,會調用通知打印group success。
__block int time = 30; CGFloat reSecond = 1.0; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, reSecond * NSEC_PER_SEC, 0); dispatch_source_set_event_handler(timer, ^{ time--; NSLog(@"%d", time); if (time == 0) { dispatch_source_cancel(timer); } }); dispatch_resume(timer);
代碼效果:建立了一個計時器,計時器運行30秒,每過一秒會調用一次block,咱們能夠在block裏面寫代碼。由於dispatch_source_t默認是掛起狀態,所以咱們使用時須要使用dispatch_resume方法先恢復,否則線程不會執行。
GCD線程間通訊-調用主線程修改UI:
有時候咱們請求後臺做數據處理,數據處理是異步的,數據處理完成後須要更新UI,這時候咱們須要切換到主線程修改UI,例子以下:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"異步數據處理 - %@", [NSThread currentThread]); [NSThread sleepForTimeInterval:2]; NSLog(@"數據處理完成"); // 調用主線程更新UI dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"更新UI - %@", [NSThread currentThread]); [self editUINow]; }); });
由於是在主線程修改UI,因此咱們最好是使用同步的GCD方法dispatch_sync。但這還不夠,咱們還須要使用dispatch_get_main_queue()方法來得到主線程,以後就是做UI的更新工做了。
GCD方法及屬性:
// 獲取主線程 dispatch_get_main_queue() // 建立隊列:第一個參數是隊列的名稱,它會出如今調試程序等之中,是個內部名稱。第二個參數表明它是串行隊列仍是並併發隊列,NULL表明串行隊列。 dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr); // 建立異步調度隊列 void dispatch_async(dispatch_queue_t queue, dispatch_block_t block); // 恢復隊列 void dispatch_resume(dispatch_object_t object); // 暫停隊列 void dispatch_suspend(dispatch_object_t object);
小結:本文主要介紹了IOS三種線程對比及其使用方法。須要特別注意的是,在修改任何有關於UI的東西,咱們必需要切換至主線程,在主線程裏修改UI,避免沒必要要的麻煩產生。蘋果是推薦咱們使用GCD,由於GCD是這三種裏面抽象級最高的,使用起來也簡單,也是消耗資源最低的,而且它執行效率比其它兩種都高。所以,可以使用GCD的地方,儘可能使用GCD。
使用block的另外一個好處是可讓程序在後臺較久地運行。在之前,當應用被按Home鍵退出後,應用僅有最多5秒的時間作一些保存或清理資源的工做。 可是若是使用GCD,你可讓你的應用最多有10分鐘的時間在後臺長久運行。這個時間能夠用來作各類事情,包括清理本地緩存、發送統計數據等工做。
AppDelegate.h @interface AppDelegate () @property (assign, nonatomic) UIBackgroundTaskIdentifier backGroundUpdate; @end AppDelegate.m - (void)applicationDidEnterBackground:(UIApplication *)application { [self beginBackGroundUpdate]; // 須要長久運行的代碼 [self endBackGroundUpdate]; } - (void)beginBackGroundUpdate { self.backGroundUpdate = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self endBackGroundUpdate]; }]; } - (void)endBackGroundUpdate { [[UIApplication sharedApplication] endBackgroundTask:self.backGroundUpdate]; self.backGroundUpdate = UIBackgroundTaskInvalid; }
建議你們在真機上測試,由於筆者在模擬器測試了24分鐘還有效。
若是咱們想要某段代碼延遲執行,那麼可使用dispatch_after ,可是有一個缺點是,當提交代碼後(代碼執行後),咱們不能取消它,它將會運行。另外,咱們可使用 NSTimer 進行延時操做,值得一提,它是能夠被取消的。
dispatch_time_t time_t = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_after(time_t, queue, ^{ NSLog(@"hahalo"); });
1、Thread:
優勢:量級較輕。
缺點:須要本身管理線程的生命週期,線程同步。線程同步對數據的加鎖會有必定的系統開銷。
2、Cocoa operations:
優勢:不須要關心線程管理,數據同步的事情,能夠把精力放在本身須要執行的操做上。
3、Grand Central Dispatch (GCD):
優勢:GCD基於C的API,很是底層,能夠充分利用多核,可以輕鬆在多核系統上高效運行併發代碼,也是蘋果推薦使用的多線程技術。
本文參考:
全面掌握iOS多線程攻略 —— PS:這個攻略較多,可是有不少重複的內容。
博文做者:GarveyCalvin
博文出處:http://www.cnblogs.com/GarveyCalvin/
本文版權歸做者和博客園共有,歡迎轉載,但須保留此段聲明,並給出原文連接,謝謝合做!