Reactivecocoa: Abonnement à deux signaux mais un ne s'exécute qu'une seule fois

Créé le 16 mars 2014  ·  11Commentaires  ·  Source: ReactiveCocoa/ReactiveCocoa

J'essaie de comprendre la meilleure façon de mettre en œuvre le scénario suivant :

Disons que j'ai deux signaux, l'un qui renvoie les données mises en cache et l'autre qui renvoie les données du réseau. Ce que je cherche à faire, c'est souscrire à un signal indiquant que les données "suivantes" sont immédiatement mises en cache, suivies de "suivantes" des données de la demande réseau. Ensuite, tout abonnement ultérieur au signal ne reçoit que des données du réseau, car les données mises en cache ne sont plus pertinentes.

J'ai essayé cela, mais j'aimerais savoir s'il existe une approche plus déclarative pour savoir que le signal de données mis en cache a été exécuté une fois.

Ceci illustre le scénario :

RACSignal *cachedSignal, *networkSignal;
...
RACSignal *signal = [RACSignal doSignal:cachedSignal count:1 followedBy:networkSignal];
[signal subscribeNext:...];
[signal subscribeNext:...];
[signal subscribeNext:...];

// outputs
// data from cache
// data from network
// data from network
// data from network

Et c'est l'implémentation de doSignal:count:followedBy:

+ (RACSignal *)doSignal:(RACSignal *)signal count:(NSUInteger)count followedBy:(RACSignal *)followedBy
{
    __block NSUInteger used = 0;

    return [[RACSignal if:[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@(used < count)];
        used++;

        return nil;
    }] then:[signal concat:followedBy] else:followedBy] setNameWithFormat:@"+doSignal:count:followedBy:"];
}
question

Commentaire le plus utile

-takeUntilReplacement: pourrait être ce que vous recherchez. Vérifie et tiens-moi au courant!

Tous les 11 commentaires

J'ai déjà pensé à ce même problème, mais mon approche était un peu différente. J'ai pensé qu'il serait plus propre de s'abonner uniquement au cache, puis de laisser le cache faire une requête réseau si nécessaire. Le cache s'abonnerait alors à l'appel réseau et se mettrait à jour. De cette façon, les nouvelles données du réseau se retrouveraient d'abord dans le cache. De cette façon, l'interface utilisateur, le ViewController ou le ViewModel n'auraient qu'à savoir comment communiquer avec un cache.

-takeUntilReplacement: pourrait être ce que vous recherchez. Vérifie et tiens-moi au courant!

Merci pour vos commentaires @KyleLeneau. L'approche que j'adopte est telle qu'elle est, car j'utilise MMRecord pour effectuer la requête réseau et mettre à jour le cache pour moi (il met à jour CoreData en coulisse), qui est tout enveloppé dans son propre signal. Le "scénario illustré" ci-dessus ne donne pas vraiment une image complète. Ce que j'ai vraiment en cours, c'est une méthode dans un gestionnaire de données qui renvoie un signal auquel mon ViewModel s'abonne. Cette méthode instancie les signaux de cache et de réseau, les combine en un seul signal (en utilisant doSignal:count:followedBy:) et renvoie ce signal à l'appelant. Donc, vraiment, cette méthode agit comme le "cache" dans votre situation, mais elle brise simplement l'acte réel de récupération du cache et d'exécution d'une demande réseau / d'une sauvegarde dans le cache.

L'objectif derrière cette méthode est que mon ViewModel s'y abonner, obtienne un ensemble initial de données pour remplir une table, puis lance immédiatement une demande réseau pour obtenir les dernières données. Plus tard, après qu'une certaine action se soit produite (rafraîchir la vue du tableau), le signal réagira en saisissant de nouvelles données et en les poussant sur le signal.

@Coneko Je vais jeter un œil maintenant pour voir si je peux faire fonctionner cela!

@Coneko -takeUntilReplacement: était exactement ce que je cherchais ! Merci :glace:

Super! Je vais continuer et fermer ceci, mais n'hésitez pas à l'ouvrir à nouveau si quelque chose d'autre lié à cela se présente. Bien sûr, si quelque chose sans rapport avec cela survient, vous pouvez toujours ouvrir un nouveau problème.

En fait, je viens de le tester et cela ne fonctionne pas, et c'est probablement parce que j'ai une mauvaise compréhension fondamentale de RAC. Peut-être pourriez-vous aider. J'ai développé l'exemple ci-dessous :

Ici, nous configurons le signal en utilisant takeUntilReplacement:

RACSignal *cachedSignal, *networkSignal;
...
RACSignal *signal = [cachedSignal takeUntilReplacement:networkSignal];

Ensuite, immédiatement après, j'ai configuré une liaison sur self.messages. Il est déclenché par le changement de valeur de self.refresh. Quand c'est le cas, je mappe le signal.

RAC(self, messages) = [[RACObserve(self, refresh) map:^id(id value) {
    return signal;
}] switchToLatest];

[RACObserve(self, messages) subscribeNext:^(NSArray *messages) {
    NSLog(@"Update the interface with new messages: %@", messages);
}];

À ce stade, le signal est souscrit et il est déclenché immédiatement. La console produit :

Update the interface with new messages: (null)
Update the interface with new messages: [ @"array from cache" ]
...a few seconds later when the network request finishes...
Update the interface with new messages: [ <strong i="14">@updated</strong> array" ]

Pour démontrer le problème, j'ai à nouveau invoqué le signal en définissant la valeur d'actualisation sur YES

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    self.refresh = YES;
});

Ce qui n'a pas donné les résultats que j'attendais. Ce que j'ai obtenu, c'est un retour initial du cache puis la demande de réseau.

