前言
在多線程簡介中,我已經說明過了,為了提高界面的流暢度以及用戶體驗。我們務必要把耗時的操作放到別的線程中去執行,千萬不要阻塞主線程。
iOS中有以下3種多線程編程方法:
NSThread Grand Centeral Dispatch(GCD) NSOperation和NSOperationQueue
1.NSThread
這是最輕量級的多線程的方法,使用起來最直觀的多線程編程方法。但是因為需要自己管理線程的生命周期,線程同步。經常使用NSThread進行調試,在實際項目中不推薦使用。
//獲取當前線程 NSThread *current = [NSThread currentThread]; //獲取主線程 NSThread *main = [NSThread mainThread]; NSLog(@"當前線程 --- %@",current); NSLog(@"主線程 --- %@",main);
控制台輸出結果:
2015-11-22 22:30:29.572 多線程demo[1289:2925847] 當前線程 --- <NSThread: 0x7fc0e1401dc0>{number = 1, name = main}
2015-11-22 22:30:29.572 多線程demo[1289:2925847] 主線程 --- <NSThread: 0x7fc0e1401dc0>{number = 1, name = main}
從結果我們看出當前的線程就是主線程,number相當於線程的id,name是線程的名稱,主線程的number就是1
阻塞線程:
//阻塞線程3秒
[NSThread sleepForTimeInterval:3];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
2.GCD(Grand Central Dispatch)
GCD是基於C語言底層API實現的一套多線程並發機制,非常的靈活方便,在實際的開發中使用很廣泛。
簡單來說CGD就是把操作放在隊列中去執行。
你只需定義好操作和隊列就可以了,不需要直接控制線程的創建和銷毀,線程的生命周期由隊列來管理。
隊列:負責操作的調度和執行,有先進先出(FIFO)的特點。也就是說先加入隊列的操作先執行,後加入的後執行。
隊列有兩種:
串行隊列:
隊列中的操作只會按順序執行,你可以想象成單窗口排隊。
並行隊列:
隊列中的操作可能會並發執行,這取決與操作的類型,你可以想象成多窗口排隊。
代碼如下:
//創建串行隊列
dispatch_queue_t q = dispatch_queue_create("my_serial_queue", DISPATCH_QUEUE_SERIAL);
//創建並行隊列
dispatch_queue_t q = dispatch_queue_create("my_concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
my_serial_queue和my_concurrent_queue是隊列的名字標簽,為了與其他的隊列區分,在一個項目裡面必須是唯一的。
DISPATCH_QUEUE_SERIAL表示串行隊列
DISPATCH_QUEUE_CONCURRENT表示並行隊列
操作同樣也分兩種類型:
同步操作:只會按順序執行,執行順序是確定的。
異步操作:在串行隊列中執行順序確定,在並行隊列中執行順序不確定
使用block來定義操作要執行的代碼,q是已經定義好的,操作要加入的隊列
//定義同步操作 dispatch_sync(q, ^{ //要執行的代碼 }); //定義異步操作 dispatch_async(q, ^{ //要執行的代碼 });
下面我們看一下同步,異步操作加入到串行和並行隊列裡面,執行的順序和特點:
1.同步操作不管加入到何種隊列,只會在主線程按順序執行
dispatch_queue_t q_serial = dispatch_queue_create("my_serial_queue", DISPATCH_QUEUE_SERIAL); dispatch_queue_t q_concurrent = dispatch_queue_create("my_concurrent_queue", DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i < 5; ++i) { dispatch_sync(q_serial, ^{ NSLog(@"串行隊列裡的同步任務 %@ %d", [NSThread currentThread], i); }); } for (int i = 0; i < 5; ++i) { dispatch_sync(q_concurrent, ^{ NSLog(@"並行隊列裡的同步任務 %@ %d", [NSThread currentThread], i); }); }
下面是控制台輸出結果:
2015-11-23 00:40:36.862 01.GCD演練[1952:3613752] 串行隊列裡的同步任務 <NSThread: 0x7ff833505450>{number = 1, name = main} 0
2015-11-23 00:40:36.863 01.GCD演練[1952:3613752] 串行隊列裡的同步任務 <NSThread: 0x7ff833505450>{number = 1, name = main} 1
2015-11-23 00:40:36.863 01.GCD演練[1952:3613752] 串行隊列裡的同步任務 <NSThread: 0x7ff833505450>{number = 1, name = main} 2
2015-11-23 00:40:36.863 01.GCD演練[1952:3613752] 串行隊列裡的同步任務 <NSThread: 0x7ff833505450>{number = 1, name = main} 3
2015-11-23 00:40:36.863 01.GCD演練[1952:3613752] 串行隊列裡的同步任務 <NSThread: 0x7ff833505450>{number = 1, name = main} 4
2015-11-23 00:40:36.863 01.GCD演練[1952:3613752] 並行隊列裡的同步任務 <NSThread: 0x7ff833505450>{number = 1, name = main} 0
2015-11-23 00:40:36.863 01.GCD演練[1952:3613752] 並行隊列裡的同步任務 <NSThread: 0x7ff833505450>{number = 1, name = main} 1
2015-11-23 00:40:36.863 01.GCD演練[1952:3613752] 並行隊列裡的同步任務 <NSThread: 0x7ff833505450>{number = 1, name = main} 2
2015-11-23 00:40:36.864 01.GCD演練[1952:3613752] 並行隊列裡的同步任務 <NSThread: 0x7ff833505450>{number = 1, name = main} 3
2015-11-23 00:40:36.864 01.GCD演練[1952:3613752] 並行隊列裡的同步任務 <NSThread: 0x7ff833505450>{number = 1, name = main} 4
2.異步操作只在非主線程的線程執行,在串行隊列中異步操作會在新建的線程中按順序執行。
代碼如下:
dispatch_queue_t q_serial = dispatch_queue_create("my_serial_queue", DISPATCH_QUEUE_SERIAL);
for(int i = 0; i < 5; ++i){
dispatch_async(q_serial, ^{
NSLog(@"串行隊列 -- 異步任務 %@ %d", [NSThread currentThread], i);
});
}
因為是異步操作,所以會新建一個線程。又因為加入到串行隊列中,所以所有的操作只會按順序執行。
2015-11-23 01:03:22.372 01.GCD演練[2081:3627139] 串行隊列 -- 異步任務 <NSThread: 0x7fb593d42f50>{number = 2, name = (null)} 0
2015-11-23 01:03:23.373 01.GCD演練[2081:3627139] 串行隊列 -- 異步任務 <NSThread: 0x7fb593d42f50>{number = 2, name = (null)} 1
2015-11-23 01:03:24.374 01.GCD演練[2081:3627139] 串行隊列 -- 異步任務 <NSThread: 0x7fb593d42f50>{number = 2, name = (null)} 2
2015-11-23 01:03:25.375 01.GCD演練[2081:3627139] 串行隊列 -- 異步任務 <NSThread: 0x7fb593d42f50>{number = 2, name = (null)} 3
2015-11-23 01:03:26.376 01.GCD演練[2081:3627139] 串行隊列 -- 異步任務 <NSThread: 0x7fb593d42f50>{number = 2, name = (null)} 4
3.異步操作,並行隊列
代碼如下:
dispatch_queue_t q_concurrent = dispatch_queue_create("my_concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 5; ++i){
dispatch_async(q_concurrent, ^{
NSLog(@"並行隊列 -- 異步任務 %@ %d", [NSThread currentThread], i);
});
}
理論上並行隊列會給每一個異步操作新建線程,然後讓所有的任務並發執行。但是實際上系統能創建的線程數量是有限的,當創建的線程達到最大線程數以後,後面的異步操作就需要等待前面的操作執行完畢才能得到執行。哪個線程操作執行完畢,就把等待的異步任務安排到哪個線程。直到所有的操作執行完畢。
你可以把上述代碼的循環次數改成5000就可以觀察到此現象。
2015-11-23 01:14:15.282 01.GCD演練[2165:3634728] 並行隊列 -- 異步任務 <NSThread: 0x7fb3b841b0a0>{number = 4, name = (null)} 3
2015-11-23 01:14:15.282 01.GCD演練[2165:3634724] 並行隊列 -- 異步任務 <NSThread: 0x7fb3b8514da0>{number = 3, name = (null)} 0
2015-11-23 01:14:15.282 01.GCD演練[2165:3634726] 並行隊列 -- 異步任務 <NSThread: 0x7fb3b8604db0>{number = 5, name = (null)} 2
2015-11-23 01:14:15.282 01.GCD演練[2165:3634725] 並行隊列 -- 異步任務 <NSThread: 0x7fb3b86119d0>{number = 2, name = (null)} 1
2015-11-23 01:14:15.285 01.GCD演練[2165:3634729] 並行隊列 -- 異步任務 <NSThread: 0x7fb3b87011f0>{number = 6, name = (null)} 4
3.NSOperation & NSOperationQueue
雖然GCD的功能已經很強大了,但是它使用的API依然是C語言的。在某些時候,在面向對象的objective-c中使用起來非常的不方便和不安全。
所以蘋果公司把GCD中的操作抽象成NSOperation對象,把隊列抽象成NSOperationQueue對象。
抽象為NSOperation & NSOperationQueue以後的好處有一下幾點:
代碼風格統一了,我們不用在面向對象的objective-C中寫面對過程的C語言代碼了。
我們知道在GCD中操作的執行代碼都是寫在匿名的block裡面,那麼我們很難做到給操作設置依賴關系以及取消操作。這些功能都已經封裝到NSOperation對象裡面了。^-^
NSOperationQueue對象比GCD中隊列更加的強大和靈活,比如:設置並發操作數量,取消隊列中所有操作。
NSOperation分為NSInvocationOperation和NSBlockOperation
NSInvocationOperation的使用
//首先定義一個NSOperationQueue對象 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationAction:) object:@"這裡可以穿參數"]; [queue addOperation:op];//把操作加入隊列中即開始執行 - (void)operationAction:(id)obj { NSLog(@"%@ - obj : %@", [NSThread currentThread], obj); }
輸出為:
2015-11-23 02:55:19.067 多線程demo[2604:3686934] <NSThread: 0x7f9dfa443510>{number = 2, name = (null)} - obj : 這裡可以穿參數
NSBlockOperation的使用
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
[self operationAction:@"這是NSBlockOperation"];
}];
[queue addOperation:op];
輸出為:
2015-11-23 02:56:11.812 多線程demo[2617:3687872] <NSThread: 0x7fa983f10a50>{number = 2, name = (null)} - obj : 這是NSBlockOperation
設置依賴關系(執行順序)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationAction:) object:@"op1"];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationAction:) object:@"op2"];
//op2在op1之後執行
[op2 addDependency:op1];//這裡需要注意,一定要在addOperation之前設置依賴關系
[queue addOperation:op1];
[queue addOperation:op2];
輸出為:
2015-11-23 02:57:40.283 多線程demo[2661:3689737] <NSThread: 0x7fb663e132d0>{number = 2, name = (null)} - obj : op1
2015-11-23 02:57:40.284 多線程demo[2661:3689737] <NSThread: 0x7fb663e132d0>{number = 2, name = (null)} - obj : op2
沒有設置依賴關系的輸出:
2015-11-23 03:00:45.939 多線程demo[2709:3692307] <NSThread: 0x7fe951d0d8a0>{number = 2, name = (null)} - obj : op2
2015-11-23 03:00:45.939 多線程demo[2709:3692308] <NSThread: 0x7fe951c24720>{number = 3, name = (null)} - obj : op1
到這裡你應該發現了,在NSOperation & NSOperationQueue中,我們不需要再像GCD那樣定義操作的類型和隊列的類型和控制操作的執行順序了,你只需要直接設定操作的執行順序就可以了。