ios - tecnologia - iphone and nfc
NSURLSession con NSBlockOperation y colas (2)
Tengo una aplicación que actualmente usa NSURLConnection
para la gran mayoría de sus redes. Me gustaría pasar a NSURLSession
porque Apple me dice que ese es el camino a seguir.
Mi aplicación solo usa la versión síncrona de NSURLConnection
por medio del + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
método de clase de + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
. Hago esto dentro de un NSBlockOperation
ejecutándose en un NSOperationQueue
así que no estoy bloqueando innecesariamente la cola principal. La gran ventaja de hacer las cosas de esta manera es que puedo hacer que las operaciones dependan unas de otras. Por ejemplo, puedo hacer que la tarea que solicita los datos dependa del final de la tarea de inicio de sesión.
No he visto ningún soporte para las operaciones sincrónicas dentro de NSURLSession
. Todo lo que puedo encontrar son artículos que me ridiculizan incluso por pensar en usarlo sincrónicamente y que soy una persona horrible para bloquear los hilos. Multa. Pero no veo forma de hacer que NSURLSessionTask
dependa uno del otro. ¿Hay una manera de hacer eso?
¿O hay una descripción de cómo haría una cosa así de otra manera?
@Rob Le animo a que publique su respuesta como una solución, en vista de la siguiente nota de documentación de NSURLSession.dataTaskWithURL(_:completionHandler:)
:
Este método está pensado como una alternativa al método sendAsynchronousRequest: queue: completionHandler: de NSURLConnection, con la capacidad adicional de admitir autenticación y cancelación personalizadas.
Las críticas más duras a las solicitudes de red síncrona están reservadas para quienes lo hacen desde la cola principal (ya que sabemos que nunca se debe bloquear la cola principal). Pero lo hace en su propia cola de fondo, que aborda el problema más atroz con las solicitudes sincrónicas. Pero está perdiendo algunas características maravillosas que ofrecen las técnicas asincrónicas (por ejemplo, cancelación de solicitudes, si es necesario).
Responderé a su pregunta (cómo hacer que NSURLSessionDataTask
comporte de forma sincronizada) a continuación, pero realmente le NSURLSessionDataTask
adoptar los patrones asincrónicos en lugar de luchar contra ellos. Sugeriría refactorizar tu código para usar patrones asíncronos. Específicamente, si una tarea depende de otra, simplemente ponga el inicio de la tarea dependiente en el controlador de finalización de la tarea anterior.
Si tiene problemas en esa conversión, publique otra pregunta sobre el desbordamiento de pila, mostrándonos lo que intentó y podemos intentar ayudarlo.
Si desea sincronizar una operación asincrónica, un patrón común es usar un semáforo de envío para que la secuencia que inició el proceso asíncrono pueda esperar una señal del bloque de finalización de la operación asincrónica antes de continuar. Nunca haga esto desde la cola principal, pero si está haciendo esto desde alguna cola de fondo, puede ser un patrón útil.
Puede crear un semáforo con:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
A continuación, puede hacer que el bloque de finalización del proceso asíncrono señalice el semáforo con:
dispatch_semaphore_signal(semaphore);
Y luego puede tener el código fuera del bloque de finalización (pero aún en la cola de fondo, no en la cola principal) espere esa señal:
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
Entonces, con NSURLSessionDataTask
, juntando todo eso, podría verse así:
[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
]);
Con NSURLConnection
(ahora en desuso), debe pasar por algunos aros para iniciar solicitudes desde una cola en segundo plano, pero NSURLSession
maneja con elegancia.
Habiendo dicho eso, el uso de operaciones de bloque como este significa que las operaciones no responderán a los eventos de cancelación (mientras se están ejecutando, al menos). Por lo tanto, generalmente evito esta técnica de semáforo con operaciones de bloque y simplemente NSOperation
tareas de datos en la subclase asíncrona NSOperation
. Entonces disfruta de los beneficios de las operaciones, pero también puede hacerlos cancelables. Es más trabajo, pero un patrón mucho mejor.
Por ejemplo:
//
// 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
y
//
// 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
Dónde:
//
// AsynchronousOperation.h
//
@import Foundation;
@interface AsynchronousOperation : NSOperation
/// Complete the asynchronous operation.
///
/// This also triggers the necessary KVO to support asynchronous operations.
- (void)completeOperation;
@end
Y
//
// 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