sendSynchronousRequest & time out

Bonjour à  tous.



Je post se sujet car j'ai réussi à  résoudre un problème image/rolleyes.gif' class='bbc_emoticon' alt='::)' /> mais je ne sais pas comment ni pourquoi ... image/crazy.gif' class='bbc_emoticon' alt=' B) ' />



Explication :

Je fait des requêtes http pour aller chercher un script php pour récupérer des données dans une base de données (MySQL avec charset par défaut donc Latin1). donc je créer ma requête (NSString) puis la passe en paramètre à  une url exécutant un script php.

J'instancie donc un objet NSURL avec une string concaténé le plus classiquement du monde en passant par un stringByAddingPercentEscapeUsingEncoding:NSUTF8StirngEncoding.

Une fois le NSURL défini j'initialise un NSURLRequest de cette façon :
NSURLRequest *_urlRequest = [[NSURLRequest alloc]initWithURL:_myUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15.0];<br />


Je récupère ensuite le résultat :
NSData *_responseData = [NSURLConnection sendSynchronousRequest:_urlRequest returningResponse:&amp;_reponse error&amp;_error];<br />

Oui je sais le synchronous c'est pas terrible mais en l'occurrence je n'est pas besoin de faire de l'asynchrone ici.


Jusqu'a ce matin tout à  toujours bien fonctionné le plus "fluidement" du monde cependant lors d'une compile de vérification ce matin (sans aucun changement de code) j'ai une erreur durant l'exécution de mon application.

Donc suite à  des tests (NSLog, bp, ...) j'ai isoler le problème et il se trouve que c'est l'initialisation de _responseData qui plante. Enfin, plantage est un grand mot, je dirais plutôt que le senSynchronous fait des sienne avec un code error à  1001 représentant ni plus ni moins qu'un bon vieux time out.



Du coup j'ai tester en dur avec plusieurs requêtes différente (en dur) et il s'est avérer que le problème est totalement aléatoire (du moins c'est ma conclusion). J'ai vérifier les requêtes exécutées et qui avaient déjà  posé problème à  l'aide de cette même url via un navigateur et en directe sur la base de donnée et aucune erreur. Donc mes requêtes sont bonnes également.

J'ai essayer avec NSURLRequest alloc]initWithURL:_myUrl (on s'est jamais) mais toujours le même problème.

Du coup je déconnecte l'ipad, efface l'application de ce dernier, reboot du mac en quittant xCode correctement etc ... (tentative du désespoir image/rolleyes.gif' class='bbc_emoticon' alt='::)' /> ) Je rebranche, je relance et recompile et toujours le même problème et toujours sans avoir modifier le code car il fonctionnais à  23h08 hier (dernière compile et test de ma part) alors pourquoi pas ce matin ?! image/crazy.gif' class='bbc_emoticon' alt=' B) ' />

Du coup ca commence à  m'agacer légèrement, j'ai cherché sur le web des syntaxe peut-être différente pour exécuter une requête, en vain. Je désinstalle à  nouveau l'appli, je build, je débranche l'ipad, je lance l'application et bing plus aucun problème ... image/implore.gif' class='bbc_emoticon' alt=' o:) ' />

Je rebranche l'ipad pour recompiler avec xCode et suivre le déroulement de l'exécution du code et en effet je n'est plus de time out au niveau du sendSynchronous !



Alors ma question est simple, avez vous une idée de la ou le problème peut provenir ?

