NSURLSessionTasks
のシリアルキューを作成するためのベストプラクティスは何ですか?
私の場合、私は次のことをする必要があります:
NSURLSessionDataTask
)NSURLSessionDownloadTask
)これが私がこれまで持ってきたものです:
session = [NSURLSession sharedSession];
//Download the JSON:
NSURLRequest *dataRequest = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task =
[session dataTaskWithRequest:dataRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//Figure out the URL of the file I want to download:
NSJSONSerialization *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
NSURL *downloadURL = [NSURL urlWithString:[json objectForKey:@"download_url"]];
NSURLSessionDownloadTask *fileDownloadTask =
[session downloadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:playlistURL]]
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
NSLog(@"completed!");
}];
[fileDownloadTask resume];
}
];
別の補完内に補完ブロックを書き込むのが面倒に見えるという事実は別として、[fileDownloadTask resume]
...を呼び出すと、EXC_BAD_ACCESSエラーが発生します。fileDownloadTask
はnilではありません!
それで、NSURLSessionTasks
を配列する最良の方法は何ですか?
最も簡単なこのアプローチを使用する必要があります: https://stackoverflow.com/a/31386206/2308258
または、操作キューを使用して、タスクを相互に依存させる
================================================== =====================
HTTPMaximumConnectionsPerHostメソッドについて
NSURLSessionTasksの先入れ先出しシリアルキューを実装する簡単な方法は、HTTPMaximumConnectionsPerHostプロパティが1に設定されているNSURLSessionですべてのタスクを実行することです。
HTTPMaximumConnectionsPerHostは、1つのshared接続がそのセッションのタスクに使用されることを保証するだけですが、それらが順次処理されることを意味しません。
http://www.charlesproxy.com/ を使用してネットワークレベルで確認できます。HTTPMaximumConnectionsPerHostを設定すると、NSURLSessionによって同時にタスクが同時に開始され、信じられているように連続していない。
実験1:
Task2の場合:url = download.thinkbroadband.com/20MB.Zip
結果:task1 completionBlockが呼び出され、次にtask2 completionBlockが呼び出されます
完了ブロックは、タスクに同じ時間がかかる場合に期待した順序で呼び出されることがあります(== --- ==)ただし、同じNSURLSessionを使用すると、NSURLSessionに基本的な呼び出しの順序がなく、最初に終了したものだけが完了することがわかります。
実験2:
task2:url = download.thinkbroadband.com/10MB.Zip(小さいファイル)
結果:task2 completionBlockが呼び出され、次にtask1 completionBlockが呼び出されます
結論として、注文を自分で行う必要があります。NSURLSessionには、リクエストの注文に関するロジックがありません。ホストごとの最大接続数を1に設定した場合でも、最初に終了するものの完了ブロックを呼び出すだけです。
PS:申し訳ありませんが、スクリーンショットを投稿するのに十分な評判がありません。
編集:
Mataejoonが指摘したように、HTTPMaximumConnectionsPerHost
を1に設定しても、接続がシリアルに処理されることは保証されません。 NSURLSessionTask
。の信頼できるシリアルキューが必要な場合は、別のアプローチを試してください(元の回答は次のとおりです)。
NSURLSessionTasks
の先入れ先出しシリアルキューを実装する簡単な方法は、 NSURLSession
があるHTTPMaximumConnectionsPerHost
ですべてのタスクを実行することですプロパティを1
に設定:
+ (NSURLSession *)session
{
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
[configuration setHTTPMaximumConnectionsPerHost:1];
session = [NSURLSession sessionWithConfiguration:configuration];
});
return session;
}
次に、必要な順序でタスクを追加します。
NSURLSessionDataTask *sizeTask =
[[[self class] session] dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
#import "SessionTaskQueue.h"
@interface SessionTaskQueue ()
@property (nonatomic, strong) NSMutableArray * sessionTasks;
@property (nonatomic, strong) NSURLSessionTask * currentTask;
@end
@implementation SessionTaskQueue
- (instancetype)init {
self = [super init];
if (self) {
self.sessionTasks = [[NSMutableArray alloc] initWithCapacity:15];
}
return self;
}
- (void)addSessionTask:(NSURLSessionTask *)sessionTask {
[self.sessionTasks addObject:sessionTask];
[self resume];
}
// call in the completion block of the sessionTask
- (void)sessionTaskFinished:(NSURLSessionTask *)sessionTask {
self.currentTask = nil;
[self resume];
}
- (void)resume {
if (self.currentTask) {
return;
}
self.currentTask = [self.sessionTasks firstObject];
if (self.currentTask) {
[self.sessionTasks removeObjectAtIndex:0];
[self.currentTask resume];
}
}
@end
このように使います
__block __weak NSURLSessionTask * wsessionTask;
use_wself();
wsessionTask = [[CommonServices shared] doSomeStuffWithCompletion:^(NSError * _Nullable error) {
use_sself();
[self.sessionTaskQueue sessionTaskFinished:wsessionTask];
...
}];
[self.sessionTaskQueue addSessionTask:wsessionTask];
私はNSOperationQueueを使用しています(Owenが示唆しています)。 NSURLSessionTasksをNSOperationサブクラスに配置し、依存関係を設定します。依存するタスクは、実行する前に、依存するタスクが完了するまで待機しますが、ステータス(成功または失敗)をチェックしないため、プロセスを制御するロジックを追加します。
私の場合、最初のタスクはユーザーが有効なアカウントを持っているかどうかをチェックし、必要に応じて作成します。最初のタスクでは、NSUserDefault値を更新して、アカウントが有効(またはエラー)であることを示します。 2番目のタスクはNSUserDefault値をチェックし、すべてのOKがユーザー資格情報を使用してサーバーにデータを投稿するかどうかを確認します。
(NSURLSessionTasksを別々のNSOperationサブクラスに入れると、コードのナビゲートも簡単になりました)
NSOperationサブクラスをNSOperationQueueに追加し、依存関係を設定します。
NSOperationQueue *ftfQueue = [NSOperationQueue new];
FTFCreateAccount *createFTFAccount = [[FTFCreateAccount alloc]init];
[createFTFAccount setUserid:@"********"]; // Userid to be checked / created
[ftfQueue addOperation:createFTFAccount];
FTFPostRoute *postFTFRoute = [[FTFPostRoute alloc]init];
[postFTFRoute addDependency:createFTFAccount];
[ftfQueue addOperation:postFTFRoute];
最初のNSOperationサブクラスでは、サーバーにアカウントが存在するかどうかを確認します。
@implementation FTFCreateAccount
{
NSString *_accountCreationStatus;
}
- (void)main {
NSDate *startDate = [[NSDate alloc] init];
float timeElapsed;
NSString *ftfAccountStatusKey = @"ftfAccountStatus";
NSString *ftfAccountStatus = (NSString *)[[NSUserDefaults standardUserDefaults] objectForKey:ftfAccountStatusKey];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setValue:@"CHECKING" forKey:ftfAccountStatusKey];
// Setup and Run the NSURLSessionTask
[self createFTFAccount:[self userid]];
// Hold it here until the SessionTask completion handler updates the _accountCreationStatus
// Or the process takes too long (possible connection error)
while ((!_accountCreationStatus) && (timeElapsed < 5.0)) {
NSDate *currentDate = [[NSDate alloc] init];
timeElapsed = [currentDate timeIntervalSinceDate:startDate];
}
if ([_accountCreationStatus isEqualToString:@"CONNECTION PROBLEM"] || !_accountCreationStatus) [self cancel];
if ([self isCancelled]) {
NSLog(@"DEBUG FTFCreateAccount Cancelled" );
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setValue:@"ERROR" forKey:ftfAccountStatusKey];
}
}
次のNSOperation投稿データ:
@implementation FTFPostRoute
{
NSString *_routePostStatus;
}
- (void)main {
NSDate *startDate = [[NSDate alloc] init];
float timeElapsed;
NSString *ftfAccountStatusKey = @"ftfAccountStatus";
NSString *ftfAccountStatus = (NSString *)[[NSUserDefaults standardUserDefaults] objectForKey:ftfAccountStatusKey];
if ([ftfAccountStatus isEqualToString:@"ERROR"])
{
// There was a ERROR in creating / accessing the user account. Cancel the post
[self cancel];
} else
{
// Call method to setup and run json post
// Hold it here until a reply comes back from the operation
while ((!_routePostStatus) && (timeElapsed < 3)) {
NSDate *currentDate = [[NSDate alloc] init];
timeElapsed = [currentDate timeIntervalSinceDate:startDate];
NSLog(@"FTFPostRoute time elapsed: %f", timeElapsed);
}
}
if ([self isCancelled]) {
NSLog(@"FTFPostRoute operation cancelled");
}
}