iOS-多線程-NSOperation

和GCD的對比

操作隊列,比GCD更早,GCD在設計上很多都是基于操作隊列的原理構建的。在iOS4之后,操作隊列的底層都是由GCD來實現的。
GCD是純c的語法函數,操作隊列是oc語法對象。
操作隊列能提供比GCD更精細的控制,比如:

  1. 取消某個操作(開始之前可以取消,已經開始的任務無法取消)。
  2. 設置操作之間的依賴關系、設置操作的優先級。(GCD只能對queue設置優先級)
  3. 狀態控制。對單個NSOperation 狀態屬性isReady、isExecuting、 isCancelled、isFinished通過kvo監控,執行完畢之后獲得通知。

一、NSOperation簡介

NSOperation是蘋果提供給我們的一套多線程解決方案。實際上NSOperation是基于GCD更高一層的封裝,但是比GCD更簡單易用、代碼可讀性也更高。

NSOperation需要配合NSOperationQueue來實現多線程。因為默認情況下,NSOperation單獨使用時系統同步執行操作,并沒有開辟新線程的能力,只有配合NSOperationQueue才能實現異步執行。

因為NSOperation是基于GCD的,那么使用起來也和GCD差不多,其中,NSOperation相當于GCD中的任務,而NSOperationQueue則相當于GCD中的隊列。NSOperation實現多線程的使用步驟分為三步:

  • 創建任務:先將需要執行的操作封裝到一個NSOperation對象中。
  • 創建隊列:創建NSOperationQueue對象。
  • 將任務加入到隊列中:然后將NSOperation對象添加到NSOperationQueue中。
    之后呢,系統就會自動將NSOperationQueue中的NSOperation取出來,在新線程中執行操作。

下面我們來學習下NSOperation和NSOperationQueue的基本使用。

二、NSOperation和NSOperationQueue的基本使用

1. 創建任務

NSOperation是個抽象類,并不能封裝任務。我們只有使用它的子類來封裝任務。我們有三種方式來封裝任務。

  • 使用子類NSInvocationOperation
  • 使用子類NSBlockOperation
  • 定義繼承自NSOperation的子類,通過實現內部相應的方法來封裝任務。
    在不使用NSOperationQueue,單獨使用NSOperation的情況下系統同步執行操作,下面我們學習以下任務的三種創建方式。
1. 使用子類- NSInvocationOperation:
// 1.創建NSInvocationOperation對象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

// 2.調用start方法開始執行操作
[op start];

- (void)run
{
    NSLog(@"------%@", [NSThread currentThread]);
}

輸出結果:
2016-09-05 14:29:58.483 NSOperation[15834:2384555] ------<NSThread: 0x7fa3e2e05410>{number = 1, name = main}
從中可以看到,在沒有使用NSOperationQueue、單獨使用NSInvocationOperation的情況下,NSInvocationOperation在主線程執行操作,并沒有開啟新線程。

2. 使用子類- NSBlockOperation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    // 在主線程
    NSLog(@"------%@", [NSThread currentThread]);
}];

[op start];

輸出結果:
2016-09-05 14:33:15.268 NSOperation[15884:2387780] ------<NSThread: 0x7fb2196012c0>{number = 1, name = main}
我們同樣可以看到,在沒有使用NSOperationQueue、單獨使用NSBlockOperation的情況下,NSBlockOperation也是在主線程執行操作,并沒有開啟新線程。

但是,NSBlockOperation還提供了一個方法addExecutionBlock:,通過addExecutionBlock:就可以為NSBlockOperation添加額外的操作,這些額外的操作就會在其他線程并發執行。

- (void)blockOperation
{
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        // 在主線程
        NSLog(@"1------%@", [NSThread currentThread]);
    }];    

    // 添加額外的任務(在子線程執行)
    [op addExecutionBlock:^{
        NSLog(@"2------%@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        NSLog(@"3------%@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        NSLog(@"4------%@", [NSThread currentThread]);
    }];

    [op start];
}

輸出結果:
2016-09-05 14:36:59.353 NSOperation[15896:2390616] 1------<NSThread: 0x7ff633f03be0>{number = 1, name = main}
2016-09-05 14:36:59.354 NSOperation[15896:2390825] 2------<NSThread: 0x7ff633e24600>{number = 2, name = (null)}
2016-09-05 14:36:59.354 NSOperation[15896:2390657] 3------<NSThread: 0x7ff633c411e0>{number = 3, name = (null)}
2016-09-05 14:36:59.354 NSOperation[15896:2390656] 4------<NSThread: 0x7ff633f1d3e0>{number = 4, name = (null)}

可以看出,blockOperationWithBlock:方法中的操作是在主線程中執行的,而addExecutionBlock:方法中的操作是在其他線程中執行的。

