NSURLSession с nsblockoperation и очередями


у меня есть приложение, которое в настоящее время использует NSURLConnection для подавляющего большинства сетей. Я хотел бы перейти к NSURLSession потому что Apple говорит мне, что это путь.

мое приложение просто использует синхронную версию NSURLConnection через + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error метод класса. Я делаю это в течение NSBlockOperation работает на NSOperationQueue так что я не напрасно блокирую основную очередь. Большим преимуществом такого способа является то, что я могу сделать операции зависимыми друг от друга. Например, я может ли задача, запрашивающая данные, зависеть от завершения задачи входа в систему.

Я не видел никакой поддержки синхронных операций в пределах NSURLSession. Все, что я могу найти, это статьи, высмеивающие меня за то, что я даже думаю об использовании его синхронно, и что я ужасный человек для блокировки потоков. Штраф. Но я не вижу способа сделать NSURLSessionTasks зависит друг от друга. Есть ли способ сделать это?

или есть описание того, как я мог сделать такую вещь в другом путь?

2   51   2014-01-18 04:22:07

2 ответа:

самая жесткая критика синхронных сетевых запросов зарезервирована для тех, кто делает это из основной очереди (поскольку мы знаем, что никогда не следует блокировать основную очередь). Но вы делаете это на своей собственной фоновой очереди, которая решает самую вопиющую проблему с синхронными запросами. Но вы теряете некоторые замечательные функции, которые обеспечивают асинхронные методы (например, отмена запросов, если это необходимо).

Я отвечу на ваш вопрос (как сделать 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

@Rob я бы рекомендовал вам опубликовать свой ответ в качестве решения, учитывая следующее примечание к документации от NSURLSession.dataTaskWithURL(_:completionHandler:):

этот метод предназначен в качестве альтернативы sendAsynchronousRequest: queue:completionHandler: метод NSURLConnection, с дополнительной возможностью поддержки пользовательских аутентификация и отмена.