J'utilise NSURLSessionDownloadTask
avec des sessions en arrière-plan pour réaliser toutes mes demandes REST. De cette façon, je peux utiliser le même code sans avoir à penser que mon application est en arrière-plan ou à l'avant-plan.
Mon back-end est mort depuis un moment et j'ai saisi cette opportunité pour tester le comportement de NSURLSession
avec les délais d'attente.
À ma grande surprise, aucun de mes rappels NSURLSessionTaskDelegate
ne reçoit jamais d'appel. Quel que soit le délai d'attente que j'ai défini sur la variable NSURLRequest
ou sur la variable NSURLSessionConfiguration
, je ne reçois jamais de rappel de la part d'iOS qui me dit que la demande s'est terminée avec le délai d'attente.
C'est-à-dire lorsque je lance une NSURLSessionDownloadTask
sur une session en arrière-plan. Le même comportement se produit lorsque l'application est en arrière-plan ou au premier plan.
Exemple de code:
- (void)launchDownloadTaskOnBackgroundSession {
NSString *sessionIdentifier = @"com.mydomain.myapp.mySessionIdentifier";
NSURLSessionConfiguration *backgroundSessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier];
backgroundSessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
backgroundSessionConfiguration.timeoutIntervalForRequest = 40;
backgroundSessionConfiguration.timeoutIntervalForResource = 65;
NSURLSession *backgroundSession = [NSURLSession sessionWithConfiguration:backgroundSessionConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.timeout.com/"]];
request.timeoutInterval = 30;
NSURLSessionDownloadTask *task = [backgroundSession downloadTaskWithRequest:request];
[task resume];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSLog(@"URLSession:task:didCompleteWithError: id=%d, error=%@", task.taskIdentifier, error);
}
Cependant, lorsque j'utilise la session par défaut, j'obtiens un rappel d'erreur après 30 secondes (le délai d'attente que j'ai défini au niveau de la demande).
Exemple de code:
- (void)launchDownloadTaskOnDefaultSession {
NSURLSessionConfiguration *defaultSessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
defaultSessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
defaultSessionConfiguration.timeoutIntervalForRequest = 40;
defaultSessionConfiguration.timeoutIntervalForResource = 65;
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultSessionConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.timeout.com/"]];
request.timeoutInterval = 30;
NSURLSessionDownloadTask *task = [defaultSession downloadTaskWithRequest:request];
[task resume];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSLog(@"URLSession:task:didCompleteWithError: id=%d, error=%@", task.taskIdentifier, error);
}
Je n'arrive pas à trouver dans la documentation quoi que ce soit qui suggère que le délai d'attente devrait se comporter différemment lors de l'utilisation de sessions en arrière-plan.
Est-ce que quelqu'un a aussi rencontré ce problème? Est-ce un bug ou une fonctionnalité?
J'envisage de créer un rapport de bogue, mais je reçois généralement des commentaires beaucoup plus rapidement sur SO (quelques minutes) que sur le rapport de bogue (six mois).
Cordialement,
Depuis iOS8, NSUrlSession en mode arrière-plan n'appelle pas cette méthode de délégation si le serveur ne répond pas .-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
Le téléchargement/téléchargement reste inactif indéfiniment . Ce délégué est appelé sur iOS7 avec une erreur lorsque le serveur ne répond pas.
En général, une session en arrière-plan NSURLSession n'échoue pas une tâche si quelque chose ne va pas sur le fil. Au contraire, il continue à chercher un bon moment pour exécuter la demande et réessaie à ce moment. Cela continue jusqu'à l'expiration du délai d'attente de la ressource (c'est-à-dire la valeur de la propriété timeoutIntervalForResource dans l'objet NSURLSessionConfiguration utilisé pour créer la session). La valeur par défaut actuelle pour cela la valeur est une semaine!
Informations citées tirées de cette source
En d'autres termes, le comportement de l'échec pendant un délai d'attente dans iOS7 était incorrect. Dans le contexte d'une session en arrière-plan, il est plus intéressant de ne pas échouer immédiatement en raison de problèmes de réseau. Ainsi, depuis iOS8, la tâche NSURLSession se poursuit même si elle rencontre des délais d'attente et une perte de réseau. Il continue toutefois jusqu'à atteindre timeoutIntervalForResource.
Donc, fondamentalement, timeoutIntervalForRequest ne fonctionnera pas en arrière-plan, mais timeoutIntervalForResource fonctionnera.
Le délai d'attente pour DownloadTask est lancé par NSURLSessionTaskDelegate et non par NSURLSessionDownloadDelegate.
Pour déclencher un délai d'attente (-1001) lors d'une tâche de téléchargement:
Attendez que le téléchargement commence. pourcentage de morceaux de téléchargement de données va déclencher:
URLSession: downloadTask: didWriteData: totalBytesWritten: totalBytesExpectedToWrite:
Ensuite, mettez en pause l'application entière dans le débogueur XCode.
Attendez 30secs.
Annulez l'application à l'aide des boutons du débogueur XCode
La connexion http du serveur doit expirer et déclencher:
-1001 "La requête a expiré."
#pragma mark -
#pragma mark NSURLSessionTaskDelegate - timeouts caught here not in DownloadTask delegates
#pragma mark -
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
if(error){
ErrorLog(@"ERROR: [%s] error:%@", __PRETTY_FUNCTION__,error);
//-----------------------------------------------------------------------------------
//-1001 "The request timed out."
// ERROR: [-[SNWebServicesManager URLSession:task:didCompleteWithError:]] error:Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSUnderlyingError=0x1247c42e0 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, NSErrorFailingURLStringKey=https://directory.clarksons.com/api/1/dataexport/ios/?lastUpdatedDate=01012014000000, NSErrorFailingURLKey=https://directory.clarksons.com/api/1/dataexport/ios/?lastUpdatedDate=01012014000000, _kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-2102, NSLocalizedDescription=The request timed out.}
//-----------------------------------------------------------------------------------
}else{
NSLog(@"%s SESSION ENDED NO ERROR - other delegate methods should also be called so they will reset flags etc", __PRETTY_FUNCTION__);
}
}
Comme vous, l'application sur laquelle je travaille utilise toujours une session en arrière-plan. Une chose que j’ai remarquée, c’est que le délai d’attente fonctionne correctement si une connexion est interrompue, c’est-à-dire si le transfert a bien démarré. Cependant, si je lance une tâche de téléchargement pour une URL qui n'existe pas, le délai d'expiration ne sera pas écoulé.
Étant donné que vous avez dit que votre back-end était mort depuis un certain temps, cela ressemble beaucoup à ce que vous voyiez.
C'est assez facile à reproduire. Il suffit de définir un délai d'attente d'environ 5 secondes. Avec une URL valide, vous obtiendrez des mises à jour sur l'avancement, puis l'expiration du délai. Même avec une session de fond. Avec une URL non valide, la communication se calme dès que vous appelez reprendre.
Il existe une méthode dans UIApplicationDelegate, qui vous permet de connaître le processus d'arrière-plan.
-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
S'il y a plus d'une session, vous pouvez identifier votre session en
if ([identifier isEqualToString:@"com.mydomain.myapp.mySessionIdentifier"])
Une autre méthode est utilisée pour informer périodiquement de l’avancement des travaux. Vous pouvez vérifier ici l’état de NSURLSession.
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
NSURLSessionTaskStateRunning = 0,
NSURLSessionTaskStateSuspended = 1,
NSURLSessionTaskStateCanceling = 2,
NSURLSessionTaskStateCompleted = 3,
Je suis venu au même problème. Une solution que j'ai trouvée consiste à utiliser deux sessions, une pour les téléchargements en avant-plan utilisant la configuration par défaut et une pour les téléchargements en arrière-plan avec configuration en arrière-plan. Lorsque vous passez en arrière-plan/au premier plan, générez des données de reprise et transmettez-les de l'une à l'autre. Mais je me demande si vous avez trouvé une autre solution.