Gérer un groupe de NSOperation avec des dépendances
Je travaille sur une application qui créer des contenus et l'envoyer à un serveur. Le contenu est un titre, une image et l'emplacement. Rien de fantaisie.
Le backend est un peu compliqué donc, voici ce que j'ai à faire :
- Permettre à l'utilisateur de prendre une photo, entrez un titre et d'autoriser la carte à utiliser son emplacement
- Générer un identifiant unique pour le poste
- La création d'un poste sur le backend
- Téléchargez l'image
- De rafraîchissement de l'INTERFACE utilisateur
J'ai utilisé un couple de NSOperation des sous-classes pour faire ce travail mais je ne suis pas fier de mon code, voici un exemple.
NSOperation *process = [NSBlockOperation blockOperationWithBlock:^{
//Process image before upload
}];
NSOperation *filename = [[NSInvocationOperation alloc] initWithTarget: self selector: @selector(generateFilename) object: nil];
NSOperation *generateEntry = [[NSInvocationOperation alloc] initWithTarget: self selector: @selector(createEntry) object: nil];
NSOperation *uploadImage = [[NSInvocationOperation alloc] initWithTarget: self selector: @selector(uploadImageToCreatedEntry) object: nil];
NSOperation *refresh = [NSBlockOperation blockOperationWithBlock:^{
//Update UI
[SVProgressHUD showSuccessWithStatus: NSLocalizedString(@"Success!", @"Success HUD message")];
}];
[refresh addDependency: uploadImage];
[uploadImage addDependency: generateEntry];
[generateEntry addDependency: filename];
[generateEntry addDependency: process];
[[NSOperationQueue mainQueue] addOperation: refresh];
[_queue addOperations: @[uploadImage, generateEntry, filename, process] waitUntilFinished: NO];
Ici sont les choses que je n'aime pas: les
- dans mon createEntry: par exemple, je suis stocker le nom de fichier généré dans une propriété, qui mées avec la portée mondiale de ma classe
- dans le uploadImageToCreatedEntry: la méthode, je suis en utilisant dispatch_async + dispatch_get_main_queue() pour mettre à jour le message dans mon HUD
- etc.
Comment voulez-vous gérer un tel flux de travail ? J'aimerais éviter l'incorporation de plusieurs achèvement des blocs et j'ai envie de NSOperation est vraiment la voie à suivre mais je crois aussi qu'il y a une meilleure mise en œuvre quelque part.
Merci!
source d'informationauteur Romain Pouclet
Vous devez vous connecter pour publier un commentaire.
Vous pouvez utiliser ReactiveCocoa à
accomplir cela assez facilement. Un des grands objectifs est de rendre ce genre de
composition trivial.
Si vous n'avez pas entendu parler de ReactiveCocoa avant, ou ne sont pas familiers avec elle, vérifier
les Introduction
pour une explication rapide.
Je vais éviter la duplication d'un ensemble de cadre de présentation ici, mais il suffit de dire que
L'ACFC offre en fait un superset de promesses/à terme. Il vous permet de composer et de
transformer les événements d'origines complètement différentes (UI, réseau, base de données, KVO,
les notifications, etc.), ce qui est incroyablement puissant.
Pour commencer RACifying ce code, la première et la meilleure chose que nous pouvons faire est de mettre
ces opérations sur les méthodes, et de s'assurer que chacun retourne
un
RACSignal
. Ce n'est pas strictement nécessaire (ils peuvent tous être définis à l'intérieurun champ d'application), mais elle rend le code plus modulaire et lisible.
Par exemple, nous allons créer un couple de signaux correspondant à
process
etgenerateFilename
:Les autres opérations (
createEntry
etuploadImageToCreatedEntry
) serait très proche.Une fois que nous avons en place, il est très facile de composer et d'exprimer leurs
dépendances (même si les commentaires de le faire paraître un peu dense):
Remarque que j'ai renommé certaines de vos méthodes afin qu'ils puissent accepter des entrées de
leurs dépendances, en nous donnant une manière plus naturelle de nourrir les valeurs d'une
opération à l'autre.
Il y a d'énormes avantages, cliquez ici:
les choses arrivent, et là où les dépendances mensonge.
l'utilisation de
-deliverOn:
.reste du travail, et finit par atteindre la
subscribeError:
bloc pour facilela manipulation.
les opérations). Par exemple, vous pourriez mettre en place pour déclencher que lorsque d'une INTERFACE utilisateur
signal (comme un clic sur un bouton) feux.
ReactiveCocoa est un grand cadre, et il est malheureusement difficile de distinguer l'
les avantages en un petit exemple de code. Je recommande fortement de vérifier l'
exemples pour quand utiliser
ReactiveCocoa
pour en savoir plus sur la façon dont il peut vous aider.
Un couple de pensées:
J'ai tendance à me prévaloir de l'achèvement des blocs parce que vous probablement ne voulez lancer l'opération suivante que si la précédente a réussi. Vous voulez vous assurer que vous gérer correctement les erreurs et peut facilement sortir de votre chaîne d'opérations, si l'on échoue.
Si je voulais passer des données d'opération à l'autre et ne voulais pas utiliser une propriété de l'appelant classe, je serais probablement définir ma propre réalisation bloc comme une propriété de mon opération personnalisée qui avait un paramètre qui comprenait le domaine que je voulais passer d'une opération à l'autre. Cela suppose, cependant, que vous êtes en train de faire
NSOperation
sous-classement.Par exemple, je pourrais avoir un
FilenameOperation.h
qui définit une interface pour mon opération de la sous-classe:et si ce n'était pas un fonctionnement parallèle, la mise en œuvre pourrait ressembler à:
Clairement, si vous avez un concurrent à l'opération, vous allez mettre en œuvre tous de la norme
isConcurrent
isFinished
etisExecuting
logique, mais l'idée est la même. En aparté, parfois, les gens vont distribuer ces succès ou des échecs de retour à la file d'attente principale, de sorte que vous pouvez le faire si vous le souhaitez, trop.Peu importe, ce qui illustre l'idée d'une propriété personnalisée avec ma propre réalisation bloc qui transmet les données appropriées. Vous pouvez répéter ce processus pour chacun des types d'opérations, vous pouvez ensuite la chaîne d'eux, tous ensemble, avec quelque chose comme:
Une autre approche dans les scénarios plus complexes est d'avoir votre
NSOperation
sous-classe employer une technique analogue à la façon dont la normeaddDependency
méthode fonctionne, dans lequelNSOperation
définit laisReady
état basé sur KVO surisFinished
sur l'autre opération. Non seulement cela vous permet non seulement de créer plus de dépendances complexes entre les opérations, mais aussi pour passer de la base de données entre eux. C'est probablement au-delà de la portée de cette question (et je suis déjà à la souffrance de tl:dr), mais laissez-moi savoir si vous avez besoin de plus ici.Je ne serais pas trop inquiet que
uploadImageToCreatedEntry
est en train d'envoyer au thread principal. En dessins compliqués, vous pouvez avoir toutes sortes de différentes files d'attente dédiée, pour certains types d'opérations, et le fait que l'INTERFACE utilisateur des mises à jour sont ajoutés à la file d'attente principale est parfaitement compatible avec ce mode. Mais au lieu dedispatch_async
j'ai peut-être enclins à utiliser l'NSOperationQueue
équivalent:Je me demande si vous avez besoin de toutes ces opérations. Par exemple, j'ai du mal à imaginer que
filename
est suffisamment complexe pour justifier son propre fonctionnement (mais si vous obtenez le nom de fichier à partir d'une certaine distance de la source, puis d'une opération distincte est parfaitement logique). Je vais supposer que vous êtes en train de faire quelque chose d'assez compliqué qui la justifie, mais les noms de ces opérations, je me demande, si.Si vous le souhaitez, vous voudrez peut-être jeter un oeil à couchdeveloper de
RXPromise
classe qui utilise promesses (a) le contrôle de la relation logique entre les différentes opérations; et (b) simplifier la transmission des données de l'un à l'autre. Mike Ash a un vieuxMAFuture
classe qui fait la même chose.Je ne suis pas sûr non plus de ceux qui sont assez matures que j'avais contempler les utiliser dans mon propre code, mais c'est une idée intéressante.
Je suis probablement totalement biaisée - mais pour une raison particulière, j'aime bien @Rob approche #6 😉
En supposant que vous avez créé appropriée des wrappers pour votre asynchrone des méthodes et des opérations qui en retour d'une Promesse à la place de la signalisation de la fin avec l'achèvement de bloc, la solution ressemble à ceci:
Et, si vous voulez annuler l'ensemble de l'asynchrone séquence à tout tine, n'importe où et n'importe comment à présent, il a été procédé:
(Une petite remarque: la propriété
root
n'est pas encore disponible dans la version actuelle de RXPromise, mais c'est en fait très simple à mettre en œuvre).Si vous voulez toujours utiliser NSOperation, vous pouvez compter sur ProcedureKit et l'utilisation de l'injection propriétés de la
Procedure
classe.Pour chaque opération, précisez de quel type il produit et de l'injecter à la prochaine opération dépendant. Vous pouvez aussi à la fin d'envelopper l'ensemble du processus à l'intérieur d'un
GroupProcedure
classe.