天天看點

iOS多線程:NSOperation為什麼要使用 NSOperation、NSOperationQueue?NSOperation單獨使用的時候NSOperationQueue

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

為什麼要使用 NSOperation、NSOperationQueue?

1、可添加完成的代碼塊,在操作完成後執行。

2、添加操作之間的依賴關系,友善的控制執行順序。

3、設定操作執行的優先級。

4、可以很友善的取消一個操作的執行。

5、使用 KVO 觀察對操作執行狀态的更改:isExecuteing、isFinished、isCancelled。

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

NSOperation單獨使用的時候

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

  1. 使用子類NSInvocationOperation
  2. 使用子類NSBlockOperation
  3. 定義繼承自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]);

}

2、使用子類NSBlockOperation   在主線程中

NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{

    // 在主線程

    NSLog(@"------%@", [NSThread currentThread]);

}];

[op start];

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];

}

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];

NSOperationQueue

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

1、建立任務:先将需要執行的操作封裝到一個NSOperation對象中。

2、建立隊列:建立NSOperationQueue對象。

3、将任務加入到隊列中:然後将NSOperation對象添加到NSOperationQueue中。

系統就會自動将NSOperationQueue中的NSOperation取出來,在新線程中執行操作。

1、建立隊列

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

  • 主隊列

    凡是添加到主隊列中的任務(NSOperation),都會放到主線程中執行

NSOperationQueue *queue = [NSOperationQueue mainQueue];

  • 其他隊列(非主隊列)

    添加到這種隊列中的任務(NSOperation),就會自動放到子線程中執行

    同時包含了:串行、并發功能

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

2、将任務加入到隊列中

前邊說了,NSOperation需要配合NSOperationQueue來實作多線程。

那麼我們需要将建立好的任務加入到隊列中去。總共有兩種方法

1、NSInvocationOperation和NSOperationQueue結合後能夠開啟新線程,進行并發執行。NSBlockOperation和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]);

    }

}

2、addOperationWithBlock:和NSOperationQueue能夠開啟新線程,進行并發執行。

addOperationWithBlock無需先建立任務,在block中添加任務,直接将任務block加入到隊列中。

- (void)addOperationWithBlockToQueue{

    // 1. 建立隊列

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2. 添加操作到隊列中:addOperationWithBlock:

    [queue addOperationWithBlock:^{

        for (int i = 0; i < 2; ++i) {

            NSLog(@"-----%@", [NSThread currentThread]);

        }

    }];

}

3、NSOperationQueue控制串行執行和并行執行的關鍵

關鍵參數maxConcurrentOperationCount,叫做最大并發數。

  1. 為-1,表示不進行限制,預設為并發執行。
  2. 為1時,進行串行執行。
  3. 大于1時,進行并發執行,當然這個值不應超過系統限制,即使自己設定一個很大的值,系統也會自動調整。

4、操作依賴

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];

}

5、其他方法

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

    1.這裡的暫停和取消并不代表可以将目前的操作立即取消,而是當目前的操作執行完畢之後不再執行新的操作。

    2.暫停和取消的差別就在于:暫停操作之後還可以恢複操作,繼續向下執行;而取消操作之後,所有的操作就清空了,無法再接着執行剩下的操作。

6、NSOperation優先級

GCD中,任務(block)是沒有優先級的,而隊列具有優先級。和GCD相反,我們一般考慮 NSOperation 的優先級。

NSOperation 有一個NSOperationQueuePriority 枚舉類型的屬性 queuePriority。

public enum NSOperationQueuePriority : Int {  

    case VeryLow

    case Low

    case Normal

    case High

    case VeryHigh

}

需要注意的是,NSOperationQueue 也不能完全保證優先級高的任務一定先執行。

queuePriority預設值是NSOperationQueuePriorityNormal。根據實際需要我們可以通過調用queuePriority的setter方法修改某個操作的優先級。

NSBlockOperation *blkop1 = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"執行blkop1");

    }];

    NSBlockOperation *blkop2 = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"執行blkop2");

    }];

    // 設定操作優先級

    blkop1.queuePriority = NSOperationQueuePriorityLow;

    blkop2.queuePriority = NSOperationQueuePriorityVeryHigh;

    NSLog(@"blkop1 == %@",blkop1);

    NSLog(@"blkop2 == %@",blkop2);

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 操作添加到隊列

    [queue addOperation:blkop1];

    [queue addOperation:blkop2];

    NSLog(@"%@",[queue operations]);

    for (NSOperation *op in [queue operations]) {

        NSLog(@"op == %@",op);

    }