Réponses

  • AliGatorAliGator Membre, Modérateur
    janvier 2013 modifié #2
    Pas d'explication directe à  part que si tu appelles sendSynchronousRequest depuis le main thread, j'ai bien vu que tu avais noté que c'était pas terrible, mais plus que ça ça bloque ton UI et ton main thread et c'est à  éviter à  tout prix. D'une part pour l'expérience utilisateur certes, mais d'autre part pour les perfs, un blocage de thread (qui s'apparente à  un deadlock limite) est toujours source de soucis.



    Utilise plutôt sendAsynchronousRequest, c'est pas plus compliqué à  utiliser et ça t'évitera des déboires de blocage de thread
    // Au lieu de<br />
    /*<br />
    NSURLResponse* _response;<br />
    NSError* _error;<br />
    NSData* _responseData = [NSURLConnection sendSynchronousRequest:_urlRequest<br />
                                                  returningResponse:&amp;_reponse<br />
                                                              error:&amp;_error];<br />
    // Ton code pour traiter _responseData, _response et _error<br />
    */<br />
    <br />
    // Utilise<br />
    [NSURLConnection sendAsynchronousRequest:_urlRequest<br />
                                   queue:[[[NSOperationQueue alloc] init] autorelease]<br />
                           completionHandler:^(NSURLResponse* response, NSData* responseData, NSError* error)<br />
    {<br />
    	// Ton code pour traiter responseData, response et error<br />
    }]
    
    Donc au final ça change pas grand chose à  ton code déjà  écrit, t'as juste à  changer le nom de la méthode et mettre le code qui était après directement dans le block, et le tour est joué, et ça sera mieux et moins bloquant (et donc potentiellement évitera les timeouts en utilisant une queue dédiée pour la requête plutôt que de risquer qu'elle soit bloquée par le thread courant par deadlock...
  • klozenklozen Membre
    janvier 2013 modifié #3
    Yes ca fonctionne image/thumbsup.gif' class='bbc_emoticon' alt='' /> ...

    ... sauf que j'exécute ca dans une classe qui normalement me retourne un NSdictionnary suite à  l'exécution de la requête passé en paramètre. Du coup vu que c'est asynchrome, le return s'effectue avant la requête et du coup me retourne un dictionnaire vide et donc n'affiche rien dans mes conteneur et ce malgré que quelques temps après la requête s'exécute bien avec le bon résultat ...

    Du coup est ce que je doit faire un wait (ou équivalent en objective c) ou j'ai meilleur temps de réorganiser ma classe SQL à  ce type d'utilisation ?



    Ou alors avec un NSNotification qui m'indique que les datas sont remonté et que je peut les afficher pour ensuite effectuer un UIRefreshControle dans ma vue ?
  • CéroceCéroce Membre, Modérateur
    Comme le disait Ali, il ne faut pas bloquer le thread principal, parce que c'est lui qui met à  jour l'IHM. Une méthode simple est de lancer la requête réseau, et en attendant, bloquer l'écran en plaçant une UIView par dessus, pour éviter que l'utilisateur ne lance d'autres requêtes. On placera dedans une UIProgressView pour que l'utilisateur patiente.



    Quand le serveur a répondu à  la requête, le bloc passé à  sendAsynchronousRequest: est exécuté. Si c'est ton contrôleur qui a appelé cette méthode, alors il n'a qu'à  retirer la UIView bloquante et se rafraà®chir.



    Il y a au moins trois manière d'implémenter un mécanisme de call-back:

    - utiliser le centre de notification

    Souvent une mauvaise idée parce que les notifications sont globales à  l'application.

    - utiliser le design pattern Délégation

    C'est beaucoup mieux (dépendances limitées à  un objet uniquement), mais un peu lourd à  mettre en oe“uvre.

    - utiliser un bloc

    Suffit quand les callbacks sont simples, rapide à  mettre en oe“uvre.



    Pour finir, créer le corps des requêtes HTTP, gérer les erreurs et gérer l'asynchronisme est complexe; justement, AFNetworking gère tout ça. Si nous le plébiscitons ici régulièrement, c'est qu'il y a de bonnes raisons !
  • Ok, c'est parti pour les blocs avec AFNetworking / UIProgressView merci image/wink.png' class='bbc_emoticon' alt=';)' />
  • AliGatorAliGator Membre, Modérateur
    'klozen' a écrit:


    ... sauf que j'exécute ca dans une classe qui normalement me retourne un NSdictionnary suite à  l'exécution de la requête passé en paramètre. Du coup vu que c'est asynchrome, le return s'effectue avant la requête et du coup me retourne un dictionnaire vide et donc n'affiche rien dans mes conteneur et ce malgré que quelques temps après la requête s'exécute bien avec le bon résultat ...

    Du coup est ce que je doit faire un wait (ou équivalent en objective c) ou j'ai meilleur temps de réorganiser ma classe SQL à  ce type d'utilisation ?



    Ou alors avec un NSNotification qui m'indique que les datas sont remonté et que je peut les afficher pour ensuite effectuer un UIRefreshControle dans ma vue ?
    Rien de tout ça, surtout pas faire de wait, malheureux !!



    Il suffit de mettre le code où je t'ai dit de le mettre dans mon post plus haut, regarde de nouveau mon exemple de code avec commentaires, en particulier le commentaire "Ton code pour traiter _responseData, _response et _error" (qui correspond donc à  ton code qui va construire ton NSDictionary à  partir de la réponse puis en faire je ne sais quoi) !

    Je n'ai pas mis ces commentaires pour rien !
  • si si j'avais lu image/wink.png' class='bbc_emoticon' alt=';)' /> J'ai bien placer la construction de mon dictionnaire de données avec le résultat de la requête dans le bloc comme tu me l'a indiqué image/wink.png' class='bbc_emoticon' alt=';)' />



    Le problème étant qu'avec le sendSynchronous je fonctionnais avec une classe SqlPerso avec une méthode du type consultationDataServer qui est une fonction retournant un NSDictionnary.

    Du coup dans une vue, dans son viewDidLoad, j'instancie mon objet SqlPerso et j'appel la méthode consultationDataServer avec en paramètre la requête préconstruite comme suit :
    NSDictionnary *myDic = [[NSDictionnary alloc]initWithDictionnary:[sqlPerso consultationDataServer:maReqSql];<br />
    


    Ainsi je récupérais directement un Dictionnaire me permettant ensuite de l'afficher dans un tableView par exemple.

    Sauf qu'avec l'asynchrome le dictionnaire sera forcément vide puisque le code est effectué dans un thread autre que le main. Le code dans le block asynchrome sera exécuté un peu plus tard. Sauf que c'est trop tard puisque le dictionnaire est déjà  initialisé avec la ligne de code cité ci dessus et que par conséquent ma tableView traite un dictionnaire vide.
  • klozenklozen Membre
    janvier 2013 modifié #8
    Après quelques essai voici ce que j'obtiens :
    • Affichage de la vue avec son initialisation avec un dictionnaire vide, etc
    • Appel d'une méthode pour consulter les données sur le serveur
    • Au début de la méthode lancement d'une vue transparente avec un activityindicator
    • construction du NSURLRequest
    • Appel du bloc comme indiqué par Ali
    • fin de la méthode mais dans le bloc récupération des données pour remplir mon dictionnaire
    • puis à  la fin du bloc appel d'une méthode refresh.


    Dans cette méthode refresh (propre à  la vue que l'utilisateur veux afficher) je place :
    dispatch_async(dispatch_get_main_queue(), ^{<br />
    /*reloadData de la tableView<br />
    &amp; removeFromSuperView de la vue transparente ajouté au début */<br />
    });<br />
    




    Cela semble fonctionné mais est ce que cette manière de procédé est correcte ?
  • AliGatorAliGator Membre, Modérateur
    Oui c'est en effet typiquement comme cela qu'on fait habituellement.



    PS : tu peux utiliser des classes toutes faites comme SVProgressHUD pour faire ta vue transparente d'attente, tu la trouveras sur GitHub et elle est plutôt bien faite et jolie, perso c'est celle que j'utilise tout le temps !
  • Merci du tuyau je me garde ça sous le coude pour quand j'aurais un peu plus de temps. En attendant j'ai fait ça avec un AFHTTPClient et une vue d'attente fait maison avec un activity classique.

    Du coup ça,modifie un peu le principe de fonctionnement mais étant donné que j'ai copié le fonctionnement du AFNetworking fournit sur gitub je pense que c'est bon image/smile.png' class='bbc_emoticon' alt=':)' />



    Si des personnes sont curieuse de se savoir exactement comment ça fonctionne je peut fournir ce que j'ai tester. J'integre ça dans mon projet lundi.



    Merci pour vos réponse image/wink.png' class='bbc_emoticon' alt=';)' />
  • klozenklozen Membre
    janvier 2013 modifié #11
    'AliGator' a écrit:


    PS : tu peux utiliser des classes toutes faites comme SVProgressHUD pour faire ta vue transparente d'attente, tu la trouveras sur GitHub et elle est plutôt bien faite et jolie, perso c'est celle que j'utilise tout le temps !


    3 secondes pour le download et 1 minute pour le mette en place image/implore.gif' class='bbc_emoticon' alt=' o:) ' />

    finalement je vais l'utiliser par défaut maintenant image/thumbsup.gif' class='bbc_emoticon' alt='' />
  • klozenklozen Membre
    janvier 2013 modifié #12
    Bon par contre comment gérer l'affichage ? Plus précisément comment savoir quand afficher ou non un message d'attente. Cela afin d'éviter l'affichage d'une nanoseconde du SVProgress entre chaque vue ou rafraà®chissement et du coup ne l'afficher uniquement quand cela est nécessaire.

    Actuellement je le lance avant le déclenchement du block puis le dismiss à  la fin du dernier block d'exécution en asynchrone (avec AFNetworking) du coup cela crée le problème expliquer ce dessus.



    Intégration d'un timer qui au bout de tant de temps d'exécution d'une NSOperationQueue fille (autre que mainQueue) affiche le message ou alors c'est à  moi de gérer et en fonction du code exécuter je choisis ou non d'afficher le SVProgressHUD ?



    J'ai vu qu'il était possible d'ajouter un afterDelay avant le dismiss mais j'aimerais aller plus loin en autorisant ou non l'affichage en fonction du temps d'exécution que le code va prendre. Par exemple au delà  d'une demi seconde on affiche le message.
  • CéroceCéroce Membre, Modérateur
    Teste ton appli sur le réseau GSM, et vue la réactivité, tu verras que ton problème n'en est plus un image/evil.gif' class='bbc_emoticon' alt='>:D' />.
  • J'ai ma carte sim demain mais du coup vu ta réponse ça confirme ce que je pensais : je vais laisser comme ça car c'est vrai qu'en GSM c'est un (gros) poil plus long image/xd-laugh.gif' class='bbc_emoticon' alt='xd' />
  • AliGatorAliGator Membre, Modérateur
    janvier 2013 modifié #15
    Sinon tu peux utiliser ma classe OHHTTPStubs pour bouchonner tes requêtes réseau et simuler un réseau lent à  la vitesse que tu veux !



    Très pratique pour tester ton appli avec des réponses toutes faites qui marchent même si la plateforme à  qui tu dois envoyer des requêtes a un problème ou n'est pas prête (cas très courant du client qui te dit qu'il va te fournir un WebService mais seulement dans 2 mois...) et justement aussi et surtout pour simuler les cas un peu plus réalistes que tes tests en Wifi depuis ton canapés, genre quand tu as un réseau lent façon Edge ou 3D ou des pertes de connexion image/wink.png' class='bbc_emoticon' alt=';)' />
  • klozenklozen Membre
    janvier 2013 modifié #16
    C'est bon à  savoir image/wink.png' class='bbc_emoticon' alt=';)' />



    Par contre j'ai remarqué quelque chose. Lorsque j'utilise mon application la ou je développe pas de problème de connexion ou d'exécution de requête via http/php etc.

    Je prend mon iPad et je vais me connecter à  un autre point wifi donc autre adresse ip publique, etc. Pas de problème la aussi tout fonctionne.

    Seulement lorsque je reviens à  l'endroit ou je développe donc avec ma connexion internet initial ça ne fonctionne plus. Je me retrouve avec des time out pour presque toutes mes requêtes !

    Pourtant j'initialise mon NSURLRequest uniquement avec un NSURL (donc avec utilisation du cache) du coup j'ai essayé en utilisant IngnoringCacheData en gardant un timeoutInterval à  60 mais rien n'y fait ...



    une petite idée de la ou cela pourrait venir ?



    NB : autre observation, si je laisse la requête se mettre en time out au bout de 60s et que je la relance directement après elle s'exécute en mois d'une demi seconde ...

    NB² : bien sur le time out est aléatoire c'est à  dire pas tout le temps et pas toujours pour les même requêtes ...



    Voici la structure du code pour l'exécution d'une requête :


    -(void)maMethodePourLireLaBdd:(void(^)(NSDictionnary *posts, NSError *error))block req:(NSString *)requeteSql{<br />
    //Construction d&#39;un NSURLRequest avec un NSURL pour accéder au script php sur le serveur<br />
    AFHTTPClient *httpClient = [[AFHTTPClient alloc]initWithBaseURL:myUrl];<br />
    AFHTTPRequestOperation *operation = [httpClient HTTPRequestOperationWithRequest:myRequest success:^(AFHTTPRequestOperation *operation, id responseObject) {<br />
    //Traitement du résultat<br />
    //Affichage du résultat avec le block<br />
    }failure:^(AFHTTPRequestOperation *operation, NSError *error) {<br />
    //Affichage de l&#39;erreur dans mon cas time out<br />
    }];<br />
    [httpClient enqueueHTTPRequestOperation:operation];<br />
    }<br />
    
  • AliGatorAliGator Membre, Modérateur
    Et si une fois que tu es revenu sur ton réseau wifi initial mais avant de lancer ta requête (qui sinon tomberait en Timeout d'après ce que tu dis), tu ouvres Safari et essaye d'accéder à  une page quelconque, ça marche ou pas ?



    Si tu n'utilises pas ta connexion data pendant un certain temps, il me semble que l'iPhone met "en veille" la puce qui gère la connection Data/Wifi, et du coup que quand tu fais une requête alors qu'elle était en veille, parfois elle met un peu de temps à  se réveiller. Ce qui pourrait expliquer ce comportement, et dans ce cas même si tu faisais n'importe quelle requête avec Safari tu aurais le même problème, ce qui montrerait que ça n'est pas lié à  ton application.
  • klozenklozen Membre
    janvier 2013 modifié #18
    Ok, sauf qu'avec safari sur le mac ca marche impec' ...

    Je me suis également dit que ca pouvait venir des requêtes, j'ai testé une par une chacune de mes requêtes sur la bdd puis via mon script php dans safari et aucun bug.



    Du coup j'ai tester tout au long de la journée et pour finir (et pour "limiter la casse" pour le moment) j'ai défini un time out interval de 3 avec une condition qui fait que si ma requête est en time out je relance ma requête en boucle (boucle infinie si ma requête reste en time out image/baby.gif' class='bbc_emoticon' alt=' :o ' /> mais c'est uniquement pour les tests)

    Par conséquent j'ai observer qu'en moyenne il va y avoir 2 time out dans une fourchette allant de 0 à  5 et ce jamais sur les mêmes requêtes (ça serais trop simple sinon image/grin.gif' class='bbc_emoticon' alt=';D' /> ).



    Je sais que mon code n'est pas très optimisé du fait que c'est ma toute première application mais c'est bizarre non ? J'utilise mal AFNetworking et les blocks ? Problème de cache local ou de cache tout court ? Etant donnée qu'avant tout mon code était basé sur une application avec des requêtes synchrone (voir début du sujet) j'ai du transiter sur AFNetworking, est ce que du coup ça provoquerais des dysfonctionnement au niveau de l'exécution du code ? Car la gestion de la récupération des données entre sendSynchronous et sendAsynchronous ou encore avec AFNetworking n'est pas du tout la même.
  • klozenklozen Membre
    janvier 2013 modifié #19
    Mais il est vrai qu'après quelques heures (approximativement 2) de tests (donc compilation sur compilation avec tests des fonctionnalités) cela est redevenu fluide comme avant ... et pourtant je suis en wifi alors je n'ose même pas imaginé en 3g ou pire en EDGE ... image/crazy.gif' class='bbc_emoticon' alt=' B) ' />
  • Pour vous tenir un peu au courant, tests ce matin dans les même condition qu'hier mais plus aucun problème de time out ... image/implore.gif' class='bbc_emoticon' alt=' o:) ' />
Connectez-vous ou Inscrivez-vous pour répondre.