参考ブログ:NSOperation入門
【iOS】NSOperation、NSOperationQueue
NSOperation の簡単な紹介
NSOperation は、objective-c に基づく GCD のオブジェクト指向カプセル化とスレッド実行メソッドです。NSOperation は、NSOperationQueue を使用してマルチスレッド プログラミングも実現できます。
NSOperation と NSOperationQueue がマルチスレッドを実装するための具体的な手順は次のとおりです。
- まず、実行する必要がある操作を NSOperation オブジェクトにカプセル化します。
- 次に、NSOperation オブジェクトを NSOperationQueue に追加します。
- フェッチされた NSOperation のカプセル化された操作を新しいスレッドに入れて実行します。
NSOperation と NSOperationQueue の共通プロパティとメソッドの概要
NSOperation の共通プロパティとメソッド
- キャンセル操作キャンセル
- 稼働状況の確認方法
- isFinished は操作が終了したかどうかを判断します
- isCancelled は操作がキャンセルされるかどうかを決定します
- is Executing は操作が実行中かどうかを決定します
- isReady は準備完了状態かどうかを判断します
- isAsynchronous は、タスクが同時実行されるか同期的に実行されるかを示します。
- 動作の同期
- waitUntilFinished は現在のスレッドをブロックします
- setCompletionBlock:(void(^)(void))block; 現在の操作が完了した後にブロックを実行します
- addDependency依存関係を追加する
- RemoveDependency依存関係を削除する
- @property (読み取り専用、コピー) NSArray<NSOperation *> *依存関係; 配列ストレージ操作
NSOperationQueue の共通プロパティとメソッド
- キュー内の操作をキャンセル/一時停止/再開する
- cancelAllOperations;キュー内のすべての操作をキャンセルします
- isSuspended は、キューが一時停止状態を処理するかどうかを決定します。 YES: 一時停止状態、NO 再開状態
- setSuspended:(BOOL)b; 動作の一時停止と再開を設定 YES:一時停止、NO:再開
- 同期動作
- waitUntilAllOperationsAreFinished; キュー内のすべての操作が完了するまで現在のスレッドをブロックします
- 追加/取得
- addOperationWithBlock:(void (^)(void))block は、NSBlockOperation タイプの操作オブジェクトをキューに追加します
- addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; 操作配列を追加し、すべての操作が完了するまで待機フラグが現在のスレッドをブロックするかどうかを指定します。
- 操作 現在キューにある操作の配列
- OperationCount操作の数
- キューの取得
- currentQueue は現在のキューです。現在のスレッドがキューにない場合は、nil を返します。
- mainQueue はメインキューを取得します
一時停止とキャンセル
一時停止とキャンセル (操作のキャンセルとキューのキャンセルを含む) は、現在の操作をすぐにキャンセルできることを意味するのではなく、現在の操作が完了した後に新しい操作が実行されないことを意味します。
一時停止とキャンセルの違いは、操作を一時停止した後は操作を再開できますが、操作をキャンセルするとすべての操作がクリアされ、残りの操作は実行できなくなります。
NSOperationの使用方法
NSOperation は抽象クラスであり、操作をカプセル化する機能がないため、そのサブクラスを使用する必要があります。NSOperation のサブクラスを使用するには、Apple が提供する既存のサブクラス NSInvocationOperation および NSBlockOperation を使用する方法と、NSOperation を継承するカスタム サブクラスを使用して、対応する内部メソッドを実装する 2 つの方法があります。ここで、システムが提供するサブクラスの使用法について簡単に説明します。
NS呼び出し操作
NSOperationQueue と一緒に使用しない場合、現在のスレッドでシリアルに実行され、新しいスレッドは開始されません。
1. オペレーションを作成し、タスクをカプセル化する
- 最初のパラメータ: ターゲット オブジェクト self
- 2 番目のパラメータ: 呼び出しメソッドの名前
- 3 番目のパラメータ: 前のメソッドが受け入れる必要があるパラメータ nil
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:<#(nonnull id)#> selector:<#(nonnull SEL)#> object:<#(nullable id)#>];
2. 1を開始して操作を実行します
[op1 start];
NSブロック操作
1.作成オペレーション
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
/*code*/
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
/*code*/
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
/*code*/
}];
タスクの追加 (サブスレッドの開始)
//注: 操作内のタスクの数が 1 より大きい場合、タスクを同時に実行するためにサブスレッドが開かれます。
//注: これはサブスレッドではない可能性があります。メインスレッドかもしれない
[op1 addExecutionBlock:^{
/*code*/
}];
[op2 addExecutionBlock:^{
/*code*/
}];
[op3 addExecutionBlock:^{
/*code*/
}];
2. キューを作成する
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
3. オペレーションをキューに追加します
[queue addOperation:op1]; // 内部已经调用了[op1 start];
[queue addOperation:op2];
[queue addOperation:op3];
簡単な方法
1) オペレーションを作成します。
2) オペレーションをキューに追加します。
[queue addOperationWithBlock^{
<#code#>
}];
NSOperation から継承したカスタム サブクラスを使用する
NSOperation から継承したサブクラスをカスタマイズし、main または start を書き換えることで、独自の NSOperation オブジェクトを定義できます。
main メソッドを書き換えるだけであれば、タスクの実行、完了ステータス、およびタスクの終了を変更するための基礎となるコントロールが存在します。
start メソッドをオーバーライドする場合は、タスクのステータスを自分で制御する必要があります。
main メソッドをオーバーライドするのは比較的簡単で、スレッドのステータス属性 executing (実行中かどうか) および completed (完了したかどうか) を管理する必要はありません。main が実行から戻ると、操作は終了します。
main メソッドをオーバーライドする
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSOperationTest : NSOperation
@end
NS_ASSUME_NONNULL_END
#import "NSOperationTest.h"
@implementation NSOperationTest
- (void)main {
if (!self.isCancelled) {
[NSThread sleepForTimeInterval:2];
NSLog(@"test1--%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"test2--%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"test3--%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"test4--%@", [NSThread currentThread]);
}
}
@end
NSOperationTest* test = [[NSOperationTest alloc] init];
NSLog(@"%d", test.finished);
[test start];
NSLog(@"%d", test.finished);
操作結果:
開始メソッドをオーバーライドする
- (void)start {
if (!self.isCancelled) {
[NSThread sleepForTimeInterval:2];
NSLog(@"test1--%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"test2--%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"test3--%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"test4--%@", [NSThread currentThread]);
}
}
操作結果:
start メソッドをオーバーライドするシステムがスレッドのステータス属性を自動的に管理しないことが明らかにわかります。
NSOperationQueueは使用せず、カスタムサブクラスの操作オブジェクトをメインスレッドで作成して実行し、新しいスレッドを開始しませんでした。
NSOperationQueue はシリアル実行とパラレル実行を制御します
NSOperation 操作の依存関係
NSOperation と NSOperationQueue の最も魅力的な点は、オペレーション間に依存関係を追加できることです。依存関係を通じて、オペレーション間の実行順序を簡単に制御できます。
NSOperation は依存関係を使用するための 3 つのインターフェイスを提供します
(void)addDependency:(NSOperation *) op は依存関係を追加します。つまり、現在の操作は op の完了に依存します。op が完了するまで現在の操作は実行されません。
(void)removeDependency:(NSOperation *)op は依存関係を削除し、現在の操作の操作 op への依存をキャンセルします。
@property (readonly, copy) NSArray<NSOperation *> *dependency; 操作オブジェクトのプロパティ、現在の操作の実行が開始される前に完了したすべての操作オブジェクトの配列。
依存関係が追加されていない場合:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"firstOperation");
}];
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"secondOperation");
}];
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"thirdOperation");
}];
[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];
依存関係は追加されず、実行は同時に実行され、順序はまったくありません。
依存関係を追加した後:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"firstOperation");
}];
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"secondOperation");
}];
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"thirdOperation");
}];
[secondOperation addDependency:firstOperation]; // 让secondOperation依赖于firstOperation,即firstOperation先执行,在执行secondOperation
[thirdOperation addDependency:secondOperation]; // 让thirdOperation依赖于secondOperation,即secondOperation先执行,在执行thirdOperation
[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];
実行は順調です。
相互依存性:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"firstOperation");
}];
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"secondOperation");
}];
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"thirdOperation");
}];
[secondOperation addDependency:firstOperation]; // 让secondOperation依赖于firstOperation,即firstOperation先执行,在执行secondOperation
[firstOperation addDependency:secondOperation]; // 让firstOperation依赖于secondOperation,即secondOperation先执行,在执行firstOperation
[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];
これらはどちらも実装されていません。
NSO操作の優先順位
依存関係は単なる実行関係です。NSOperation には優先順位属性も提供されます。setQueuePriority: メソッドを使用して、同じキュー内の操作の優先順位を設定できます。システムによって与えられる優先順位は次のとおりです (デフォルトは NSOperationQueuePriorityNormal):
// 优先级的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
}
キューに追加された操作の場合、操作が最初に準備完了状態になり、次に準備完了状態になる順序は、操作間の相対的な優先順位によって決まります。
準備完了状態は、操作間の依存関係によって異なります。つまり、操作は、この操作の依存関係にある操作が完了した場合にのみ準備完了状態になります。
キュー優先度:
- queuePriority 属性は、準備完了状態になった操作間の実行の開始順序を決定します。優先順位は依存関係を置き換えません。この属性は実行の開始順序を決定するだけであり、実行の完了順序は保証されません。
- キューに優先度の高い操作と優先度の低い操作の両方が含まれており、両方の操作の準備ができている場合、キューは優先度の高い操作を最初に実行します。
- queuePriority 属性は、準備完了状態になるオペレーション間の実行開始順序を決定するものであり、実行完了順序を保証するものではありません。依存関係は 2 つの操作間の実行シーケンスを制御し、1 つの操作が依存する操作が完了した後に実行を開始します。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"begin firstOperation");
[NSThread sleepForTimeInterval:2];
NSLog(@"firstOperation end");
}];
firstOperation.queuePriority = NSOperationQueuePriorityLow;
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"begin secondOperation");
[NSThread sleepForTimeInterval:2];
NSLog(@"secondOperation end");
}];
secondOperation.queuePriority = NSOperationQueuePriorityHigh;
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"begin thirdOperation");
[NSThread sleepForTimeInterval:2];
NSLog(@"thirdOperation end");
}];
thirdOperation.queuePriority = NSOperationQueuePriorityNormal;
queue.maxConcurrentOperationCount = 3;
[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];
- 実行するオペレーションの最大数を 1 に設定すると、キュー内のオペレーションは追加された順に順番に実行され、1 つのオペレーションが実行されるまで別のオペレーションは実行されません。
- キュー内のすべての操作が同じ優先度を持ち、準備完了状態になる場合、実行順序はキューに送信された順序に基づきます。それ以外の場合、キューは常に他の準備完了操作よりも高い優先順位で操作を実行します。
NSOperation のqualityOfService 属性を使用して、キュー内の操作のサービス品質を設定することもできます (iOS8 以降、Apple は優先度ではなくサービス品質を使用することを推奨しています)。
typedef NS_ENUM(NSInteger, NSQualityOfService) {
NSQualityOfServiceUserInteractive = 0x21,
NSQualityOfServiceUserInitiated = 0x19,
NSQualityOfServiceUtility = 0x11,
NSQualityOfServiceBackground = 0x09,
NSQualityOfServiceDefault = -1
}
@property NSQualityOfService qualityOfService;
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"执行任务1,%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"执行任务2,%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"执行任务3,%@",[NSThread currentThread]);
}];
// 设置服务质量
op1.qualityOfService = NSQualityOfServiceBackground;
op2.qualityOfService = NSQualityOfServiceUserInitiated;
op3.qualityOfService = NSQualityOfServiceUtility;
// 将操作加入队列
[queue addOperations:[NSArray arrayWithObjects:op1, op2, op3, nil] waitUntilFinished:YES];
優先順位もサービスの品質も、コードの優先実行を絶対に保証するものではありません。これは優先順位の逆転に関連しています。自分で調べてみてください。
つまり、優先順位とサービス品質を設定しても、コード実行の絶対的な順序は保証できず、コードが最初に実行されるか最後に実行される確率が変わるだけです。
操作の実行順序を保証したい場合、つまり操作間に同期関係がある場合は、操作オブジェクトが異なる操作キューにある場合でも、依存関係を使用して絶対的な実行順序を保証する必要があります。アクション オブジェクトは、依存するすべての操作が実行を完了するまで実行の準備ができているとは見なされません。