37 votes

Meilleur flux de contrôle asynchrone avec les blocs Objective-C

J'utilise AFNetworking pour les appels asynchrones à un service web. Certains de ces appels doivent être enchaînés, les résultats de l'appel A étant utilisés par l'appel B qui est utilisé par l'appel C, etc.

L'AFNetworking traite les résultats des appels asynchrones avec des blocs de réussite/échec définis au moment de la création de l'opération :

NSURL *url = [NSURL URLWithString:@"http://api.twitter.com/1/statuses/public_timeline.json"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
    NSLog(@"Public Timeline: %@", JSON);
} failure:nil];
[operation start];

Il en résulte des blocs d'appels asynchrones imbriqués qui deviennent rapidement illisibles. C'est encore plus compliqué lorsque les tâches ne sont pas dépendantes les unes des autres et doivent au contraire s'exécuter en parallèle et que l'exécution dépend des résultats de toutes les opérations.

Il semble qu'une meilleure approche serait de tirer parti d'une promesses pour nettoyer le flux de contrôle.

Je suis tombé sur MAFuture mais je n'arrive pas à trouver la meilleure façon de l'intégrer à AFNetworking. Étant donné que les appels asynchrones peuvent avoir plusieurs résultats (succès/échec) et qu'ils n'ont pas de valeur de retour, cela ne semble pas être une solution idéale.

Toute indication ou idée serait appréciée.

0 votes

Merci pour cette question - vous avez d'excellentes réponses. J'ai eu un peu de mal à la trouver au départ, et je suis arrivé ici en regardant les promesses. Cet anti-modèle peut se produire pour n'importe quelle API de rappel asynchrone : ce n'est pas spécifique à AFNetworking. J'ai utilisé une recherche du type : "sérialisation des callbacks de blocs imbriqués". Peut-être que quelques balises supplémentaires pourraient aider ? Mais ce n'est peut-être que moi ! :-)

19voto

Tal Bereznitskey Points 1623

J'ai créé une solution légère pour cela. Elle s'appelle Sequencer et est disponible sur github .

Il permet d'enchaîner les appels d'API (ou tout autre code asynchrone) de manière simple et directe.

Voici un exemple d'utilisation de l'AFNetworking :

Sequencer *sequencer = [[Sequencer alloc] init];

[sequencer enqueueStep:^(id result, SequencerCompletion completion) {
    NSURL *url = [NSURL URLWithString:@"https://alpha-api.app.net/stream/0/posts/stream/global"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
        completion(JSON);
    } failure:nil];
    [operation start];
}];

[sequencer enqueueStep:^(NSDictionary *feed, SequencerCompletion completion) {
    NSArray *data = [feed objectForKey:@"data"];
    NSDictionary *lastFeedItem = [data lastObject];
    NSString *cononicalURL = [lastFeedItem objectForKey:@"canonical_url"];

    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:cononicalURL]];
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        completion(responseObject);
    } failure:nil];
    [operation start];
}];

[sequencer enqueueStep:^(NSData *htmlData, SequencerCompletion completion) {
    NSString *html = [[NSString alloc] initWithData:htmlData encoding:NSUTF8StringEncoding];
    NSLog(@"HTML Page: %@", html);
    completion(nil);
}];

[sequencer run];

1 votes

C'est une solution simple et efficace. Merci de l'avoir partagée.

0 votes

Il semble que dans le cas d'une erreur dans l'étape 1 ou 2, le reste des étapes ne sera pas exécuté.

0 votes

@fabb Je crois que c'est le résultat souhaité ici - c'est certainement l'effet que je veux obtenir.

10voto

Jon Reid Points 9726

Je ne l'ai pas encore utilisé, mais il semble que Cacao réactif a été conçu pour faire exactement ce que vous décrivez.

1 votes

Je l'ai utilisé, et Jon a raison. Il est excellent pour ce genre de choses.

0 votes

Intéressant. J'avais découvert Reactive Cocoa mais je ne l'avais pas envisagé pour ce scénario. Puisque les opérations AF sont toutes conformes au KVO, je pourrais ajouter des gestionnaires soit à la file d'attente des opérations, soit aux opérations individuelles. Je vais m'en occuper.

1 votes

J'aime l'approche ReactiveCocoa. Mon article de blog explique comment utiliser ReactiveCocoa à cette fin.

10voto

mattt Points 10419

Il n'était pas rare, lors de l'utilisation de l'AFNetworking dans Gowalla, que les appels s'enchaînent en blocs de réussite.

Mon conseil serait de prendre en compte les demandes et les sérialisations du réseau du mieux que vous pouvez dans les méthodes de classe de votre modèle. Ensuite, pour les demandes qui ont besoin de faire des sous-requêtes, vous pouvez appeler ces méthodes dans le bloc de réussite.

Aussi, au cas où vous ne l'utiliseriez pas déjà, AFHTTPClient simplifie grandement ce type d'interactions complexes entre les réseaux.

0 votes

Merci @mattt. C'est en gros ce que je fais maintenant. Les blocs imbriqués ont juste une odeur de code. C'est la même odeur que j'ai avec la logique conditionnelle profondément imbriquée. Peut-être que j'ai envie d'un peu de la propreté que node.js et d'autres frameworks Javascript offrent pour rendre la programmation fonctionnelle plus lisible.

1 votes

L'imbrication profonde n'est pas un résultat inhérent à cette approche - en factorisant efficacement les callbacks dans leurs propres méthodes, cela devrait ressembler beaucoup plus au chaînage dans un langage fonctionnel. Le fait d'avoir à aller plus loin que deux appels imbriqués est certainement un inconvénient, et cela signifie probablement que vous devriez envisager de créer un nouvel appel d'API pour obtenir ce dont vous avez besoin en une seule fois (si vous en avez le pouvoir).

6voto

jeffmax329 Points 76

PromiseKit pourrait être utile. Il semble qu'il s'agisse de l'une des implémentations de la promesse les plus populaires, et d'autres ont écrit des catégories pour l'intégrer à des bibliothèques comme AFNetworking, cf. PromiseKit-AFNetworking .

4voto

Ben Clayton Points 16793

Il existe une implémentation Objective-C des promesses de style CommonJS ici sur Github :

https://github.com/mproberts/objc-promise

Exemple (tiré du fichier Readme.md)

Deferred *russell = [Deferred deferred];
Promise *promise = [russell promise];

[promise then:^(NSString *hairType){
    NSLog(@"The present King of France is %@!", hairType);
}];

[russell resolve:@"bald"];

// The present King of France is bald!

Je n'ai pas encore essayé cette bibliothèque, mais elle semble "prometteuse" malgré cet exemple un peu décevant. (désolé, je n'ai pas pu résister).

0 votes

Il semble qu'il pourrait être très utile mais il n'est pas conforme à l'ARC et je n'ai pas les moyens de le rendre conforme {soupir}.

1 votes

Ce commit semble l'avoir rendu conforme à l'ARC : github.com/mproberts/objc-promise/commit/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X