How do I sequentially loop an observable in RxSwift? - swift

I am trying to create a stream that polls a network service. At the moment it queries the service then completes after a short delay. I'd like the onward stream to restart rather than completing thereby polling the service forever.
You could do something like ...
myPollingStream.repeat()
But repeat in RxSwift is actually repeatElement and so actually generates a stream of observables. You could possibly concatMap these into a flattened serial sequence but RxSwift does not have the concatMap operator.
So how do I loop an observable in RxSwift?
I'd like the requests to be sequential, not concurrent so flatMap is not an option since it merges streams leading to overlapping requests. I'm looking for something similar to how retry() works but restarting onComplete not onError

Observable.repeatElement(myPollingStream, scheduler: MainScheduler.instance).concat()
repeatElement(_:scheduler:) will create an infinite stream of polling queries.
contat() will then make sure each polling query is completed before subscribing to the next.
Attention
While the above works in theory, without a backpressure implemetation, repeatElements(_:scheduler:) will emit events until you eventually run out of memory. This makes this solution not viable as of RxSwift 3.0. More details can be found in this issue on RxSwift repository.

Option 1: Recursive function
Your myPollingStream:
func myPollingStream() -> Observable<Result> {
return Observable<String>.create { observer in
// your network code here
return Disposables.create()
}
}
Then you create a a recursive function:
func callMyPollingStream() {
myPollingStream()
.subscribe(onNext: { result in
callMyPollingStream() // when onNext or onCompleted, call it again
})
.addDisposableTo(db)
}
Option 2: Use interval
let _ = Observable<Int>
.interval(5, scheduler: MainScheduler.instance)
.subscribe(onNext: { _ in
let _ = myPollingStream().subscribe()
})
.addDisposableTo(db)
With this option, myPollingStream() function will be called every 5 seconds.

Related

SwiftUI Combine synchronously execute codes

I call an API and perform some actions based on the response.
let test = apiPublisher
.subscribe(...)
.receive(...)
.share()
test
.sink {
//do task1
}.store(...)
test
.sink {
//do task2
}.store(...)
test
.sink {
//do task3
}.store(...)
Now how can I execute the task1, task2, task3 one after another. I know I can have all the code in one sink block. For code readability I'm using the share() operator.
Code put in sinks need to be independent. If you want them to depend on each other (one should not start until the other finishes) then you can't put them in sinks.
You will have to put each task in its own Publisher. That way the system will know when each is finished and you can concat them.
test.task1
.append(test.task2)
.append(test.task3)
.sink { }
.store(...)
I'm assuming that each task needs something from test in order to perform its side effect. Also each task needs to emit a Void event before completing.

How to add a timeout to an awaiting function call

What's the best way to add a timeout to an awaiting function?
Example:
/// lets pretend this is in a library that I'm using and I can't mess with the guts of this thing
func fetchSomething() async -> Thing? {
// fetches something
}
// if fetchSomething() never returns then doSomethingElse() is never ran. Is there anyway to add a timeout to this system?
let thing = await fetchSomething()
doSomethingElse()
I wanted to make the system more robust in the case that fetchSomething() never returns. If this was using combine, I'd use the timeout operator.
One can create a Task, and then cancel it if it has not finished in a certain period of time. E.g., launch two tasks in parallel:
// cancel the fetch after 2 seconds
func fetchSomethingWithTimeout() async throws -> Thing {
let fetchTask = Task {
try await fetchSomething()
}
let timeoutTask = Task {
try await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC)
fetchTask.cancel()
}
let result = try await fetchTask.value
timeoutTask.cancel()
return result
}
// here is a random mockup that will take between 1 and 3 seconds to finish
func fetchSomething() async throws -> Thing {
let duration: TimeInterval = .random(in: 1...3)
try await Task.sleep(nanoseconds: UInt64(TimeInterval(NSEC_PER_SEC) * duration))
return Thing()
}
If the fetchTask finishes first, it will reach the timeoutTask.cancel and stop it. If timeoutTask finishes first, it will cancel the fetchTask.
Obviously, this rests upon the implementation of fetchTask. It should not only detect the cancelation, but also throw an error (likely a CancellationError) if it was canceled. We cannot comment further without details regarding the implementation of fetchTask.
For example, in the above example, rather than returning an optional Thing?, I would instead return Thing, but have it throw an error if it was canceled.
I hesitate to mention it, but while the above assumes that fetchSomething was well-behaved (i.e., cancelable), there are permutations on the pattern that work even if it does not (i.e., run doSomethingElse in some reasonable timetable even if fetchSomething “never returns”).
But this is an inherently unstable situation, as the resources used by fetchSomething cannot be recovered until it finishes. Swift does not offer preemptive cancelation, so while we can easily solve the tactical issue of making sure that doSomethingElse eventually runs, if fetchSomething might never finish in some reasonable timetable, you have deeper problem.
You really should find a rendition of fetchSomething that is cancelable, if it is not already.
// You can use 'try and catch'. Wait for the fetch data inside the try block. When it fails the catch block can run a different statement. Something like this:
await getResource()
try {
await fetchData();
} catch(err){
doSomethingElse();
}
// program continues

