web-dev-qa-db-ja.com

NSBlockOperationとキューを含むNSURLSession

現在、ネットワークの大半でNSURLConnectionを使用しているアプリがあります。 Appleがその道を教えてくれるので、NSURLSessionに移動したいと思います。

私のアプリは、+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)errorクラスメソッド経由でNSURLConnectionの同期バージョンを使用します。 NSBlockOperationで実行されているNSOperationQueue内でこれを行うので、メインキューを不必要にブロックすることはありません。このように物事を行うことの大きな利点は、操作を互いに依存させることができることです。たとえば、データを要求しているタスクを、ログインタスクの終了に依存させることができます。

NSURLSession内での同期操作のサポートを見たことはありません。私が見つけることができるのは、それを同期的に使用することさえ考えており、私がスレッドをブロックするのが恐ろしい人だということを私にしている記事です。いいよしかし、NSURLSessionTasksを互いに依存させる方法はありません。それを行う方法はありますか?

または、そのようなことを別の方法で行う方法の説明はありますか?

53
Erik Allen

同期ネットワークリクエストに対する最も厳しい批判は、メインキューからそれを行う人のために予約されています(メインキューをブロックしてはならないことがわかっているため)。しかし、あなたはあなた自身のバックグラウンドキューでそれをしている、それは同期リクエストで最もひどい問題に対処する。しかし、非同期技術が提供する素晴らしい機能(たとえば、必要に応じてリクエストをキャンセルするなど)を失っています。

以下の質問(NSURLSessionDataTaskを同期的に動作させる方法)に答えますが、非同期パターンと戦うのではなく、非同期パターンを採用することを本当にお勧めします。非同期パターンを使用するようにコードをリファクタリングすることをお勧めします。具体的には、あるタスクが別のタスクに依存している場合、依存タスクの開始を前のタスクの完了ハンドラーに配置するだけです。

その変換に問題がある場合は、スタックオーバーフローに関する別の質問を投稿して、あなたが試したことを示してください。


非同期操作を同期する場合、一般的なパターンは、ディスパッチセマフォを使用して、非同期プロセスを開始したスレッドが、続行する前に非同期操作の完了ブロックからの信号を待機できるようにすることです。これをメインキューから実行しないでください。ただし、バックグラウンドキューから実行している場合は、便利なパターンになります。

以下を使用してセマフォを作成できます。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

次に、非同期プロセスの完了ブロックがセマフォに信号を送るようにします。

dispatch_semaphore_signal(semaphore);

そして、完了ブロックの外側のコード(メインキューではなく、バックグラウンドキューにあるコード)にそのシグナルを待機させることができます。

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

したがって、NSURLSessionDataTaskを使用して、すべてをまとめると、次のようになります。

[queue addOperationWithBlock:^{

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    NSURLSession *session = [NSURLSession sharedSession]; // or create your own session with your own NSURLSessionConfiguration
    NSURLSessionTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (data) {
            // do whatever you want with the data here
        } else {
            NSLog(@"error = %@", error);
        }

        dispatch_semaphore_signal(semaphore);
    }];
    [task resume];

    // but have the thread wait until the task is done

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    // now carry on with other stuff contingent upon what you did above
]);

NSURLConnection(非推奨)を使用すると、バックグラウンドキューから要求を開始するためにいくつかのフープをジャンプする必要がありますが、NSURLSessionはそれを適切に処理します。


そうは言っても、このようなブロック操作を使用すると、操作はキャンセルイベントに応答しなくなります(少なくとも実行中は)。そのため、私は通常、このセマフォ手法をブロック操作で回避し、非同期NSOperationサブクラスでデータタスクをラップするだけです。その後、操作の利点を享受しますが、それらをキャンセル可能にすることもできます。それはより多くの作業ですが、はるかに良いパターンです。

例えば:

//
//  DataTaskOperation.h
//
//  Created by Robert Ryan on 12/12/15.
//  Copyright © 2015 Robert Ryan. All rights reserved.
//

@import Foundation;
#import "AsynchronousOperation.h"

NS_ASSUME_NONNULL_BEGIN

@interface DataTaskOperation : AsynchronousOperation

