0

How do I leverage ReactiveX to execute async calls in sequence? I.e., execute a second call after first one has finished.

More specifically, I'm working with RxSwift in iOS, and the asyncs I want to chain together are UIView animations (instead of calling the second animation inside the completion block of the first one).

I know I have other options like Easy Animation, but I'd like to leverage Rx, since I'm already using it for streams.

Also, one solution would be (for 3 chained animations):

_ = UIView.animate(duration: 0.2, animations: {
        sender.transform = CGAffineTransformMakeScale(1.8, 1.8)
    })
    .flatMap({ _ in
        return UIView.animate(duration: 0.2, animations: {
            sender.transform = CGAffineTransformMakeScale(0.8, 0.8)
        })
    })
    .flatMap({ _ in
        return UIView.animate(duration: 0.2, animations: {
            sender.transform = CGAffineTransformIdentity
        })
    })
    .subscribeNext({ _ in })

But I'm looking for something more elegant, the right way of doing it with Rx.

solidcell
  • 7,639
  • 4
  • 40
  • 59
Rodrigo Ruiz
  • 4,248
  • 6
  • 43
  • 75
  • What's up with your code example? Did you mean for it to be pseudo code? You're calling `flatMap` on `Void`, not a `SequenceType` or an `Observable`. Also, it's `animateWithDuration:animations:`, not `animate:animations:`. I'm not sure what you were intending by including that code. – solidcell Aug 10 '16 at 08:00
  • I created that `animate:...` method to return an observable, so no, not pseudocode – Rodrigo Ruiz Aug 10 '16 at 18:01

2 Answers2

2

I don't think using Rx makes it much cleaner, but here's how you could do it:

let animation1 = Observable<Void>.create { observer in
    UIView.animateWithDuration(0.2,
        animations: {
            // do your animations
        }, completion: { _ in
            observer.onCompleted()
        })
    return NopDisposable.instance
}

let animation2 = Observable<Void>.create { observer in
    UIView.animateWithDuration(0.2,
        animations: {
            // do your animations
        }, completion: { _ in
            observer.onCompleted()
        })
    return NopDisposable.instance
}

Observable.of(animation1, animation2)
    .concat()
    .subscribe()
    .addDisposableTo(disposeBag)

It would also be cleaner if you create a function to construct the Observable<Void>s for you.

func animation(duration: NSTimeInterval, animations: () -> Void) -> Observable<Void> {
    return Observable<Void>.create { observer in
        UIView.animateWithDuration(duration,
            animations: animations,
            completion: { _ in
                observer.onCompleted()
            })
        return NopDisposable.instance
    }

I guess a plus side to using Rx instead of just animateWithDuration:animations: chained, is that you don't have to nest the animations in completion blocks. This way, you can just define them on their own and compose them as you want afterward.

As an alternative to RxSwift, check out PromiseKit. RxSwift is a bit overkill for your animation callback needs. This blog post in particular is relevant.

solidcell
  • 7,639
  • 4
  • 40
  • 59
  • won't `Observable.of(animation1, animation2).concat()` run them in parallel? – Rodrigo Ruiz Aug 10 '16 at 18:03
  • and would you use something else besides `Rx` doe this? And that is exactly my problem, I don't want to have nested callbacks. – Rodrigo Ruiz Aug 10 '16 at 18:03
  • Also, why do I need the `.addDisposableTo(disposeBag)`? shouldn't the observer go away by itself after completed? – Rodrigo Ruiz Aug 10 '16 at 22:10
  • concat will run the Observables consecutively, only moving on to the next one once the current one has sent a Completed event. I'm sure there's a library more suited to animation control, however I haven't searched. This does avoid (coupled) nested callbacks, so if that's all you're concerned about, then it's fine. You still need the addDisposableTo call so that the Observable is disposed of properly when disposeBag is deallocated (so, the view controller or whoever owns it, presumably). – solidcell Aug 11 '16 at 07:10
  • But wouldn't the subscription get disposed by itself when the last one is completed? – Rodrigo Ruiz Aug 12 '16 at 22:54
  • And when I said "would you use anything else" I meant for any callback based functions. Would you just use Rx or something else? (not necessarily for animations). – Rodrigo Ruiz Aug 12 '16 at 22:55
  • As mentioned here: https://github.com/ReactiveX/RxSwift/blob/4b602fd88e5e6035931d90f4a80492a7e41741ca/Documentation/GettingStarted.md#observables-aka-sequences You don't have to use a DisposeBag, but if the Observable never completes on its own, you will never get those resources deallocated. Even if the Observer completes on its own, you'll still be wasting resources until it completes on its own, even though you don't need it anymore. Also, some Observables might want to do extra cleanup work upon completion, like cancelling a network call, so you'll be missing out on stuff like that. – solidcell Aug 14 '16 at 20:31
  • Yea, I would suggest you take a look at `PromiseKit` for managing callbacks. Rx seems to be a bit of overkill if that's all you want, as evidenced by the fact that you don't need any events or operators. In particular, check out this blog post: http://promisekit.org/docs/cookbook/promises-and-ux/ – solidcell Aug 14 '16 at 20:38
1

Maybe its too late for @Rodrigo Ruiz, but I'm sure it might help other developers that happen to come across this post (like I did).

RxAnimated is available for free on GitHub: https://github.com/RxSwiftCommunity/RxAnimated

You can get started using this blog post

A small discloser - I'm not connected to this project or post by any way - I just found it while searching for a reactive solution for my animations :)

goldengil
  • 1,007
  • 9
  • 25