1
votes

Comment implémenter un opérateur switchMap approprié dans Combine?

Prenons l'exemple suivant:

    cancellable = Just(2).map { x in
        Just(x * x).delay(for: 2.0, scheduler: RunLoop.main)
    }
    .switchToLatest()
    .sink(receiveCompletion: {_ in
        print("completed")
    }, receiveValue: {result in
        print(result)
    })

Ici, j'essaie d'imiter le comportement du célèbre opérateur switchMap en utilisant les opérateurs Combine. Je m'attends à obtenir le résultat après deux secondes et l'achèvement. En réalité, ni résultat ni achèvement ne viennent. Ce qui est très mauvais car l'amont a été terminé!

On dirait que switchToLatest s'annule dès que l'amont se termine et oublie de se terminer. D'un autre côté, si je le remplace par un flatMap , tout fonctionne comme prévu.

Y a-t-il de bons exemples d'un opérateur switchMap approprié?

Clause de non-responsabilité: Eh bien, je comprends mon achèvement en amont. Bien que je souhaite que mon switchMap fonctionne indépendamment du fait que mon amont soit terminé avant l'éditeur interne ou après.


3 commentaires

Yuck semble être un autre bug dans Combine. J'ai eu du mal avec une combinaison de .receive (on: queue) .combineLatest (Just (2)) . Reste silencieux comme dans votre exemple, et flatMap fonctionne très bien.


Merci d'avoir posé cette question; cela m'a juste mordu aussi, donc je suis heureux de découvrir que je ne suis pas seul.


Le bogue est corrigé dans Xcode 11.4


3 Réponses :


0
votes

Ne vouliez-vous pas dire pour ce qui suit (du moins, pour autant que je sache, cela aboutit à ce que vous semblez attendre)?

let cancellable = Just(2)
    .map { x in
        Just(x * x)
    }
    .delay(for: 2.0, scheduler: RunLoop.main)
    .switchToLatest()
    .sink(receiveCompletion: {_ in
        print("completed")
    }, receiveValue: {result in
        print(result)
    })


2 commentaires

Désolé, ce n'est pas ce que je voulais dire. Je voulais montrer que l'exemple suivant ne génère pas de sorties et d'événements terminés, ce qui est une erreur en soi. De plus, dans votre exemple, vous avez retardé la sortie d'une carte tandis que j'ai retardé l'observable interne de la carte, ce qui a causé le comportement en question.


Pour rendre les choses encore plus simples: je pense que publisher.map (transform) .switchToLatest () devrait générer des événements même si l'éditeur termine avant l'éditeur interne.



0
votes

Je pense que c'est un bug. Essayez plutôt d'utiliser flatMap , qui fonctionne comme prévu.

let cancellable = Just(2)
.flatMap { (x) in
    Just(x * x)
        .delay(for: 2.0, scheduler: RunLoop.main)
}
.sink(receiveCompletion: {_ in
    print("completed")
}, receiveValue: {result in
    print(result)
})


4 commentaires

C'est vrai, sauf que flatMap n'est pas ce que je veux :-) Je veux un switchMap en fait. Imaginez que j'ai un flux au lieu de Just () et que je souhaite annuler les abonnements précédents lorsqu'il émet. Eh bien, j'ai déjà implémenté un opérateur personnalisé en le faisant donc je le posterai probablement plus tard.


@norekhov s'il vous plaît postez votre solution, j'aimerais en tirer des leçons.


@norekhov btw il n'y a vraiment pas beaucoup de documents disponibles pour être honnête, je suis celui-ci non officiel: heckj.github.io/swiftui-notes/... . D'après ce qu'il dit, il n'y a aucune différence entre .map (). SwitchToLatest () et .flatMap () . Vous venez peut-être de ReactiveCocoa?


Vous pouvez vérifier ma solution ici: github.com/tcldr/Entwine/issues/16 . Il sera peut-être inclus dans Entwine ou l'auteur suggérera une autre solution de contournement. Ce n'est en fait que switchToLatestWaitable mais il peut être combiné avec map pour former un switchMap approprié



0
votes

En effet, switchToLatest est probablement ce que vous recherchez. Le problème est qu'il est buggé. Ou du moins c'était; le bogue est corrigé dans Xcode 11.4 (actuellement en version bêta) et maintenant switchToLatest se comporte exactement comme on s'y attendrait. Votre code fonctionne désormais correctement:

    Just(2).map { x in
        Just(x * x).delay(for: 2.0, scheduler: RunLoop.main)
    }
    .switchToLatest()
    .sink(receiveCompletion: {_ in
        print("completed")
    }, receiveValue: {result in
        print(result)
    }).store(in:&storage)  // 4, completed


2 commentaires

Lorsque vous dites que cela est corrigé dans Xcode 11.4, cela signifie-t-il que tous les appareils exécutant iOS 13.3 ou une version antérieure seront affectés par ce bogue? Je suppose que la version de Combine qui est livrée avec ces systèmes d'exploitation est boguée, donc que le binaire ait été construit avec Xcode 11.4 ou non ne fait pas de différence, êtes-vous d'accord?


@Rog C'est ce que je pense que cela signifie, oui. Fondamentalement, cette fonctionnalité de Combine n'est devenue utilisable qu'avec iOS 13.4. C'est dommage, mais c'est ce que je pense arrivé. Heureusement, il est extrêmement improbable qu'un utilisateur avec iOS 13 ne se soit pas mis à jour vers iOS 13.4 ou une version ultérieure. - Mais vous pouvez tester cela par vous-même, il n'y a pas besoin de spéculer.