本文導讀:NSOperation做爲蘋果推薦的重要併發技術之一,在開發當中也較爲經常使用。本文將詳細介紹NSOperation兩個子類以及NSOperationQueue的使用。而筆者前面的文章[iOS多線程基礎][1]已經詳細介紹了簡單的多線程NSThread和基於C語言的功能強大的GCD,有須要的同窗能夠去看一下。既然有三種多線程技術,那它們又有什麼區別呢?使用場景怎樣呢?筆者將在本文末尾爲你們一一解答bash
NSOperation是蘋果推薦使用的併發技術,它提供了一些用GCD不是很好實現的功能。NSOperation是基於GCD的面向對象的使用OC語言的封裝。相比GCD,NSOperation的使用更加簡單。NSOperation是一個抽象類,也就是說它並不能直接使用,而是應該使用它的子類。使用它的子類的方法有三種,使用蘋果爲咱們提供的兩個子類 NSInvocationOperation,NSBlockOperation和自定義繼承自NSOperation的子類。多線程
NSOperation的使用經常是配合NSOperationQueue來進行的。只要是使用NSOperation的子類建立的實例就能添加到NSOperationQueue操做隊列之中,一旦添加到隊列,操做就會自動異步執行(注意是異步)。若是沒有添加到隊列,而是使用start方法,則會在當前線程執行。併發
咱們知道,線程間的通訊主要是主線程與分線程之間進行的。主線程到分線程,NSOperation子類也有相應帶參數的方法;而分線程到主線程,好比更新UI,它也有很方便的獲取主隊列(被添加到主隊列的操做默認會在主線程執行)的方法:[NSOperationQueue mainQueue]。異步
##1、NSInvocationOperation ###1.單個NSInvocationOperation <1>直接建立一個NSInvocationOperation的對象,而後調用start方法會直接在主線程執行性能
//1.建立
NSOperation *op = [[NSInvocationOperation alloc]initWithTarget:self
selector:@selector(downloadImage:) object:@"Invocation"];
//2.start方法,直接在當前線程執行
[op start];
#pragma mark - 調用的耗時操做,後面調用的耗時操做都是這個
- (void)downloadImage:(id)obj{
NSLog(@"%@-----%@",[NSThread currentThread],obj);
}
複製代碼
輸出 [1151:50868] <NSThread: 0x7fae624047b0>{number = 1, name = main}-----Invocation
複製代碼
<2>添加到NSOperationQueue優化
//1.建立
NSOperation *op = [[NSInvocationOperation alloc]initWithTarget:self
selector:@selector(downloadImage:) object:@"Invocation"];
//2.放到隊列裏面去
NSOperationQueue *q = [[NSOperationQueue alloc]init];
//只要把操做放到隊列,會自動異步執行調度方法
[q addOperation:op];
複製代碼
輸出:[1192:55469] <NSThread: 0x7fbe59e45c30>{number = 3, name = (null)}-----Invocation
複製代碼
在number爲3,name爲空的子線程執行ui
2.多個NSInvocationOperationatom
//隊列,GCD裏面的併發隊列使用最多,因此NSOperation技術直接把GCD裏面的併發隊列封裝起來
//NSOperationQueue本質就是GCD裏面的併發隊列
//操做就是GCD裏面異步執行的任務
NSOperationQueue *q = [[NSOperationQueue alloc]init];
//把多個操做放到隊列裏面
for (int i = 0; i < 100; i++) {
NSOperation *op = [[NSInvocationOperation alloc]initWithTarget:self
selector:@selector(downloadImage:) object:[NSString stringWithFormat:@"Invocation%d",i]];
[q addOperation:op];
}
複製代碼
輸出:
**[1222:58476] <NSThread: 0x7fdc14b0cd20>{number = 7, name = (null)}-----Invocation5
**[1222:58478] <NSThread: 0x7fdc1357e5f0>{number = 9, name = (null)}-----Invocation7
**[1222:58307] <NSThread: 0x7fdc14a06ad0>{number = 3, name = (null)}-----Invocation1
**[1222:58477] <NSThread: 0x7fdc134916e0>{number = 8, name = (null)}-----Invocation6
**[1222:58481] <NSThread: 0x7fdc1357e120>{number = 12, name = (null)}-----Invocation10
**[1222:58475] <NSThread: 0x7fdc14801710>{number = 6, name = (null)}-----Invocation4
**[1222:58480] <NSThread: 0x7fdc13415630>{number = 11, name = (null)}-----Invocation9
**[1222:58306] <NSThread: 0x7fdc13512e20>{number = 4, name = (null)}-----Invocation3
··· ···
複製代碼
線程名與輸出均沒有規律,很明顯就是併發隊列。spa
##2、NSBlockOperation線程
NSBlockOperation的用法與NSInvocationOperation相同,只是建立的方式不一樣,它不須要去調用方法,而是直接使用代碼塊,顯得更方便。這也使得NSBlockOperation比NSInvocationOperation更加流行
//跟GCD中的併發隊列同樣
NSOperationQueue *q = [[NSOperationQueue alloc]init];
//跟GCD中的主隊列同樣
// NSOperationQueue *q = [NSOperationQueue mainQueue];
//把多個操做放到隊列裏面
for (int i = 0; i < 100; i++) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@------%d",[NSThread currentThread],i);
}];
//把Block操做放到隊列
[q addOperation:op];
}
NSLog(@"完成");
複製代碼
併發隊列輸出結果:
**[1378:72440] <NSThread: 0x7f9cb2603460>{number = 6, name = (null)}------5**
**[1378:72442] <NSThread: 0x7f9cb48106a0>{number = 5, name = (null)}------7**
**[1378:72441] <NSThread: 0x7f9cb242b3e0>{number = 7, name = (null)}------6**
**[1378:72325] <NSThread: 0x7f9cb4851550>{number = 9, name = (null)}------2**
**[1378:72320] <NSThread: 0x7f9cb492be70>{number = 4, name = (null)}------3**
**[1378:72313] <NSThread: 0x7f9cb24077b0>{number = 2, name = (null)}------1**
**[1378:72276] 完成
**[1378:72444] <NSThread: 0x7f9cb481cc40>{number = 11, name = (null)}------9**
**[1378:72326] <NSThread: 0x7f9cb4923fe0>{number = 3, name = (null)}------0**
**[1378:72440] <NSThread: 0x7f9cb2603460>{number = 6, name = (null)}------12**
... ...
複製代碼
主隊列輸出結果:
**[1417:76086] 完成
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------0**
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------1**
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------2**
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------3**
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------4**
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------5**
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------6**
**[1417:76086] <NSThread: 0x7fa452e04360>{number = 1, name = main}------7**
... ...
複製代碼
事實上NSBlockOperation有更簡單的使用方法
NSOperationQueue *q = [[NSOperationQueue alloc]init];
for (int i = 0; i < 10; i++) {
[q addOperationWithBlock:^{
NSLog(@"%@------%d",[NSThread currentThread],i);
}];
}
複製代碼
##3、線程間通訊
主線程到子線程傳對象,前面的例子裏面已經有了,再也不綴述。下面的例子就是回到主線程更新UI。
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; c
NSOperationQueue *q = [[NSOperationQueue alloc]init];
[q addOperationWithBlock:^{
NSLog(@"耗時操做--%@",[NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"更新UI-----%@",[NSThread currentThread]);
}];
}];
複製代碼
##4、NSOperationQueue的一些高級操做 NSOperationQueue支持的高級操做有:隊列的掛起,隊列的取消,添加操做的依賴關係和設置最大併發數
<1>最大併發數
@property (nonatomic,strong)NSOperationQueue *opQueue;
//重寫getter方法實現懶加載
- (NSOperationQueue*)opQueue{
if (_opQueue == nil) {
_opQueue = [[NSOperationQueue alloc]init];
}
return _opQueue;
}
#pragma mark - 高級操做:最大併發數
//設置最大的併發數量(並不是線程的數量)
self.opQueue.maxConcurrentOperationCount = 2;
//把多個操做放到隊列裏面
for (int i = 0; i < 10; i++) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:3.0];
NSLog(@"%@------%d",[NSThread currentThread],i);
}];
//把Block操做放到隊列
[self.opQueue addOperation:op];
}
複製代碼
<2>線程的掛起
#pragma mark - 高級操做:線程的掛起
//暫停繼續(對隊列的暫停和繼續),掛起的是隊列,不會影響已經在執行的操做
- (IBAction)pause:(UIButton *)sender {
//判斷操做的數量,當前隊列裏面是否是有操做?
if (self.opQueue.operationCount == 0) {
NSLog(@"當前隊列沒有操做");
return;
}
self.opQueue.suspended = !self.opQueue.isSuspended;
if (self.opQueue.suspended) {
NSLog(@"暫停");
}else{
NSLog(@"繼續");
}
}
複製代碼
<3>取消隊列裏的全部操做
#pragma mark - 高級操做:取消隊列裏的全部操做
- (IBAction)cancelAll:(UIButton *)sender {
//只能取消全部隊列的裏面的操做,正在執行的沒法取消
//取消操做並不會影響隊列的掛起狀態
[self.opQueue cancelAllOperations];
NSLog(@"取消隊列裏全部的操做");
//取消隊列的掛起狀態
//(只要是取消了隊列的操做,咱們就把隊列處於不掛起狀態,以便於後續的開始)
self.opQueue.suspended = NO;
}
複製代碼
<4>依賴關係
/*
* 例子
*
* 1.下載一個小說壓縮包
* 2.解壓縮,刪除壓縮包
* 3.更新UI
*/
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1.下載一個小說壓縮包,%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2.解壓縮,刪除壓縮包,%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3.更新UI,%@",[NSThread currentThread]);
}];
//指定任務之間的依賴關係 --依賴關係能夠跨隊列(能夠再子線程下載,在主線程更新UI)
[op2 addDependency:op1];
[op3 addDependency:op2];
// [op1 addDependency:op3]; 必定不能出現循環依賴
//waitUntilFinished 相似GCD中的調度組的通知
//NO不等待,直接執行輸出come here
//YES等待任務執行完再執行輸出come here
[self.opQueue addOperations:@[op1,op2] waitUntilFinished:YES];
//在主線程更新UI
[[NSOperationQueue mainQueue] addOperation:op3];
[op3 addDependency:op2];
NSLog(@"come here");
複製代碼
還有一個NSOperationQueuePriority,隊列優先級的概念,由於用的極少,因此這裏不作介紹,確實有須要的同窗能夠本身百度或者查看Documentation and API Reference。
##5、三種多線程技術 <1>NSThread
<2>GCD GCD 是iOS 4.0之後纔出現的併發技術
- 使用方式:將任務添加到隊列(串行/並行(全局)),指定執行任務的方法,(同步(阻塞)/異步 )
<3>NSOperation NSOperation iOS2.0的時候就出現了(當時很差用,後來蘋果對其進行改造)
- 使用方式:將操做(異步執行)添加到隊列(併發/全局)
GCD是比較底層的封裝,咱們知道較低層的代碼通常性能都是比較高的,相對於NSOperationQueue。因此追求性能,而功可以用的話就能夠考慮使用GCD。若是異步操做的過程須要更多的用戶交互和被UI顯示出來,NSOperationQueue會是一個好選擇。若是任務之間沒有什麼依賴關係,而是須要更高的併發能力,GCD則更有優點。 高德納的教誨:「在大概97%的時間裏,咱們應該忘記微小的性能提高。過早優化是萬惡之源。」只有Instruments顯示有真正的性能提高時纔有必要用低級的GCD。 [1]:http://www.jianshu.com/p/7267206834fb