/// Creates a operation that retrieves the contents of a URL based on the specified URL request object, and calls a handler upon completion.
///
/// @param  request                    A NSURLRequest object that provides the URL, cache policy, request type, body data or body stream, and so on.
/// @param  dataTaskCompletionHandler  The completion handler to call when the load request is complete. This handler is executed on the delegate queue. This completion handler takes the following parameters:
///
/// @returns                           The new session data operation.

- (instancetype)initWithRequest:(NSURLRequest *)request dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler;

/// Creates a operation that retrieves the contents of a URL based on the specified URL request object, and calls a handler upon completion.
///
/// @param  url                        A NSURL object that provides the URL, cache policy, request type, body data or body stream, and so on.
/// @param  dataTaskCompletionHandler  The completion handler to call when the load request is complete. This handler is executed on the delegate queue. This completion handler takes the following parameters:
///
/// @returns                           The new session data operation.

- (instancetype)initWithURL:(NSURL *)url dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler;

@end

NS_ASSUME_NONNULL_END

そして

//
//  DataTaskOperation.m
//
//  Created by Robert Ryan on 12/12/15.
//  Copyright © 2015 Robert Ryan. All rights reserved.
//

#import "DataTaskOperation.h"

@interface DataTaskOperation ()

@property (nonatomic, strong) NSURLRequest *request;
@property (nonatomic, weak) NSURLSessionTask *task;
@property (nonatomic, copy) void (^dataTaskCompletionHandler)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error);

@end

@implementation DataTaskOperation

- (instancetype)initWithRequest:(NSURLRequest *)request dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler {
    self = [super init];
    if (self) {
        self.request = request;
        self.dataTaskCompletionHandler = dataTaskCompletionHandler;
    }
    return self;
}

- (instancetype)initWithURL:(NSURL *)url dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler {
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    return [self initWithRequest:request dataTaskCompletionHandler:dataTaskCompletionHandler];
}

- (void)main {
    NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:self.request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        self.dataTaskCompletionHandler(data, response, error);
        [self completeOperation];
    }];

    [task resume];
    self.task = task;
}

- (void)completeOperation {
    self.dataTaskCompletionHandler = nil;
    [super completeOperation];
}

- (void)cancel {
    [self.task cancel];
    [super cancel];
}

@end

どこで:

//
//  AsynchronousOperation.h
//

@import Foundation;

@interface AsynchronousOperation : NSOperation

/// Complete the asynchronous operation.
///
/// This also triggers the necessary KVO to support asynchronous operations.

- (void)completeOperation;

@end

そして

//
//  AsynchronousOperation.m
//

#import "AsynchronousOperation.h"

@interface AsynchronousOperation ()

@property (nonatomic, getter = isFinished, readwrite)  BOOL finished;
@property (nonatomic, getter = isExecuting, readwrite) BOOL executing;

@end

@implementation AsynchronousOperation

@synthesize finished  = _finished;
@synthesize executing = _executing;

- (instancetype)init {
    self = [super init];
    if (self) {
        _finished  = NO;
        _executing = NO;
    }
    return self;
}

- (void)start {
    if ([self isCancelled]) {
        self.finished = YES;
        return;
    }

    self.executing = YES;

    [self main];
}

- (void)completeOperation {
    self.executing = NO;
    self.finished  = YES;
}

#pragma mark - NSOperation methods

- (BOOL)isAsynchronous {
    return YES;
}

- (BOOL)isExecuting {
    @synchronized(self) {
        return _executing;
    }
}

- (BOOL)isFinished {
    @synchronized(self) {
        return _finished;
    }
}

- (void)setExecuting:(BOOL)executing {
    @synchronized(self) {
        if (_executing != executing) {
            [self willChangeValueForKey:@"isExecuting"];
            _executing = executing;
            [self didChangeValueForKey:@"isExecuting"];
        }
    }
}

- (void)setFinished:(BOOL)finished {
    @synchronized(self) {
        if (_finished != finished) {
            [self willChangeValueForKey:@"isFinished"];
            _finished = finished;
            [self didChangeValueForKey:@"isFinished"];
        }
    }
}

@end
107
Rob

@Rob NSURLSession.dataTaskWithURL(_:completionHandler:)からの以下のドキュメンテーションノートを考慮して、回答をソリューションとして投稿することをお勧めします。

このメソッドは、NSURLConnectionのsendAsynchronousRequest:queue:completionHandler:メソッドの代替として意図されており、カスタム認証とキャンセルをサポートする機能が追加されています。

2
Andrew Ebling