AFNetworking + cancelRequests + blocks

muqaddarmuqaddar Administrateur
mars 2012 modifié dans Objective-C, Swift, C, C++ #1
Bon, je poste à  tout hasard sur ce forum même si je pense que peu de monde ici utilise AFNetworking.



Je trouve ce framework super, j'arrive à  faire à  peu près tout ce que je veux (uploader/télécharger du JSON, uploader des POST, télécharger des images, utiliser les requêtes en queue...etc).



Le tout en utilisant les blocks.



Il y a des fonctions dans AFNetworking pour tuer les requêtes en cours, y compris dans la file d'attente. En gros ça donne ça:




- ([color=#b9369d]void[/color])cancelAllRequests<br />
{<br />
[color=#d1342a][color=#000000]  [/color][color=#3f277d]NSLog[/color][color=#000000]([/color]@&quot;CancelAllRequests&quot;[color=#000000]);[/color][/color]<br />
  <br />
[color=#34595d][color=#000000]  [[[/color][color=#4f8085]HTTPClient[/color][color=#000000] [/color]sharedClient[color=#000000]] [/color]cancelAllHTTPOperationsWithMethod[color=#000000]:[/color][color=#d1342a]@&quot;GET&quot;[/color][color=#000000] [/color]path[color=#000000]:[/color][color=#d1342a]@&quot;ws/webapp/services/pull&quot;[/color][color=#000000]];[/color][/color]<br />
[color=#34595d][color=#000000]  [[[/color][color=#4f8085]HTTPClient[/color][color=#000000] [/color]sharedClient[color=#000000]] [/color]cancelAllHTTPOperationsWithMethod[color=#000000]:[/color][color=#d1342a]@&quot;GET&quot;[/color][color=#000000] [/color]path[color=#000000]:[/color][color=#d1342a]@&quot;ws/webapp/services/pull_items&quot;[/color][color=#000000]];[/color][/color]<br />
[color=#34595d][color=#000000]  [[[/color][color=#4f8085]HTTPClient[/color][color=#000000] [/color]sharedClient[color=#000000]] [/color]cancelAllHTTPOperationsWithMethod[color=#000000]:[/color][color=#d1342a]@&quot;GET&quot;[/color][color=#000000] [/color]path[color=#000000]:[/color][color=#d1342a]@&quot;ws/webapp/services/pull_image&quot;[/color][color=#000000]];[/color][/color]<br />
[color=#34595d][color=#000000]  [[[/color][color=#4f8085]HTTPClient[/color][color=#000000] [/color]sharedClient[color=#000000]] [/color]cancelAllHTTPOperationsWithMethod[color=#000000]:[/color][color=#d1342a]@&quot;POST&quot;[/color][color=#000000] [/color]path[color=#000000]:[/color][color=#d1342a]@&quot;ws/webapp/services/push_item&quot;[/color][color=#000000]];  [/color][/color]<br />
[color=#34595d][color=#000000]  [[[[/color][color=#4f8085]HTTPClient[/color][color=#000000] [/color]sharedClient[color=#000000]] [/color]operationQueue[color=#000000]] [/color][color=#3f277d]cancelAllOperations[/color][color=#000000]];[/color][/color]<br />
}<br />




Il semble que ça marche et que mes requêtes en cours soient annulées, mais le contenu de mes blocks plante juste après.



Ici par exemple:




- ([color=#b9369d]void[/color])pull<br />
{<br />
[color=#34595d][color=#000000]  [[/color][color=#4f8085]delegate[/color][color=#000000] [/color]syncServicesController[color=#000000]:[/color][color=#b9369d]self[/color][color=#000000] [/color]notifyJob[color=#000000]:[/color][color=#784a32]_T[/color][color=#000000]([/color]DOWNLOADING_SERVER_DATA[color=#000000])];[/color][/color]<br />
  <br />
[color=#34595d][color=#000000]  [/color][color=#6f43a4]NSMutableURLRequest[/color][color=#000000] *request = [[[/color][color=#4f8085]HTTPClient[/color][color=#000000] [/color]sharedClient[color=#000000]] [/color]requestWithMethod[color=#000000]:[/color][color=#d1342a]@&quot;GET&quot;[/color][color=#000000] [/color]path[color=#000000]:[/color][color=#d1342a]@&quot;ws/webapp/services/pull&quot;[/color][color=#000000] [/color]parameters[color=#000000]:[/color][color=#b9369d]nil[/color][color=#000000]];[/color][/color]<br />
  <br />
  [color=#4f8085]AFJSONRequestOperation[/color] *operation = [[color=#4f8085]AFJSONRequestOperation[/color] [color=#34595d]JSONRequestOperationWithRequest[/color]:request [color=#34595d]success[/color]:^([color=#6f43a4]NSURLRequest[/color] *request, [color=#6f43a4]NSHTTPURLResponse[/color] *response, [color=#b9369d]id[/color] JSON)<br />
  {<br />
[color=#3f277d][color=#000000]	[/color][color=#b9369d]self[/color][color=#000000].[/color][color=#4f8085]token[/color][color=#000000] = [[response [/color]allHeaderFields[color=#000000]] [/color]objectForKey[color=#000000]:[/color][color=#d1342a]@&quot;X-Authenticity-Token&quot;[/color][color=#000000]];[/color][/color]<br />
  }<br />
  [color=#34595d]failure[/color]:^([color=#6f43a4]NSURLRequest[/color] *request, [color=#6f43a4]NSHTTPURLResponse[/color] *response, [color=#6f43a4]NSError[/color] *error, [color=#b9369d]id[/color] JSON)<br />
  {									<br />
  }];<br />
									  <br />
  [operation [color=#34595d]setDownloadProgressBlock[/color]:^([color=#6f43a4]NSInteger[/color] bytesWritten, [color=#6f43a4]NSInteger[/color] totalBytesWritten, [color=#6f43a4]NSInteger[/color] totalBytesExpectedToWrite) {<br />
	  [[color=#b9369d]self[/color] [color=#34595d]calculateProgressViewWithTotalWritten[/color]:totalBytesWritten [color=#34595d]totalToWrite[/color]:totalBytesExpectedToWrite];<br />
  }];  <br />
  [operation [color=#3f277d]start[/color]];<br />
}<br />




Je me prends une EX_BAD_ACCESS dans la ligne du dernier block:


<br />
[[color=#B9369D]self[/color] [color=#34595D]calculateProgressViewWithTotalWritten[/color]:totalBytesWritten [color=#34595D]totalToWrite[/color]:totalBytesExpectedToWrite];<br />
<br />




Je pense que l'exécution du blocks est "perdue" du moment que j'ai tué la requête (dont se sert l'opération).



Bref, j'arrive pas à  tuer toutes les requêtes proprement.

Réponses

  • CéroceCéroce Membre, Modérateur
    mars 2012 modifié #2
    As-tu essayé un coup d'Instruments/Zombies ?

    L'erreur de gestion mémoire se trouve peut-être plus en amont.
  • J'utilise pas ce framework mais il a l'air vraiment pas mal. Il va falloir que j'y passe.

    Sinon pour ton souci c'est peut-être ailleurs ? Car là  perso je vois pas. J'ai vu qu'au lieu de faire un start de ton opération il fallait mieux la mettre dans la queue de requêtes.
  • muqaddarmuqaddar Administrateur
    mars 2012 modifié #4
    'Céroce' a écrit:


    As-tu essayé un coup d'Instruments/Zombies ?

    L'erreur de gestion mémoire se trouve peut-être plus en amont.




    Non, mais je suis presque sûr que ce n'est pas une erreur de mémoire de ma part.

    Je joins un fichier de logs.



    C'est vraiment lié au block eu au progress download. A vrai dire, je ne comprends pas la mécanique interne qui permet de tuer des requêtes si il y a un progress View à  updater suite à  un setDownloadProgressBlock.



    Comment faire cela proprement ?


    Sinon pour ton souci c'est peut-être ailleurs ? Car là  perso je vois pas. J'ai vu qu'au lieu de faire un start de ton opération il fallait mieux la mettre dans la queue de requêtes.




    Ouais mais j'ai 4 ou 5 accès différents aux requêtes, celle n'a pas besoin d'être dans une queue.





    EDIT: j'arrive à  ne pas planter si je ne ferme pas la modale qui "lance" les requêtes après avoir fait un cancelAllRequests. A suivre...
  • muqaddarmuqaddar Administrateur
    Bon, en fait, les requêtes ne sont pas tuées. Elles continuent de fonctionner après mon "stop". D'où mon problème.



    ça m'énerve, parce que je fais exactement comme ils disent pour tuer les requêtes. image/sad.png' class='bbc_emoticon' alt=':(' />
  • muqaddarmuqaddar Administrateur
    Je relance cette discussion parce que je n'arrive pas à  tuer mes requêtes réseau proprement.



    En gros, mon architecture est :



    viewController 1 => viewController 2 dans modale => classe métier qui gère les connexions réseaux et récupère les données => delegate vers viewController2 renvoyant les erreurs réseaux ou le data récupéré



    Le problème se pose lorsque l'utilisateur choisit de fermer viewController 2 pendant une requête réseau... et encore plus à  cause de la gestion du delegate. Forcément il est tué lorsque je ferme la modale viewController 2. Donc plantage.



    Quelle est la démarche à  adopter dans ce cas là  ? (à  part déactiver le bouton pour fermer la modale hein...) ça reste un cas classique.
  • AliGatorAliGator Membre, Modérateur
    Y'en a qui utilisent encore des delegate pour les réponses aux requêtes réseau & co ?



    Utilise les blocks, c'est plus puissant, plus souple, et tu n'auras pas ce problème (puisque le block retain implicitement tout ce dont il a besoin, pour pas que ça plante quand il s'exécute, et release ensuite le tout une fois qu'il en a fini).



    Tu utilises justement AFNetworking, qui fait un usage massif des blocks (et tant mieux), autant en profiter. Ton objet métier qui gère les connexions réseau peut de même remonter la réponse à  ton ViewController2 via des blocks sur le même modèle.
  • muqaddarmuqaddar Administrateur
    mars 2012 modifié #8
    J'utilise bien les blocks pour les requêtes réseau AFNetworking "à  fond".

    Mais effectivement, je ne les utilise pas pour remonter la réponse à  controller 2.



    Pour cela, j'utilise un delegate plus classique et plus parlant. Est-ce un mal? On doit bien pouvoir tuer ce petit monde sans trop de soucis ? (et j'ai pas trop envie de supprimer toutes mes belles méthodes delegate en plus)
  • AliGatorAliGator Membre, Modérateur
    Bah tu peux continuer à  utiliser la délégation, mais pense à  mettre le delegate à  nil quand tu annules la requête du coup.
  • muqaddarmuqaddar Administrateur
    mai 2012 modifié #10
    Mon problème est le suivant. Tout marche très bien lorsque l'utilisateur ne quitte pas l'application.

    Ce n'est plus le cas dans le cas contraire.



    Mes blocks AFNetworking appellent mes propres méthodes delegate (entre classe métier où ils sont présents et view controller):




    <br />
    [color=#4f8085]AFJSONRequestOperation[/color] *operation = [[color=#4f8085]AFJSONRequestOperation[/color] [color=#34595d]JSONRequestOperationWithRequest[/color]:request [color=#34595d]success[/color]:^([color=#6f43a4]NSURLRequest[/color] *request, [color=#6f43a4]NSHTTPURLResponse[/color] *response, [color=#b9369d]id[/color] JSON)<br />
      {<br />
    [color=#3f277d][color=#000000]	[/color][color=#b9369d]self[/color][color=#000000].[/color][color=#4f8085]token[/color][color=#000000] = [[response [/color]allHeaderFields[color=#000000]] [/color]objectForKey[color=#000000]:[/color][color=#d1342a]@&quot;X-Authenticity-Token&quot;[/color][color=#000000]];[/color][/color]<br />
    [color=#34595d][color=#000000]	[[/color][color=#b9369d]self[/color][color=#000000] [/color]pullUpdateClientItemsWithServerItems[color=#000000]:[JSON [/color][color=#3f277d]valueForKeyPath[/color][color=#000000]:[/color][color=#d1342a]@&quot;items&quot;[/color][color=#000000]]];[/color][/color]<br />
    [color=#34595d][color=#000000]	[[/color][color=#4f8085]delegate[/color][color=#000000] [/color]syncServicesController[color=#000000]:[/color][color=#b9369d]self[/color][color=#000000] [/color]notifyJob[color=#000000]:[/color][color=#784a32]_T[/color][color=#000000](UPDATING_CLIENT_DATA)];[/color][/color]<br />
      }<br />
    <br />
    [color=#34595d]failure[/color]:^([color=#6f43a4]NSURLRequest[/color] *request, [color=#6f43a4]NSHTTPURLResponse[/color] *response, [color=#6f43a4]NSError[/color] *error, [color=#b9369d]id[/color] JSON)<br />
      {<br />
    	[color=#b9369d]if[/color] ([error [color=#3f277d]code[/color]] == -[color=#2537d1]1004[/color]) [[color=#b9369d]self[/color] [color=#34595d]failureWithTitle[/color]:[color=#784a32]_T[/color](SERVER_UNAVAILABLE) [color=#34595d]message[/color]:[color=#784a32]_T[/color](PLEASE_TRY_LATER)];  <br />
    	[color=#b9369d]if[/color] ([response [color=#3f277d]statusCode[/color]] == [color=#2537d1]500[/color]) [[color=#b9369d]self[/color] [color=#34595d]failureWithTitle[/color]:[color=#784a32]_T[/color](SERVER_ERROR) [color=#34595d]message[/color]:[color=#784a32]_T[/color](PLEASE_TRY_LATER)];<br />
      }];<br />
    




    Lorsque l'app passe en background, les blocks continuent à  être exécutés, si je reviens en foreground (becomeActive), plantage assuré dans les lignes où le delegate est appelé... (exec_bad_access...), pourtant je ne touche pas au delegate...



    Pour info, voilà  le code de UIApplicationDelegate:


    <br />
    - ([color=#b9369d]void[/color])applicationWillResignActive:([color=#6f43a4]UIApplication[/color]*)application<br />
    {<br />
    [color=#d1342a][color=#000000]  [/color][color=#3f277d]NSLog[/color][color=#000000]([/color]@&quot;applicationWillResignActive&quot;[color=#000000]);[/color][/color]<br />
      <br />
      [color=#6f43a4]UIBackgroundTaskIdentifier[/color] backgroundIdentifier = [application [color=#3f277d]beginBackgroundTaskWithExpirationHandler[/color]:^([color=#b9369d]void[/color]) {<br />
    [color=#34595d][color=#000000]	[[[/color][color=#4f8085]SyncServicesController[/color][color=#000000] [/color]sharedServices[color=#000000]] [/color]cancelAllRequests[color=#000000]]; [/color][/color]<br />
      }];<br />
      [application [color=#3f277d]endBackgroundTask[/color]:backgroundIdentifier];  <br />
    }<br />
    
  • AliGatorAliGator Membre, Modérateur
    Heu c'est bizarre tu crée une backgroundTask que tu stoppes tout de suite après...



    Normalement on met le endBackgroundTask au moins dans le expirationHandler, pour qu'une fois les 10mn écoulées tu arrêtes le mode "tâche de fond". Et en plus l'appeler aussi une fois qu'une tâche que tu exécutes en tâche de fond est effectivement finie.



    Là  tu lui dis "démarre une tâche de fond" (et si au bout de 10mn elle n'est pas finie, annule toutes mes requêtes)... sauf que juste la ligne d'après tu lui dis "ah ben ça y est mes tâches de fond sont déjà  finies". Du coup ça sert à  rien...
  • muqaddarmuqaddar Administrateur
    mai 2012 modifié #12
    Ah bein oui, dit comme ça... J'ai compris. :-)

    Je vais vois si ça arrange mon problème de delegate "mort" par la même occasion



    Par contre, je me prends ce warning:



  • AliGatorAliGator Membre, Modérateur
    Bah oui normal.

    Tu déclares un block qui va utiliser la variable backgroundTaskIdentifier, qui est une variable locale. Quand tu crées le block, il va te la capturer, mais elle n'aura pas encore de valeur, puisque c'est justement seulement au retour de "beginBackgroundTaskWithExpirationHandler:" que tu vas lui affecter une valeur.



    Solution 1 (bof) : déclarer une propriété pour stocker ce TaskIdentifier. Comme ça c'est self qui sera capturé par le block et pas la variable locale. Du coup plus de problème, puisque quand tu l'utilises dans le block, tu utilises le self capturé, pour lui demander la valeur courante de backgroundTaskIdentifier (qui aura eu le temps d'être affecté), au lieu d'utiliser directement une variable locale qui n'est pas encore initialisée quand le block la capture. Mais bon créer une variable d'instance juste pour ça...



    Solution 2 (mieux) : déclarer la variable backgroundTaskIdentifier avec le mot clé __block, pour demander au block de ne pas la capturer sous forme de constante au moment de la déclaration du block, mais de permettre d'interpréter sa valeur au moment où tu la demandes, plus tard, même si entre temps elle a changé. Du coup avec ce mot clé __block cela résoud ton problème.
  • muqaddarmuqaddar Administrateur
    La solution 2) marche très bien en effet.

    Un grand merci. Je suis encore dépassé par les blocks.



    J'ai réussi à  quitter l'application pendant des synchronisations et les voir continuer en background ou se terminer au retour dans l'application. Je ne sais pas si c'est encore stable à  100% mais le delegate ne semble plus poser problème.



    Bref, d'après ce que j'ai compris, l'annulation des requêtes ne commence qu'à  600 secondes (10 min) ?
  • AliGatorAliGator Membre, Modérateur
    Oui c'est ça, aux dernières nouvelles c'est 10mn (tu peux valider ce temps restant en NSLoguant le backgroundRemainingTime, cf la doc de UIApplication)
  • muqaddarmuqaddar Administrateur
    Merci pour tout !
  • FKDEVFKDEV Membre
    mai 2012 modifié #17
    Une parenthèse :

    La plupart des tutoriels présentent du code dans lequel beginBackgroundTaskWithExpirationHandler est appelé au moment du passage en background soit dans applicationDidEnterBackground

    soit dans applicationWillResignActive.



    En fait, ce n'est pas la bonne pratique à  adopter.

    Si j'ai bien compris la doc, il faudrait appeler la méthode
    beginBackgroundTaskWithExpirationHandler
    
    au moment où les tâches à  ne pas interrompre sont lancées.

    Des librairies comme AFNetworking devrait donc inclure ces appels et proposer une option pour indiquer si le téléchargement doit être poursuivi au moment du passage en background. Ce serait beaucoup plus facile à  gérer.



    Doc :

    You can call this method at any point in your application's execution. You may also call this method multiple times to mark the beginning of several background tasks that run in parallel. However, each task must be ended separately. You identify a given task using the value returned by this method.





    De mon côté, jusqu'à  maintenant, j'ai fait comme l'indique les tutoriels et j'ai dû mettre en place des stratégies un peu pourries de communication entre les objets qui ont des tâches en background et l'"application delegate" pour savoir s'il y a des tâches en cours, s'il faut les annuler, etc.
  • zoczoc Membre
    mai 2012 modifié #18
    'FKDEV' a écrit:


    Si j'ai bien compris la doc, il faudrait appeler la méthode
    beginBackgroundTaskWithExpirationHandler
    
    au moment où les tâches à  ne pas interrompre sont lancées.

    Des librairies comme AFNetworking devrait donc inclure ces appels


    Justement, ça tombe bien, AFNetworking (plus exactement la branche master du dépôt github) le fait, dans l'implémentation de la classe AFURLConnectionOperation.



    Voir la méthode "setShouldExecuteAsBackgroundTaskWithExpirationHandler" qui doit être appelée à  chaque fois qu'une requête devant se poursuivre en arrière plan est lancée.
Connectez-vous ou Inscrivez-vous pour répondre.