10

私はオペレーションとスレッドをよりよく理解しようとしており、AFNetworking のAFURLConnectionOperationサブクラス、たとえば実世界のソース コードに目を向けました。

私の現在の理解では、 のインスタンスがNSOperationオペレーション キューに追加されると、キューはとりわけ、オペレーションの実行を担当するスレッドを管理します。Apple のドキュメントではNSOperation、サブクラスが操作のYESため-isConcurrentに戻った場合でも、常に別のスレッドで開始されることが指摘されています (10.6 以降)。

Thread Programming GuideおよびConcurrency Programming Guideの全体にわたる Apple の強力な言語に基づくと、スレッドの管理は の内部実装に任せるのが最善のようですNSOperationQueue

ただし、AFNetworking のAFURLConnectionOperationサブクラスは newNSThreadを生成し、操作の-mainメソッドの実行はこのネットワーク要求スレッドにプッシュされます。なんで?このネットワーク リクエスト スレッドが必要な理由 ライブラリは幅広いユーザーが使用するように設計されているため、これは防御的なプログラミング手法ですか? ライブラリの消費者がデバッグする手間が減るだけですか? 専用スレッドですべてのネットワーク アクティビティを実行することで、(微妙な) パフォーマンス上の利点はありますか?

(1 月 26 日に追加)
Dave Dribin によるブログ投稿で、彼は NSURLConnection の特定の例を使用して、操作をメイン スレッドに戻す方法を示しています。

私の好奇心は、Apple のThread Programming Guideの次のセクションから来ています。

スレッドを適度にビジー状態に保ちます。
スレッドを手動で作成および管理する場合は、スレッドが貴重なシステム リソースを消費することに注意してください。スレッドに割り当てるタスクが適度に長く存続し、生産的であることを確認するために最善を尽くす必要があります。同時に、ほとんどの時間をアイドル状態に費やしているスレッドを終了することを恐れてはなりません。スレッドは重要な量のメモリを使用し、その一部は有線であるため、アイドル状態のスレッドを解放すると、アプリケーションのメモリ フットプリントが削減されるだけでなく、他のシステム プロセスが使用できるように物理メモリが解放されます。

AFNetworking のネットワーク リクエスト スレッドが「適度にビジー状態に保たれていない」ように私には思えます。ネットワーク I/O を処理するために無限の while ループを実行しています。しかし、ほら、それがこの質問の要点です-私は知りません、そして私は推測しているだけです.

操作、スレッド(実行ループ?)、および/またはGCDに関する特定の洞察または分解はAFURLConnectionOperation、私の理解のギャップを埋めるのに非常に有益です。

4

1 に答える 1

6

これは興味深い質問であり、その答えはすべて、どのようNSOperationNSURLConnection相互作用し、連携するかというセマンティクスに関するものです。

AnNSURLConnection自体が非同期タスクです。これはすべてバックグラウンドで行われ、そのデリゲートを定期的に呼び出して結果を返します。を開始するNSURLConnectionと、スケジュールされている実行ループを使用してデリゲート コールバックがスケジュールされるため、実行中のスレッドで実行ループが常に実行されている必要がありますNSURLConnection

したがって、-start私たちのメソッドはAFURLConnectionOperation、コールバックを受け取ることができるように、操作が終了する前に常に戻る必要があります。これにはAFURLConnectionOperation、非同期操作である必要があります。

から: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOperation_class/index.html

プロパティの値は、現在のスレッドに対して非同期で実行される操作の場合は YES であり、現在のスレッドで同期的に実行される操作の場合は NO です。このプロパティのデフォルト値は NO です。

しかし、AFURLConnectionOperationこのメソッドをオーバーライドして、YES期待どおりに返します。次に、クラスの説明から次のことがわかります。

非同期操作の start メソッドを呼び出すと、対応するタスクが完了する前にそのメソッドが戻る場合があります。非同期操作オブジェクトは、別のスレッドでそのタスクをスケジュールする責任があります。この操作では、新しいスレッドを直接開始するか、非同期メソッドを呼び出すか、実行のためにブロックをディスパッチ キューに送信することで、これを行うことができます。制御が呼び出し元に戻ったときに操作が進行中かどうかは実際には問題ではなく、進行中の可能性があるということだけです。

AFNetworking は、すべてのNSURLConnectionオブジェクト (およびその結果のデリゲート コールバック) をスケジュールするクラス メソッドを使用して、単一のネットワーク スレッドを作成します。これがそのコードですAFURLConnectionOperation

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });

    return _networkRequestThread;
}

以下は、すべての実行ループ モードで AFNetwokring スレッドの実行ループをAFURLConnectionOperationスケジュールすることを示すコードです。NSURLConnection

