J'ai une application qui utilise actuellement NSURLConnection
pour la grande majorité de son réseau. Je voudrais passer à NSURLSession
parce que Apple me dit que c'est la voie à suivre.
Mon application utilise simplement la version synchrone de NSURLConnection
au moyen de la méthode de classe + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
. Je le fais dans un NSBlockOperation
fonctionnant sur un NSOperationQueue
donc je ne bloque pas inutilement la file d'attente principale. Le gros avantage de faire les choses de cette façon est que je peux rendre les opérations dépendantes les unes des autres. Par exemple, je peux avoir la tâche qui demande des données dépend de la fin de la tâche de connexion.
Je n'ai vu aucun support pour les opérations synchrones dans NSURLSession
. Tout ce que je peux trouver, ce sont des articles qui me tournent en dérision même en pensant à l'utiliser de manière synchrone et que je suis une personne horrible pour bloquer les fils. Bien. Mais je ne vois aucun moyen de rendre les NSURLSessionTask
dépendants les uns des autres. Y-a-t-il un moyen de faire ça?
Ou existe-t-il une description de la façon dont je ferais une telle chose d'une manière différente?
Les critiques les plus sévères des demandes de réseau synchrone sont réservées à ceux qui le font à partir de la file d'attente principale (car nous savons qu'il ne faut jamais bloquer la file d'attente principale). Mais vous le faites dans votre propre file d'attente en arrière-plan, ce qui résout le problème le plus grave avec les demandes synchrones. Mais vous perdez de merveilleuses fonctionnalités fournies par les techniques asynchrones (par exemple, l'annulation des demandes, si nécessaire).
Je répondrai à votre question (comment faire en sorte que NSURLSessionDataTask
se comporte de manière synchrone) ci-dessous, mais je vous encourage vraiment à adopter les modèles asynchrones plutôt que de les combattre. Je suggère de refactoriser votre code pour utiliser des modèles asynchrones. Plus précisément, si une tâche dépend d'une autre, placez simplement l'initiation de la tâche dépendante dans le gestionnaire d'achèvement de la tâche précédente.
Si vous rencontrez des problèmes lors de cette conversion, postez une autre question Stack Overflow, en nous montrant ce que vous avez essayé, et nous pouvons essayer de vous aider.
Si vous souhaitez rendre une opération asynchrone synchrone, un modèle courant consiste à utiliser un sémaphore de répartition afin que votre thread qui a lancé le processus asynchrone puisse attendre un signal du bloc d'achèvement de l'opération asynchrone avant de continuer. Ne faites jamais cela à partir de la file d'attente principale, mais si vous le faites à partir d'une file d'attente d'arrière-plan, cela peut être un modèle utile.
Vous pouvez créer un sémaphore avec:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
Vous pouvez alors avoir le bloc d'achèvement du processus asynchrone signaler le sémaphore avec:
dispatch_semaphore_signal(semaphore);
Et vous pouvez alors avoir le code en dehors du bloc de complétion (mais toujours dans la file d'attente en arrière-plan, pas la file d'attente principale) attendre ce signal:
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
Donc, avec NSURLSessionDataTask
, en mettant tout cela ensemble, cela pourrait ressembler à:
[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
]);
Avec NSURLConnection
(désormais obsolète), vous devez parcourir certains cercles pour lancer des requêtes à partir d'une file d'attente en arrière-plan, mais NSURLSession
la gère avec élégance.
Cela dit, l'utilisation d'opérations de bloc comme celle-ci signifie que les opérations ne répondront pas aux événements d'annulation (pendant leur exécution, au moins). Donc, j'évite généralement cette technique de sémaphore avec des opérations de bloc et j'encapsule simplement les tâches de données dans la sous-classe asynchrone NSOperation
. Ensuite, vous profitez des avantages des opérations, mais vous pouvez également les rendre annulables. C'est plus de travail, mais un modèle bien meilleur.
Par exemple:
//
// 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
et
//
// 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
Où:
//
// AsynchronousOperation.h
//
@import Foundation;
@interface AsynchronousOperation : NSOperation
/// Complete the asynchronous operation.
///
/// This also triggers the necessary KVO to support asynchronous operations.
- (void)completeOperation;
@end
Et
//
// 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 Je vous encourage à publier votre réponse en tant que solution, compte tenu de la note de documentation suivante de NSURLSession.dataTaskWithURL(_:completionHandler:)
:
Cette méthode est conçue comme une alternative à la méthode sendAsynchronousRequest: queue: compléteHandler: de NSURLConnection, avec la possibilité supplémentaire de prendre en charge l'authentification et l'annulation personnalisées.