Update the interface with new messages: [ @"array from cache" ]
...a few seconds later when the network request finishes...
Update the interface with new messages: [ <strong i="22">@updated</strong> array" ]

À ce stade, je souhaite uniquement que le signal réseau soit déclenché et ne produise que :

...a few seconds later when the network request finishes...
Update the interface with new messages: [ <strong i="26">@updated</strong> array" ]

Cela fonctionne correctement avec ma méthode -doSignal:count:followedBy: mais cela ne me convient pas. J'essaie juste de comprendre pourquoi -takeUntilReplacement: ne se souvient pas de son état précédent lorsqu'il est à nouveau déclenché. Je veux dire par là pourquoi le cachedSignal est-il à nouveau déclenché ?

Parce que les signaux dans RAC sont généralement froids : cela signifie qu'ils ne font rien tant que vous ne vous y abonnez pas, et ils font ce qu'ils font à chaque fois que vous vous y abonnez. Ils sont apatrides par conception.

Il suffit de les combiner différemment, par exemple :

RAC(self, messages) = [[[RACObserve(self, refresh) mapReplace:cachedSignal]
    takeUntilReplacement:[RACObserve(self, refresh) mapReplace:networkSignal]]
    swithToLatest];

Cela ne semble pas fonctionner pour moi. La réponse que j'obtiens est :

Update the interface with new messages: (null)
...a few seconds later when the network request finishes...
Update the interface with new messages: [ <strong i="6">@updated</strong> array" ]

Il semble que le signal mis en cache soit immédiatement remplacé par le signal réseau. De plus, les actualisations ultérieures ne provoquent rien d'autre, aucune demande réseau ou mise en cache, rien.

De plus, cette approche semble coupler très étroitement l'appelant à la mise en œuvre de l'accès aux données. N'est-il pas forcément possible (ou conseillé) d'avoir un signal qui enveloppe deux signaux et sache dans quelles conditions invoquer l'un, l'autre, ou les deux ?

Considérez qu'il s'agit d'une méthode dans certains gestionnaires de données :

- (RACSignal)messagesSignal
{
    RACSignal *cachedSignal, *networkSignal;
    ...
    // use the cache signal until the network signal sends next. then any subsequent subscriptions SHOULD only use the network signal.
    return [cachedSignal takeUntilReplacement:networkSignal];
}

Ensuite, mon viewController (pour plus de clarté) invoquera cette méthode pour obtenir un nouveau signal et s'y abonner à sa guise :

- (void)viewDidLoad
{
    RAC(self, messages) = [[RACObserve(self, refresh) 
        mapReplace:[DataManager messagesSignal]] 
        switchToLatest];

    [RACObserve(self, messages) subscribeNext:^(NSArray *messages) {
        NSLog(@"Update the interface with new messages: %@", messages);
    }];
}

Ce type d'approche est-il erroné dans le monde RAC ? Je semble juste beaucoup plus propre du plus haut niveau pour pouvoir souscrire à un seul signal et laisser les détails de mise en œuvre de savoir quand utiliser un signal de mise en cache par rapport à un signal de réseau à quelque chose de plus bas.

Cela ne semble pas fonctionner pour moi.

Oui désolé, ce que j'ai écrit là-bas était faux, cela aurait dû être :

RACSignal *refreshFromCacheSignal = [[RACObserve(self, refresh)
    map:^(id value) {
        return cachedSignal;
    }]
    switchToLatest];

RACSignal *refreshFromNetworkSignal = [[RACObserve(self, refresh)
    map:^(id value) {
        return networkSignal;
    }]
    switchToLatest];

RAC(self, messages) = [refreshFromCacheSignal
    takeUntilReplacement:refreshFromNetworkSignal];

Fondamentalement, le -takeUntilReplacement: doit aller à la toute fin, même après le -switchToLatest .

Si vous souhaitez afficher les données mises en cache immédiatement et que les actualisations sont effectuées pour obtenir les données réseau en contournant le cache, c'est beaucoup plus propre :

RACSignal *refreshFromNetworkSignal = [[RACObserve(self, refresh)
    map:^(id value) {
        return networkSignal;
    }]
    switchToLatest];

RAC(self, messages) = [cachedSignal
    takeUntilReplacement:refreshFromNetworkSignal];

pas parce que c'est nécessairement une façon plus propre de le faire en général, mais parce que -takeUntilReplacement: été fait pour ce genre de motif. Cela ressemble également plus à ce que vous décrivez dans votre message.

Ce type d'approche est-il erroné dans le monde RAC ?

En général, les signaux froids sont les meilleurs, nous n'aimons donc pas introduire l'état dans nos signaux. Je préférerais l' approche de

Je vois! Cela a vraiment aidé à faire la lumière sur les concepts sous-jacents de RAC. J'ai choisi de laisser mon modèle de vue s'occuper de l'observation d'une propriété sur elle-même (self.refresh):

RACSignal *refreshedMessages = [[RACObserve(self, refresh)
        mapReplace:[MessagesModel networkSignal]]
        switchToLatest];

RAC(self, messages) = [[MessagesModel cachedSignal]
    takeUntilReplacement:refreshedMessages];

Ensuite, à mon avis contrôleur:

[RACObserve(self.viewModel, messages) subscribeNext:^(NSArray *messages) {
    NSLog(@"Update the interface with new messages: %@", messages);
}];
...
[self.viewModel refreshMessages];

Merci pour votre aide @Coneko et @KyleLeneau !

Pas de problème, content d'avoir pu t'aider !

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

dougmcbride picture dougmcbride  ·  3Commentaires

danishin picture danishin  ·  4Commentaires

v-silin picture v-silin  ·  4Commentaires

gabro picture gabro  ·  5Commentaires

toddbluhm picture toddbluhm  ·  5Commentaires