3. 定義繼承自NSOperation的子類

先定義一個繼承自NSOperation的子類,重寫main方法
YSCOperation.h

#import <Foundation/Foundation.h>

@interface YSCOperation : NSOperation

@end

YSCOperation.m

#import "YSCOperation.h"

@implementation YSCOperation
/**
 * 需要執行的任務
 */

- (void)main
{
    for (int i = 0; i < 2; ++i) {
        NSLog(@"1-----%@",[NSThread currentThread]);
    }    
}

@end

然后使用的時候導入頭文件YSCOperation.h。

// 創建YSCOperation
YSCOperation *op1 = [[YSCOperation alloc] init];

[op1 start];

輸出結果:
2016-09-05 18:15:59.674 NSOperation[16566:2501606] 1-----<NSThread: 0x7f8030d05150>{number = 1, name = main}
2016-09-05 18:15:59.675 NSOperation[16566:2501606] 1-----<NSThread: 0x7f8030d05150>{number = 1, name = main}

可以看出:在沒有使用NSOperationQueue、單獨使用自定義子類的情況下,是在主線程執行操作,并沒有開啟新線程。

2. 創建隊列

和GCD中的并發隊列、串行隊列略有不同的是:NSOperationQueue一共有兩種隊列:主隊列、其他隊列。其中其他隊列同時包含了串行、并發功能。下邊是主隊列、其他隊列的基本創建方法和特點。

  • 主隊列
    凡是添加到主隊列中的任務(NSOperation),都會放到主線程中執行
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
  • 其他隊列(非主隊列)
    添加到這種隊列中的任務(NSOperation),就會自動放到子線程中執行
    同時包含了:串行、并發功能
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

3. 將任務加入到隊列中

前邊說了,NSOperation需要配合NSOperationQueue來實現多線程。
那么我們需要將創建好的任務加入到隊列中去。總共有兩種方法

- (void)addOperation:(NSOperation *)op;
需要先創建任務,再將創建好的任務加入到創建好的隊列中去
- (void)addOperationToQueue
{
    // 1.創建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2. 創建操作  
    // 創建NSInvocationOperation    
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];    
    // 創建NSBlockOperation    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1-----%@", [NSThread currentThread]);
        }
    }];

    // 3. 添加操作到隊列中:addOperation:   
    [queue addOperation:op1]; // [op1 start]    
    [queue addOperation:op2]; // [op2 start]
}

- (void)run
{
    for (int i = 0; i < 2; ++i) {
        NSLog(@"2-----%@", [NSThread currentThread]);
    }
}

輸出結果:
2016-09-05 17:06:00.241 NSOperationQueue[16201:2452281] 1-----<NSThread: 0x7fe4824080e0>{number = 3, name = (null)}
2016-09-05 17:06:00.241 NSOperationQueue[16201:2452175] 2-----<NSThread: 0x7fe482404a50>{number = 2, name = (null)}
2016-09-05 17:06:00.242 NSOperationQueue[16201:2452175] 2-----<NSThread: 0x7fe482404a50>{number = 2, name = (null)}
2016-09-05 17:06:00.241 NSOperationQueue[16201:2452281] 1-----<NSThread: 0x7fe4824080e0>{number = 3, name = (null)}

可以看出:NSInvocationOperation和NSOperationQueue結合后能夠開啟新線程。

- (void)addOperationWithBlock:(void (^)(void))block;
無需先創建任務,在block中添加任務,直接將任務block加入到隊列中。
- (void)addOperationWithBlockToQueue
{
    // 1. 創建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2. 添加操作到隊列中:addOperationWithBlock:
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"-----%@", [NSThread currentThread]);
        }
    }];
}

輸出結果:
2016-09-05 17:10:47.023 NSOperationQueue[16293:2457487] -----<NSThread: 0x7ffa6bc0e1e0>{number = 2, name = (null)}
2016-09-05 17:10:47.024 NSOperationQueue[16293:2457487] -----<NSThread: 0x7ffa6bc0e1e0>{number = 2, name = (null)}

可以看出addOperationWithBlock:和NSOperationQueue能夠開啟新線程,進行并發執行。

三、 控制串行執行和并行執行的關鍵

之前我們說過,NSOperationQueue創建的其他隊列同時具有串行、并發功能,上邊我們演示了并發功能,那么他的串行功能是如何實現的?

這里有個關鍵參數maxConcurrentOperationCount,叫做最大并發數。

最大并發數:maxConcurrentOperationCount

  • maxConcurrentOperationCount默認情況下為-1,表示不進行限制,默認為并發執行。
  • 當maxConcurrentOperationCount為1時,進行串行執行。
  • 當maxConcurrentOperationCount大于1時,進行并發執行,當然這個值不應超過系統限制,即使自己設置一個很大的值,系統也會自動調整。
