web-dev-qa-db-fra.com

Comment définir un délai d'attente avec AFNetworking

Mon projet utilise AFNetworking.

https://github.com/AFNetworking/AFNetworking

Comment puis-je réduire le délai? Sans connexion Internet, le bloc d'échec n'est pas déclenché pour une durée d'environ 2 minutes. Waay à long ....

79
jennas

Changer le délai d'attente n'est certainement pas la meilleure solution au problème que vous décrivez. Au lieu de cela, il semble que ce que vous voulez réellement, c’est que le client HTTP gère le réseau qui devient inaccessible, non?

AFHTTPClient possède déjà un mécanisme intégré vous permettant de savoir quand une connexion Internet est perdue, -setReachabilityStatusChangeBlock:.

Les demandes peuvent prendre beaucoup de temps sur des réseaux lents. Il vaut mieux faire confiance à iOS pour savoir comment gérer les connexions lentes et faire la différence entre cela et ne pas avoir de connexion du tout.


Pour développer mon raisonnement sur la raison pour laquelle les autres approches mentionnées dans ce fil devraient être évitées, voici quelques réflexions:

  • Les demandes peuvent être annulées avant même d’être lancées. La mise en file d'attente d'une demande ne donne aucune garantie quant au moment où elle commence réellement.
  • Les intervalles de délai d'attente ne doivent pas annuler les demandes de longue durée, en particulier les requêtes POST. Imaginez si vous essayiez de télécharger ou de télécharger une vidéo de 100 Mo. Si la demande aboutit du mieux possible sur un réseau 3G lent, pourquoi l’arrêteriez-vous inutilement si cela prend un peu plus de temps que prévu?
  • Faire performSelector:afterDelay:... peut être dangereux dans les applications multithreads. Cela s’ouvre à des conditions de course obscures et difficiles à déboguer.
109
mattt

Je recommande fortement de regarder la réponse de mattt ci-dessus - bien que cette réponse ne déroge pas aux problèmes qu'il mentionne en général, pour la question des affiches originales, la vérification de l'accessibilité est un bien meilleur ajustement.

Toutefois, si vous souhaitez toujours définir un délai d’expiration (sans tous les problèmes inhérents à performSelector:afterDelay: etc, alors la demande de tirage mentionnée par Lego décrit une façon de le faire en tant que commentaire, vous faites juste:

NSMutableURLRequest *request = [client requestWithMethod:@"GET" path:@"/" parameters:nil];
[request setTimeoutInterval:120];

AFHTTPRequestOperation *operation = [client HTTPRequestOperationWithRequest:request success:^{...} failure:^{...}];
[client enqueueHTTPRequestOperation:operation];

mais voyez la mise en garde @KCHarwood mentionne qu'il apparaît Apple ne permet pas que cela soit changé pour POST (qui est corrigé dans iOS 6 et plus)) .

Comme @ChrisopherPickslay le souligne, il ne s'agit pas d'un délai d'attente global, mais d'un délai d'attente entre la réception (ou l'envoi de données). Je ne suis au courant d'aucun moyen de faire raisonnablement un délai d'attente global. La documentation de Apple pour setTimeoutInterval dit:

Le délai d'attente, en secondes. Si, au cours d'une tentative de connexion, la demande reste inactive plus longtemps que le délai imparti, la demande est considérée comme ayant expiré. L'intervalle de temporisation par défaut est de 60 secondes.

43
JosephH

Vous pouvez définir l'intervalle de délai d'attente via la méthode requestSerializer setTimeoutInterval.Vous pouvez obtenir le requestSerializer à partir d'une instance AFHTTPRequestOperationManager.

Par exemple, pour faire une demande de publication avec un délai d’attente de 25 secondes:

    NSDictionary *params = @{@"par1": @"value1",
                         @"par2": @"value2"};

    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

    [manager.requestSerializer setTimeoutInterval:25];  //Time out after 25 seconds

    [manager POST:@"URL" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {

    //Success call back bock
    NSLog(@"Request completed with response: %@", responseObject);


    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
     //Failure callback block. This block may be called due to time out or any other failure reason
    }];
26
Mostafa Abdellateef

Je pense que vous devez corriger cela manuellement pour le moment.

Je sous-classe AFHTTPClient et ai changé le

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters

méthode en ajoutant

[request setTimeoutInterval:10.0];

dans AFHTTPClient.m ligne 236. Bien sûr, il serait bon que cela puisse être configuré, mais pour autant que je sache, cela n’est pas possible pour le moment.

7
Cornelius

Enfin découvert comment le faire avec une requête asynchrone POST:

- (void)timeout:(NSDictionary*)dict {
    NDLog(@"timeout");
    AFHTTPRequestOperation *operation = [dict objectForKey:@"operation"];
    if (operation) {
        [operation cancel];
    }
    [[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
    [self perform:[[dict objectForKey:@"selector"] pointerValue] on:[dict objectForKey:@"object"] with:nil];
}

- (void)perform:(SEL)selector on:(id)target with:(id)object {
    if (target && [target respondsToSelector:selector]) {
        [target performSelector:selector withObject:object];
    }
}

- (void)doStuffAndNotifyObject:(id)object withSelector:(SEL)selector {
    // AFHTTPRequestOperation asynchronous with selector                
    NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
                            @"doStuff", @"task",
                            nil];

    AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:baseURL]];

    NSMutableURLRequest *request = [httpClient requestWithMethod:@"POST" path:requestURL parameters:params];
    [httpClient release];

    AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];

    NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
                          operation, @"operation", 
                          object, @"object", 
                          [NSValue valueWithPointer:selector], @"selector", 
                          nil];
    [self performSelector:@selector(timeout:) withObject:dict afterDelay:timeout];

    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {            
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeout:) object:dict];
        [[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
        [self perform:selector on:object with:[operation responseString]];
    }
    failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NDLog(@"fail! \nerror: %@", [error localizedDescription]);
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeout:) object:dict];
        [[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
        [self perform:selector on:object with:nil];
    }];

    NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
    [[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount];
    [queue addOperation:operation];
}

J'ai testé ce code en laissant mon serveur sleep(aFewSeconds).

Si vous devez effectuer une demande synchrone POST, faites ET NON PAS =)) == utilisez [queue waitUntilAllOperationsAreFinished];. Utilisez plutôt le même approche comme pour la demande asynchrone et attendez le déclenchement de la fonction que vous transmettez dans l’argument du sélecteur.

7
borisdiakur

Sur la base des réponses des autres et de la suggestion de @ mattt concernant des problèmes de projet connexes, voici un exemple rapide si vous sous-classez AFHTTPClient:

@implementation SomeAPIClient // subclass of AFHTTPClient

// ...

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters {
  NSMutableURLRequest *request = [super requestWithMethod:method path:path parameters:parameters];
  [request setTimeoutInterval:120];
  return request;
}

- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block {
  NSMutableURLRequest *request = [super requestWithMethod:method path:path parameters:parameters];
  [request setTimeoutInterval:120];
  return request;
}

@end

Testé pour fonctionner sur iOS 6.

5
Gurpartap Singh

Ne pouvons-nous pas faire cela avec une minuterie comme celle-ci:

Dans le fichier .h

{
NSInteger time;
AFJSONRequestOperation *operation;
}

Dans le fichier .m

-(void)AFNetworkingmethod{

    time = 0;

    NSTtimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(startTimer:) userInfo:nil repeats:YES];
    [timer fire];


    operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
        [self operationDidFinishLoading:JSON];
    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
        [self operationDidFailWithError:error];
    }];
    [operation setJSONReadingOptions:NSJSONReadingMutableContainers];
    [operation start];
}

-(void)startTimer:(NSTimer *)someTimer{
    if (time == 15&&![operation isFinished]) {
        time = 0;
        [operation invalidate];
        [operation cancel];
        NSLog(@"Timeout");
        return;
    }
    ++time;
}
0
Ulaş Sancak

Il existe deux sens différents sur la définition de "délai d'attente" ici.

Délai d'attente comme dans timeoutInterval

Vous souhaitez supprimer une demande lorsqu'elle devient inactive (pas plus de transfert) pendant une durée supérieure à un intervalle de temps arbitraire. Exemple: vous définissez timeoutInterval sur 10 secondes, vous commencez votre demande à 12h00, il est possible que certaines données soient transférées jusqu'à 12h23, puis la connexion expire à 12h33. Cette affaire est couverte par presque toutes les réponses ici (y compris JosephH, Mostafa Abdellateef, Cornelius et Gurpartap Singh).

Délai d'attente comme dans timeoutDeadline

Vous souhaitez supprimer une demande lorsqu'elle atteint une date limite ultérieure et arbitraire. Exemple: vous définissez deadline sur 10 secondes à l’avenir, vous lancez votre demande à 12h00, il peut tenter de transférer des données jusqu’à 12h23, mais la connexion expirera plus tôt à 12: 00h10. Cette affaire est couverte par borisdiakur.

Je voudrais montrer comment implémenter ceci date limite dans Swift (3 et 4) pour AFNetworking 3.1.

let sessionManager = AFHTTPSessionManager(baseURL: baseURL)
let request = sessionManager.post(endPoint, parameters: parameters, progress: { ... }, success: { ... }, failure: { ... })
// timeout deadline at 10 seconds in the future
DispatchQueue.global().asyncAfter(deadline: .now() + 10.0) {
    request?.cancel()
}

Et pour donner un exemple testable, ce code devrait afficher "échec" au lieu de "succès" en raison du délai d'attente immédiat à 0,0 seconde dans le futur:

let sessionManager = AFHTTPSessionManager(baseURL: URL(string: "https://example.com"))
sessionManager.responseSerializer = AFHTTPResponseSerializer()
let request = sessionManager.get("/", parameters: nil, progress: nil, success: { _ in
    print("success")
}, failure: { _ in
    print("failure")
})
// timeout deadline at 0 seconds in the future
DispatchQueue.global().asyncAfter(deadline: .now() + 0.0) {
    request?.cancel()
}
0
Cœur