Requête synchrone vers asynchrone

Ben77650Ben77650 Membre
juillet 2014 modifié dans API UIKit #1

Bonjour,

 

Oui je sais que je suis chiant, que j'ai déjà  posé 10 questions sur l'asynchronisme, mais la je bloque pour transformer ma requête synchrone en asynchrone afin d'optimiser mon app et que ça  ne soit plus aussi long (car la actuellement on voit cette vue Connexion 5 secondes, avant que ça passe sur la suivante Profil)



- (NSData *)executePostCall {
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@%@", @http://monsite.com/azazaz/modeles/android/login.php]];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:@MonApp accessGroup:nil];
    NSString *adEmail = [keychainItem objectForKey:(__bridge id)(kSecValueData)];
    NSString *pwd = [keychainItem objectForKey:(__bridge id)(kSecAttrAccount)];
    
    MyManager *sharedManager = [MyManager sharedManager];
    
    NSString *requestFields = @"";
    if(![_email.text isEqualToString:@""] && ![_password.text isEqualToString:@""])
    {
        requestFields = [requestFields stringByAppendingFormat:@email=%@&;", _email.text];
        requestFields = [requestFields stringByAppendingFormat:@password=%@", [self md5:_password.text]];
    }
    else if(![adEmail  isEqualToString:@""] && ![pwd isEqualToString:@""])
    {
        requestFields = [requestFields stringByAppendingFormat:@email=%@&;", adEmail];
        requestFields = [requestFields stringByAppendingFormat:@password=%@", pwd];
    }
    else if(sharedManager.userName !=nil)
    {
        requestFields = [requestFields stringByAppendingFormat:@email=%@&;", sharedManager.userName];
        requestFields = [requestFields stringByAppendingFormat:@password=%@", sharedManager.pass];
    }
    
    
    
    requestFields = [requestFields stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSData *requestData = [requestFields dataUsingEncoding:NSUTF8StringEncoding];
    request.HTTPBody = requestData;
    request.HTTPMethod = @POST;
    
    
    NSHTTPURLResponse *response = nil;
    NSError *error = nil;
    NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
    if (error == nil && response.statusCode == 200) {
    } else {
    }
    return responseData;
}

Je l'utilise comme cela par la suite:



- (void)viewDidLoad
{
    [super viewDidLoad];
    
    KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:@MonApp accessGroup:nil];
    NSString *adEmail = [keychainItem objectForKey:(__bridge id)(kSecValueData)];
    NSString *pwd = [keychainItem objectForKey:(__bridge id)(kSecAttrAccount)];
    
    if(![adEmail  isEqualToString:@""] && ![pwd isEqualToString:@""])
    {
        
        dispatch_queue_t downloadQueue = dispatch_queue_create("downloader", NULL);
        
        dispatch_async(downloadQueue, ^{
            NSData *result = [self executePostCall];
            dispatch_async(dispatch_get_main_queue(), ^{
                
                id strResult=nil;
                strResult = [NSJSONSerialization JSONObjectWithData:result options:0 error:nil];
                
                
                NSString* boolOk=[strResult objectForKey:@result];
                if([boolOk isEqualToString:@ok])
                {
        Profil* prof=[[Profil alloc]init];
        
        
        prof.email=adEmail;
        prof.password=pwd;
        
        [self.navigationController pushViewController:prof animated:NO];
        [prof getMemberInfo];
        [prof getUserOffers];
                }
            });
        });

    }

    MyManager *sharedManager = [MyManager sharedManager];
    if(sharedManager.userName !=nil)
    {
        
        dispatch_queue_t downloadQueue = dispatch_queue_create("downloader", NULL);
        
        dispatch_async(downloadQueue, ^{
            NSData *result = [self executePostCall];
            dispatch_async(dispatch_get_main_queue(), ^{
                
                id strResult=nil;
                strResult = [NSJSONSerialization JSONObjectWithData:result options:0 error:nil];
                
                
                NSString* boolOk=[strResult objectForKey:@result];
                if([boolOk isEqualToString:@ok])
                {
        
                    Profil* prof=[[Profil alloc]init];
        
        
                    prof.email=sharedManager.userName;
                    prof.password=sharedManager.pass;
        
                    [self.navigationController pushViewController:prof animated:NO];
                    [prof getMemberInfo];
                    [prof getUserOffers];
                }
            });
        });

    }
}

Merci d'avance à  ceux qui m'aideront


Mots clés:

Réponses

  • Hello,


     


    Utilise plutôt les méthodes asynchrones de NSURLConnection. 


     


    http://forum.cocoacafe.fr/topic/12678-réglé-sérialisation-très-longue/page-2


     


    @Aligator


    non pour des requêtes réseau c'est déconseillé d'utiliser dispatch_async + des méthodes synchrones à  l'intérieur du dispatch_async. NSURLConnection n'est pas prévu pour et ça va mettre beaucoup + de temps que si tu utilises le mécanisme de RunLoop pour lequel NSURLConnection est prévu.

    Pour faire des requêtes asynchrone il faut toujours utiliser [NSURLConnection sendAsynchronousRequest:queue:handler:] plutôt que d'appeler les méthodes synchrones et d'essayer soi-même de les rendre asynchrone dans un dispatch_async. Quand y'a des méthodes déjà  toutes faites pour faire de l'asynchrone, toujours les utiliser plutôt que d'utiliser les synchrones dans un thread.

     


  • C'est justement ce que je cherche à  faire (d'où le titre), seulement je vois pas trop comment l'adapter, vu que ma seule valeur de retour c'est une data représentant ma requête synchrone :/


  • AliGatorAliGator Membre, Modérateur
    Il faut changer ta signature de méthode la ta méthode executePostCall est sensée retourner une NSData vu la signature qu'elle a. Or par définition si tu veux rendre ta méthode asynchrone ça ne sera pas possible de retourner une NSData en retour de ta méthode sans attendre la fin de la requête ! Donc il faut faire autrement, par exemple prévoir un block pour pouvoir retourner la valeur NSData de façon asynchrone et pas en valeur de retour immédiate.
  • samirsamir Membre
    juillet 2014 modifié #5

    Utilises un block pour ta méthode executePostCall, tu appeles ton block à  la fin de la requête asynchrone.


     


    Construis le block avec les bons paramètres ( NSData et NSError peut être ). 


     


    ( d'ailleurs il faut donner un autre nom de méthode à  executePostCall :)).


  • @Ben77650



    .... afin d'optimiser mon app et que ça soit plus long ....



      ???   Tu veux vraiment que ça soit plus long? ou que ça ne soit plus long ... donc plus court?


  • Ben77650Ben77650 Membre
    juillet 2014 modifié #7

    @tablier, je me suis mal exprimé, que ce ne soit plus aussi long, (donc plus court)


     


    @Ali @Samir


     


    Quelque chose comme cela ?



    - (void)executePostCall: completionBlock:(void (^)(BOOL succeeded))completionBlock
    {
    //- (void)downloadImageWithURL:(NSURL *)url
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@%@", @http://www.mywebsite.com/login.php]];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:@TchatAnnonce accessGroup:nil];
    NSString *adEmail = [keychainItem objectForKey:(__bridge id)(kSecValueData)];
    NSString *pwd = [keychainItem objectForKey:(__bridge id)(kSecAttrAccount)];

    MyManager *sharedManager = [MyManager sharedManager];

    NSString *requestFields = @"";
    if(![_email.text isEqualToString:@""] && ![_password.text isEqualToString:@""])
    {
    requestFields = [requestFields stringByAppendingFormat:@email=%@&;", _email.text];
    requestFields = [requestFields stringByAppendingFormat:@password=%@", [self md5:_password.text]];
    }
    else if(![adEmail isEqualToString:@""] && ![pwd isEqualToString:@""])
    {
    requestFields = [requestFields stringByAppendingFormat:@email=%@&;", adEmail];
    requestFields = [requestFields stringByAppendingFormat:@password=%@", pwd];
    }
    else if(sharedManager.userName !=nil)
    {
    requestFields = [requestFields stringByAppendingFormat:@email=%@&;", sharedManager.userName];
    requestFields = [requestFields stringByAppendingFormat:@password=%@", sharedManager.pass];
    }



    requestFields = [requestFields stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSData *requestData = [requestFields dataUsingEncoding:NSUTF8StringEncoding];
    request.HTTPBody = requestData;
    request.HTTPMethod = @POST;

    [NSURLConnection sendAsynchronousRequest:request
    queue:[NSOperationQueue mainQueue]
    completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
    if ( !error )
    {
    completionBlock(YES);
    } else{
    completionBlock(NO);
    }
    }];

    }

    Je changerais le nom de la méthode par la suite ;)


  • Bonjour, 


    Oui ça à  a l'air correct ton choix de remplissage de mail / mot de passe me parait un peu bancale dans le sens ou même si on sait que le couple n'est pas remplie on envoie quand meme la requête qui renverra un "NO" à  chaque fois mais bon c'est plus ou moins ce que tu attends ça fait juste une requête pour rien.


     


    Sinon y a ce genre de ligne qui pour moi devrait renvoyer un warning



    //Forme trop complexe
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@%@", @http://www.mywebsite.com/login.php]];

    //Forme usuelle
    NSURL *url = [NSURL URLWithString:@http://www.mywebsite.com/login.php];



    Les deux lignes sont équivalentes elles te retourneront toutes les deux une url valide par contre je penses que ta façon de faire est plus gourmande car le compilateur va chercher à  faire une concaténation de chaine de caractère pour rien.


     


    Pour finir peut être qu'en plus de la verification sur le "error" je ferais un retour du serveur ne serait ce que pour renvoyer un message adapté que l'on pourrait affiché à  l'utilisateur "login/mdp incorrect", "Compte bannis" ou "Compte déjà  loggé ailleurs" je dis pas que tu devras mettre ces messages mais il peut éventuellement être pratique des les avoir.


     


    De plus je ne suis pas sur qu'un fail de connexion renvoies une erreur et du coup avec cette méthode je penses que tout le monde sera connecté du coté applicatif même si évidemment le serveur considérera lui que les code sont non valide et ne reverra pas de données. (à  tester car suivant comment tu fais ta connexion il renverra surement une erreur http du style error 401 unauthorized)

  • samirsamir Membre
    juillet 2014 modifié #9

    Hello,


     


    Tu es sur la bonne voie :).


     


    Y a deux problème avec ta solution :


     


    1. Il faut  jamais tester le retour de ta méthode sur l'objet error. Si tu regarde la documentation de la méthode sendAsynchro...


    tu verra que si le data est nil dans ce cas la requête à  échouée et c'est la qu'il faut regarder le détail de l'erreur dans l'objet error, et si il est différent de nil la requête à  réussi.


     


    2. Tu ne récupère pas les données retournées par ta requête réseaux ( data), puisque tu as juste défini ton block avec un seul paramètre ( Bool) donc tu vas juste savoir si la requête a réussi ou pas. Si tu veux récupérer les données aussi ( peut être l'erreur aussi) il faut rajouter un autre paramètre à  ton block ( un NSData par exemple).


Connectez-vous ou Inscrivez-vous pour répondre.