- (void)opetationQueue
{
    // 創建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 設置最大并發操作數
    //    queue.maxConcurrentOperationCount = 2;
    queue.maxConcurrentOperationCount = 1; // 就變成了串行隊列

    // 添加操作
    [queue addOperationWithBlock:^{
        NSLog(@"1-----%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.01];
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"2-----%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.01];
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"3-----%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.01];
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"4-----%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.01];
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"5-----%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.01];
    }];

    [queue addOperationWithBlock:^{
        NSLog(@"6-----%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.01];
    }];
}

最大并發數為1輸出結果:
2016-09-05 17:21:54.124 NSOperationQueue[16320:2464630] 1-----<NSThread: 0x7fc892d0b3a0>{number = 2, name = (null)}
2016-09-05 17:21:54.136 NSOperationQueue[16320:2464631] 2-----<NSThread: 0x7fc892c0a7b0>{number = 3, name = (null)}
2016-09-05 17:21:54.148 NSOperationQueue[16320:2464630] 3-----<NSThread: 0x7fc892d0b3a0>{number = 2, name = (null)}
2016-09-05 17:21:54.160 NSOperationQueue[16320:2464631] 4-----<NSThread: 0x7fc892c0a7b0>{number = 3, name = (null)}
2016-09-05 17:21:54.171 NSOperationQueue[16320:2464631] 5-----<NSThread: 0x7fc892c0a7b0>{number = 3, name = (null)}
2016-09-05 17:21:54.184 NSOperationQueue[16320:2464630] 6-----<NSThread: 0x7fc892d0b3a0>{number = 2, name = (null)}

最大并發數為2輸出結果:
2016-09-05 17:23:36.030 NSOperationQueue[16331:2466366] 2-----<NSThread: 0x7fd729f0f270>{number = 3, name = (null)}
2016-09-05 17:23:36.030 NSOperationQueue[16331:2466491] 1-----<NSThread: 0x7fd729f4e290>{number = 2, name = (null)}
2016-09-05 17:23:36.041 NSOperationQueue[16331:2466367] 3-----<NSThread: 0x7fd729d214e0>{number = 4, name = (null)}
2016-09-05 17:23:36.041 NSOperationQueue[16331:2466366] 4-----<NSThread: 0x7fd729f0f270>{number = 3, name = (null)}
2016-09-05 17:23:36.053 NSOperationQueue[16331:2466366] 6-----<NSThread: 0x7fd729f0f270>{number = 3, name = (null)}
2016-09-05 17:23:36.053 NSOperationQueue[16331:2466511] 5-----<NSThread: 0x7fd729e056c0>{number = 5, name = (null)}

可以看出:當最大并發數為1時,任務是按順序串行執行的。當最大并發數為2時,任務是并發執行的。而且開啟線程數量是由系統決定的,不需要我們來管理。這樣看來,是不是比GCD還要簡單了許多?

四、 操作依賴

NSOperation和NSOperationQueue最吸引人的地方是它能添加操作之間的依賴關系。比如說有A、B兩個操作,其中A執行完操作,B才能執行操作,那么就需要讓B依賴于A。具體如下:

- (void)addDependency
{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1-----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2-----%@", [NSThread  currentThread]);
    }];

    [op2 addDependency:op1];    // 讓op2 依賴于 op1,則先執行op1,在執行op2

    [queue addOperation:op1];
    [queue addOperation:op2];
}

輸出結果:
2016-09-05 17:51:28.811 操作依賴[16423:2484866] 1-----<NSThread: 0x7fc138e1e7c0>{number = 2, name = (null)}
2016-09-05 17:51:28.812 操作依賴[16423:2484866] 2-----<NSThread: 0x7fc138e1e7c0>{number = 2, name = (null)}

可以看到,無論運行幾次,其結果都是op1先執行,op2后執行。

五、 一些其他方法

  • -(void)cancel; NSOperation提供的方法,可取消單個操作
  • -(void)cancelAllOperations; NSOperationQueue提供的方法,可以取消隊列的所有操作
  • -(void)setSuspended:(BOOL)b; 可設置任務的暫停和恢復,YES代表暫停隊列,NO代表恢復隊列
  • -(BOOL)isSuspended; 判斷暫停狀態

注意:

這里的暫停和取消并不代表可以將當前的操作立即取消,而是當當前的操作執行完畢之后不再執行新的操作。
暫停和取消的區別就在于:暫停操作之后還可以恢復操作,繼續向下執行;而取消操作之后,所有的操作就清空了,無法再接著執行剩下的操作。

六、狀態控制

我們可以自行控制NSOperation的狀態:

  • 重寫start方法,我們需要自行控制狀態。

參考:

  1. 52個有效方法
  2. http://www.lxweimin.com/p/4b1d77054b35
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容