解析:

  1. 上面建立了兩個blockOperation并且分别設定了優先級。顯然blkop1的優先級低于blkop2的優先級。然後調用了隊列的addOperation:方法使操作入隊。最後輸出結果證明,操作在對列中的順序取決于addOperation:方法而不是優先級。
  2. 雖然blkop2優先級高于blkop1,但是bloop1卻先于blkop2執行完成。是以,優先級高的操作不一定先執行完成。
  3. blkop2優先級高于blkop1,則代表blkop2先被執行,但是由于多線程的存在,blkop1可能會與blkop2并行執行。

注意:

(1)優先級隻能應用于相同queue中的operations。

(2)操作的優先級高低不等于操作在隊列中排列的順序。換句話說,優先級高的操作不代表一定排在隊列的前面。後入隊的操作有可能因為優先級高而先被執行。PS:操作在隊列中的順序取決于隊列的addOperation:方法。

(3)優先級高隻代表先被執行。不代表操作先被執行完成。執行完成的早晚還取決于操作耗時長短。

(4)優先級不能替代依賴,優先級也絕不等于依賴。優先級隻是對已經準備好的操作确定其執行順序。

(5)操作的執行優先滿足依賴關系,然後再滿足優先級。即先根據依賴執行操作,然後再從所有準備好的操作中取出優先級最高的那一個執行。

7、:waitUntilAllOperationsAreFinished

為了最佳的性能,你應該設計你的應用盡可能地異步操作,讓應用在Operation正在執行時可以去處理其它事情。如果需要在目前線程中處理operation完成後的結果,可以使用NSOperation的waitUntilFinished方法阻塞目前線程,等待operation完成。通常我們應該避免編寫這樣的代碼,阻塞目前線程可能是一種簡便的解決方案,但是它引入了更多的串行代碼,限制了整個應用的并發性,同時也降低了使用者體驗。絕對不要在應用主線程中等待一個Operation,隻能在第二或次要線程中等待。阻塞主線程将導緻應用無法響應使用者事件,應用也将表現為無響應。

// 會阻塞目前線程,等到某個operation執行完畢  

    [operation waitUntilFinished];

除了等待單個Operation完成,你也可以同時等待一個queue中的所有操作,使用NSOperationQueue的waitUntilAllOperationsAreFinished方法。注意:在等待一個 queue時,應用的其它線程仍然可以往queue中添加Operation,是以可能會加長線程的等待時間。

// 阻塞目前線程,等待queue的所有操作執行完畢  

    [queue waitUntilAllOperationsAreFinished];

注意:waitUntilAllOperationsAreFinished一定要在操作隊列添加了操作後再設定。即,先向operation queue中添加operation,再調用[operationQueue waitUntilAllOperationsAreFinished]。

NSBlockOperation *blkop = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"執行操作 %@",[NSThread currentThread]);

    }];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    [queue addOperation:blkop];

    // waitUntilAllOperationsAreFinished就像GCD的barrier一樣起到隔離作用

    // waitUntilAllOperationsAreFinished必須要在操作添加到隊列後設定

    // waitUntilAllOperationsAreFinished必須要在NSLog(@"finish");之前設定waitUntilAllOperationsAreFinished

    [queue waitUntilAllOperationsAreFinished];

8、有了GCD,為什麼還有要用NSOperationQueue

  • GCD是底層的C語言構成的API,而NSOperationQueue以及相關對象是基于GCD的Objective-C對象的封裝,作為一個對象,NSOperationQueue為我們提供了更多的選擇
  • NSOperationQueue任務可以很友善的取消(也隻能取消未執行的任務),而GCD沒法停止已經加入隊列的任務(其實是有的,但需要許多複雜的代碼)
  • 不像GCD那樣的是按FIFO順序來執行的,NSOperation能夠友善地通過依賴關系設定操作執行順序,可以控制任務在特定的任務執行完後才執行;而GCD要實作這個功能的話,就需要通過barrier或者group來控制執行順序,如果依賴關系複雜的話,代碼邏輯就非常複雜了
  • NSOperation支援KVO(Key-Value Observing),可以友善的監聽任務的狀态(完成、執行中、取消等等狀态)
  • NSOperation可以設定同一個隊列中任務的優先級,能夠使同一個并行隊列中的任務區分先後地執行,而在GCD中,我們隻能區分不同任務隊列的優先級,如果要區分block任務的優先級,也需要大量的複雜代碼
  • 還可以通過自定義NSOperation,封裝任務邏輯,提高整個代碼的複用度
  • 綜合比較其各自使用範圍如下
  1. GCD更接近底層,而NSOperationQueue則更進階抽象,是以GCD在追求性能的底層操作來說,是速度最快的。
  2. 從異步操作之間的事務性,順序行,依賴關系。GCD需要自己寫更多的代碼來實作,而NSOperationQueue已經内建了這些支援
  3. 如果異步操作的過程需要更多的被互動和UI呈現出來,NSOperationQueue會是一個更好的選擇。底層代碼中,任務之間不太互相依賴,而需要更高的并發能力,GCD則更有優勢

應用

1、AFNetWorking

2、SDWebImage