AFNetworking + cancelRequests + blocks
muqaddar
Administrateur
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:
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:
Je me prends une EX_BAD_ACCESS dans la ligne du dernier block:
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.
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]@"CancelAllRequests"[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]@"GET"[/color][color=#000000] [/color]path[color=#000000]:[/color][color=#d1342a]@"ws/webapp/services/pull"[/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]@"GET"[/color][color=#000000] [/color]path[color=#000000]:[/color][color=#d1342a]@"ws/webapp/services/pull_items"[/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]@"GET"[/color][color=#000000] [/color]path[color=#000000]:[/color][color=#d1342a]@"ws/webapp/services/pull_image"[/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]@"POST"[/color][color=#000000] [/color]path[color=#000000]:[/color][color=#d1342a]@"ws/webapp/services/push_item"[/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]@"GET"[/color][color=#000000] [/color]path[color=#000000]:[/color][color=#d1342a]@"ws/webapp/services/pull"[/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]@"X-Authenticity-Token"[/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.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
L'erreur de gestion mémoire se trouve peut-être plus en amont.
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.
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 ?
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...
ça m'énerve, parce que je fais exactement comme ils disent pour tuer les requêtes. /sad.png' class='bbc_emoticon' alt=':(' />
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.
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.
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)
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):
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:
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...
Je vais vois si ça arrange mon problème de delegate "mort" par la même occasion
Par contre, je me prends ce warning:
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.
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) ?
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 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.
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.