- (void)start {
    [self.lock lock];
    if ([self isCancelled]) {
        [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    } else if ([self isReady]) {
        self.state = AFOperationExecutingState;

        [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    }
    [self.lock unlock];
}

- (void)operationDidStart {
    [self.lock lock];
    if (![self isCancelled]) {
        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        for (NSString *runLoopMode in self.runLoopModes) {
            [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
            [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
        }

        //...
    }
    [self.lock unlock];
}

ここでは、メソッドがそのスレッドから呼び出されるの[NSRunloop currentRunloop]ではなく、AFNetworking スレッドで実行ループを取得します。おまけとして、バックグラウンド スレッドの実行ループでも実行できます。mainRunloop-operationDidStartoutputStream

AFURLConnectionOperationコールバックを待機し、ネットワーク リクエストの進行に合わせて自身の状態変数 ( 、、 )NSURLConnectionを更新します。AFNetworking スレッドはその実行ループを繰り返しスピンするため、潜在的に多くのスケジュールからコールバックが呼び出され、オブジェクトがそれらに反応できるようになります。NSOperationcancelledfinishedexecutingNSURLConnectionsAFURLConnectionOperationsAFURLConnectionOperation

常にキューを使用して操作を実行する場合は、それらを同期として定義する方が簡単です。ただし、操作を手動で実行する場合は、操作オブジェクトを非同期として定義することをお勧めします。タスクの進行中の状態を監視し、KVO 通知を使用してその状態の変化を報告する必要があるため、非同期操作の定義にはより多くの作業が必要です。ただし、非同期操作を定義すると、手動で実行された操作が呼び出し元のスレッドをブロックしないようにする場合に役立ちます。

また、返されるまで呼び出して観察することによりNSOperation、 a なしでan を使用することもできることに注意してください。同期操作として実装され、終了を待っている現在のスレッドがブロックされた場合、現在の実行ループでコールバックをスケジュールするように実際に終了することはありません。したがって、この有効な使用シナリオをサポートするには、非同期にする必要があります。NSOperationQueue-start-isFinishedYESAFURLConnectionOperationNSURLConnectionNSURLConnectionNSOperationAFURLConnectionOperation

質問への回答

  • はい。AFNetworking は、すべての接続をスケジュールするために使用するスレッドを 1 つ作成します。スレッドの作成にはコストがかかります。(これが GCD が作成された理由の一部です。GCD は実行中のスレッドのプールを保持し、必要に応じて別のスレッドにブロックをディスパッチします。スレッドを自分で作成、破棄、管理する必要はありません)

  • バックグラウンドの AFNetworking スレッドでは処理は行われません。AFNetworking は のcompletionBlockプロパティを使用して、が に設定されているNSOperationときに実行される処理を行います。finishedYES

完了ブロックの正確な実行コンテキストは保証されていませんが、通常はセカンダリ スレッドです。したがって、このブロックを使用して、非常に特殊な実行コンテキストを必要とする作業を行うべきではありません。代わりに、その作業をアプリケーションのメイン スレッドまたはそれを実行できる特定のスレッドにシャントする必要があります。たとえば、操作の完了を調整するためのカスタム スレッドがある場合、完了ブロックを使用してそのスレッドに ping を実行できます。

HTTP 接続の後処理は で処理されAFHTTPRequestOperationます。このクラスは、特に応答オブジェクトをバックグラウンドで変換するためのディスパッチ キューを作成し、作業をそのキューに振り分けます。ここを参照

- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                              failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    self.completionBlock = ^{
        //...
        dispatch_async(http_request_operation_processing_queue(), ^{
            //...

AFURLConnectionOperationこれは、彼らがスレッドを作成しないように書いたのではないかという疑問を投げかけていると思います. このAPIがあるので、答えはイエスだと思います

- (void)setDelegateQueue:(NSOperationQueue*) queue NS_AVAILABLE(10_7, 5_0);

これは、実行ループを使用するのではなく、特定の操作キューでデリゲート コールバックをスケジュールするためのものです。しかし、AFNetworking の古い部分を見ていると、その API は iOS 5 と OS X 10.7 でしか利用できませんでした。Github の Blame ビューを見ると、2011 年に iPhone 4s と iOS 5 が発表された実際の日に、偶然にAFURLRequestOperationも Mattt が実際にメソッドを作成したことがわかります。スレッドが作成された時点で、非同期サブクラスで実行中にバックグラウンドで+networkRequestThreadコールバックを受信する唯一の方法は、スレッドを作成し、スレッド上で接続をスケジュールすることであったため、スレッドが存在すると推論できます。NSURLConnectionNSOperation

  • 関数を使用してスレッドが作成されdispatch_onceます。(あなたが提案したように追加された余分なコードを参照してください)この関数は、実行するブロックに含まれるコードがアプリケーションの存続期間中に1回だけ実行されるようにします。AFNetworking スレッドは、必要なときに作成され、アプリケーションの存続期間中存続します。

  • 私が書いたとき、私はNSURLConnectionOperation意味しAFURLConnectionOperationました。私はそれを言及してくれてありがとう:)

于 2015-01-07T17:26:29.260 に答える