Swift 5.5, when to use `Task.suspend` in custom async implementation?

The new Async/Await syntax looks great! but I wonder how to implement my own asynchronous implementation.
I've stumbled upon this API:
https://developer.apple.com/documentation/swift/task/3862702-suspend (overview in yield)
https://developer.apple.com/documentation/swift/task/3814840-yield (renamed to suspend)
This API allows me to suspend a task manually whenever I choose. The problem is, I'm am not sure how SHOULD I do it, in order to benefit from concurrency AND not avoid bad practices.
In other word, I don't know the best practices of Task.suspend()
for example:
func example() async {
for i in 0..<100 {
print("example", i)
await Task.suspend() // <-- is this OK?
}
}
Some specific questions:
how often should one call on suspend?
should suspend be called before an intensive operation, or after? (for example: IO, Crypto, etc...)
should there be a maximum amount of calls to suspend?
what is the "price" of calling suspend intensively?
when should one NOT call suspend?
are there any other ways to implement this kind of concurrency (async/await style, not GCD)
Real life example, I'm implementing a function that encrypts the content of a big file, since it is an IO+Crypto intensive task it should be async, I wonder how to use Task.suspend (or any other async/await tools) to make it asynchronous.
Calling Task.suspend() will suspend the current task for a few milliseconds in order to give some time to any tasks that might be waiting, which is particularly important if you’re doing intensive work in a loop and all your tasks use the same priority. Otherwise your heavy task can stop all asynchronous code in your app. For instance:
func f() async {
for _ in 0...10 {
var arr = (1...10000).map {_ in arc4random()}
arr.sort()
}
print("f")
}
func z() async {
print("z")
}
// Run in parallel
Task {
await f()
}
Task {
await z()
}
Outputs:
f
z
As you can see z() waits for f() because it does long-running operation of sorting a large array many times. To fix this you can add Task.suspend() in your loop:
func f() async {
for _ in 0...10 {
var arr = (1...10000).map {_ in arc4random()}
arr.sort()
await Task.suspend() // Voluntarily suspend itself
}
print("f")
}
Outputs:
z
f
async/await works on its own cooperative concurrent queues and if you don't want to do suspending consider moving your task to non-default priority(queue) e.g. Task(priority: .background) or run your heavy task on your separate queue.

RxSwift how to have two Observables be bound for completion together?

I have two Observables and I want them both to terminate with completion event when either of them is completed. They both branch from the same sequence, but have different termination condition:
.filter.take(1)
.distinctUntilChanged.take(2)
How can I have two Observables complete together when either one completes?
I think you need to manage subscriptions manually, here is an example:
var disposable1: Disposable?
var disposable2: Disposable?
disposable1 = observable.filter().take(1).subscribe(onDisposed: {
disposable2?.dispose()
})
disposable2 = observable.distinctUntilChanged().take(2).subscribe(onDisposed: {
disposable1?.dispose()
})

How can I apply a grace time using RX?

I have an Observable<Bool> that emits true when an operation begins and false when it ends. I'd like to show a message while the operation is in progress, but only if it takes longer than two seconds to begin. Is there a way I can create an observable that I can bind my message to? Any help much appreciated!
If you switchMap (a flatMap where when a second item is emitted from the source the subscription to the original observable is unsubscribed and the subscription moves to the next) you could do something like this:
booleanObservable
.switchMap ( map true to an observable timer of 2 seconds, map false to an empty observable)
.onNext show your message (next won't fire for the empty and a
quick response would have cut off the 2 second timer).
Note switchMap is 'switchLatest' in RxSwift.
Could become something like this:
booleanObservable
.map { inProgress -> Observable<Bool> in
if inProgress {
return Observable.just(true).delay(time: 2)
} else {
return Observable.just(false)
}
}
.switchLatest()