RxSwift subscribeOn and observeOn not on the expected background Thread - rx-swift

I have the following scheduler:
let scheduler = ConcurrentDispatchQueueScheduler(queue: .global(qos: .background))
tried with: observeOn(scheduler)
also with: subscribeOn(scheduler)
I'm expecting that the callback from the subscribe would be executed on some the background thread, because of the scheduler.
func signIn(withExternalUserID userID: UserID, authenticationToken: String) -> Single<QBUUser> {
let authenticationToken = "Bearer \(authenticationToken)"
return .create { observable in
let request = QBRequest.logIn(
withUserLogin: "\(userID)",
password: authenticationToken,
successBlock: { _, user in
user.password = QBSession.current.sessionDetails?.token
observable(.success(user)) // <- Here we are on the main thread
}, errorBlock: { [unowned self] in
observable(.error(ChatError.signInError(self.resolve(errorResponse: $0)))) // <- same here: main thread
}
)
return Disposables.create {
request.cancel()
}
}
}
Because the callbacks of the internal API are executed in the main thread, all my subcribes instead of background thread are also executed on the main thread.
tokenRequest
.flatMap { [unowned self] token -> Single<QBUUser> in
return self.chatService.signIn(withExternalUserID: currentUser.id, authenticationToken: token)
}
.flatMap { [unowned self] user -> Single<QBUUser> in
return self.chatService.connect(user: user).andThen(Single.just(user))
}
.observeOn(self.scheduler)
.subscribe(onNext: { [unowned self] user in
self.chatStorage.set(currentQBUser: user, currentUser: currentUser)
completable(.completed)
self.loginSempahore.signal()
}, onError: { [unowned self] error in
self.chatStorage.unsetCurrentQBUser()
completable(.error(error))
self.loginSempahore.signal()
})
.disposed(by: self.disposeBag)
What is the correct way to force all callbacks from flatMap, and subscribe being executed by background thread?

There is no way to force a function to call its callback on a particular thread unless the library gives you the option to do it. Your successBlock and errorBlock closures will be called on whatever thread QBRequest.logIn wants to call them on and there's nothing you can do about that.
That said, the .observeOn(_:) operator will transfer the execution to a different thread so in your last code example, your onNext and onError closures will be executed on the self.scheduler thread.

Related

What's the best pattern for exception handling when using coroutines in kotlinjs?

I have a kotlinjs app. I handle a particular event (dropping of data onto a component) like this:
onEvent {
drop = { event ->
GlobalScope.async {
//...
dropTask(y, data)
}
}
}
// ...
// this function has to be a suspend function because model's is
private suspend fun dropTask(y: Int, taskId: TaskId) {
// ... prepare data
model.insertBefore(taskId!!, insertBefore?.id)
}
// ... Model's function is defined like this:
suspend fun insertBefore(taskToInsert: TaskId, taskBefore: TaskId?) {
val (src, _) = memory.find(taskToInsert)
// ... and finally, the find function is:
fun find(taskId: TaskId): Pair<Task?, Int> {
// ...
return if (task != null) {
// ...
} else {
throw Exception("Couldn't find task with id $taskId!!")
}
}
The issue is that the Exception gets thrown, but isn't reported anywhere.
I have tried:
a) Installing a CoroutineExceptionHandler into the GlobalScope.async (i.e.:
val handler = CoroutineExceptionHandler { _, e ->
console.log("Caught exception: $e")
}
GlobalScope.async(handler) {
...but this never gets called. This would be relatively clean if I could make it work. It would be even nicer if this was default behavior for kotlinjs, so that exceptions weren't accidentally unreported.
b) Calling await:
drop = { event ->
GlobalScope.launch {
GlobalScope.async() {
// ...
dropTask(y, data)
}.await()
}
}
This does result in the exception being logged to the console, but it's so ugly. It's not possible to call .await() outside of a suspend function or coroutine, so for this particular event handler I have to wrap the async call in a launch. I must be doing something wrong. Anybody have a better pattern that I should be using?

RxSwift form validation and sending request in one stream

I have a case where I would like to validate form and then if everything is ok go to api request.
I've written some code and it works fine but errors dispose my stream. I know I could add .catch error at the end of flat map but then next flat map would be executed.
Can I add catch error at the end of stream without disposing it? Or the only way to deal with it is separate it to two streams validation and server responses?
enum Response {
case error(message: String)
case success
}
let start = input.validate
.withLatestFrom(input.textFields)
.flatMap { [unowned self] fields -> Observable<String> in
return self.validate(characters: fields)
}
.flatMapLatest { [unowned self] code -> Observable<String> in
return self.apiClient.rxSendData(code)
.retry(1)
}
.map { _ in return Response.success }
.asDriver { Driver.just(Response.error(message: $0.localizedDescription)) }
I'm making some assumptions about code you aren't showing. Your validate function is especially odd to me. It looks like it emits a String (which is ignored, if validation was successful and doesn't emit anything (or maybe an error) if validation failed?
let start = input.validate
.withLatestFrom(input.textFields)
.flatMapLatest { [unowned self] fields -> Observable<String> in
return self.validate(characters: fields)
.catchError { _ in Observable.empty() } // empty() doesn't emit a value so the next flatMap won't be executed.
}
.flatMapLatest { [unowned self] _ -> Observable<Response> in
return self.apiClient.rxSendData()
.retry(1)
.map { _ in Response.success }
.catchError { error in Observable.just(Response.error(message: error.localizedDescription)) }
}
.asDriver { Driver.just(Response.error(message: $0.localizedDescription)) }
If validate emits an error when validation fails, and you want to capture that error, then something like this would work:
let start = input.validate
.withLatestFrom(input.textFields)
.flatMapLatest { [unowned self] fields -> Observable<Response> in
return self.validate(characters: fields)
.map { _ in Response.success }
.catchError { Observable.just(Response.error(message: $0.localizedDescription)) }
}
.flatMapLatest { [unowned self] validation -> Observable<Response> in
// here, the above flatMap emits a value no matter what, so we have to switch on it to determine if we want to continue or just push the Response down the pipe.
switch validation {
case .error:
return Observable.just(validation)
case .success:
return self.apiClient.rxSendData()
.retry(1)
.map { _ in Response.success }
.catchError { error in Observable.just(Response.error(message: error.localizedDescription)) }
}
}
.asDriver { Driver.just(Response.error(message: $0.localizedDescription)) }
Have you considered the materialize operator? It converts an observable sequence into an observable sequence of event objects detailing what happened that cannot error but completes when the input sequence completes. You can then share that.
Something like:
let code = input.validate
.withLatestFrom(input.textFields)
.flatMap { [unowned self] fields -> Observable<String> in
self.validate(characters: fields)
.materialize()
}
.share(replay: 1)
code
.compactMap { $0.error }
.subscribe() // Show error from `self.validate`
.disposed(by: bag)
let request = code
.compactMap { $0.element }
// Will get to this flat map only if `self.validate` did not error
.flatMapLatest { [unowned self] code -> Observable<String> in
self.apiClient.rxSendData(code)
.retry(1)
.materialize()
}
.share(replay: 1)
request
.compactMap { $0.error }
.subscribe() // Show error from `self.apiClient.rxSendData`
.disposed(by: bag)
request
.compactMap { $0.element }
// Do something as a result of the request being successful
The chains would not cease upon self.validate and self.apiClient.rxSendData emitting errors.

RxSwift: Calling onCompleted after onNext delivers only the completed event

I'm wrapping some legacy completion-block code in an Observable. It will emit one event (next or error), and then complete. The problem is that calling onNext(), onCompleted() only sends the completed event to the observer. Why doesn't the next event get delivered?
UPDATE: The people stream actually works as expected. The issue turns out to be in the next stream, filteredPeople. The inner completed event is passed along to it, and I'm just returning it, which terminates the stream.
I need to filter out completed events from inner streams.
let people = Observable<Event<[Person]>>()
.flatMapLatest {
return fetchPeople().asObservable().materialize()
}
.share()
// this is bound to a search field
let filterText = PublishSubject<String>()
let filteredPeople = Observable.combineLatest(people, filterText) { peopleEvent, filter in
// this is the problem. the completed event from people is being returned, and it terminates the stream
guard let people = peopleEvent.element else { return peopleEvent }
if filterText.isEmpty { return .next(people) }
return .next(people.filter { ... })
}
func fetchPeople() -> Single<[Person]> {
return Single<[Person]>.create { observer in
PeopleService.fetch { result in
switch result {
case .success(let people):
observer(.success(people))
case .failure(let error):
observer(.error(error))
}
}
return Disposables.create()
}
}
filteredPeople.subscribe(
onNext: { event in
// ?! doesn't get called
},
onCompleted: {
// we get here, but why?
},
onError: {event in
...
}).disposed(by: disposeBag)
You haven't posted the code that is causing the problem. The code below works as expected:
struct Person { }
class PeopleService {
static func fetch(_ result: #escaping (Result<[Person], Error>) -> Void) {
result(.success([]))
}
}
let disposeBag = DisposeBag()
func fetchPeople() -> Single<[Person]> {
return Single<[Person]>.create { observer in
PeopleService.fetch { result in
switch result {
case .success(let people):
observer(.success(people))
case .failure(let error):
observer(.error(error))
}
}
return Disposables.create()
}
}
let people = Observable<Void>.just(())
.flatMapLatest { _ in
return fetchPeople().asObservable().materialize()
}
.share()
people.subscribe(
onNext: { event in
print("onNext does get called")
print("in fact, it will get called twice, once with a .next(.next([Person])) event")
print("and once with a .next(.completed) event.")
},
onCompleted: {
print("this prints after onNext gets called")
})
.disposed(by: disposeBag)
I fixed it by filtering out completed events from the inner stream. I am not sure this is the right way, but I can't think of a better solution.
let people = Observable<Event<[Person]>>()
.flatMapLatest {
return fetchPeople()
.asObservable()
.materialize()
// Our work is done, but don't end the parent stream
.filter { !$0.isCompleted }
}
.share()

RxSwift multiple observable in map

I ran into a situation where I would fetch an API which will generate json data of registered users. I would then have to loop through each user and fetch their avatar from remote url and save it to disk. I can perform this second task inside subscribe but this is not a best practice. I am trying to implement it with map, flatMap etc.
Here is my sample code:
self.dataManager.getUsers()
.observeOn(MainScheduler.instance)
.subscribeOn(globalScheduler)
.map{ [unowned self] (data) -> Users in
var users = data
// other code for manipulating users goes here
// then below I am trying to use another loop to fetch their avatars
if let cats = users.categories {
for cat in cats {
if let profiles = cat.profiles {
for profile in profiles {
if let thumbnail = profile.thumbnail,
let url = URL(string: thumbnail) {
URLSession.shared.rx.response(request: URLRequest(url: url))
.subscribeOn(MainScheduler.instance)
.subscribe(onNext: { response in
// Update Image
if let img = UIImage(data: response.data) {
try? Disk.save(img, to: .caches, as: url.lastPathComponent)
}
}, onError: { (error) in
}).disposed(by: self.disposeBag)
}
}
}
}
}
return users
}
.subscribe(onSuccess: { [weak self] (users) in
}).disposed(by: disposeBag)
There are 2 problems in this code. First is with the rx on URLSession which execute the task in background on another thread and there is no way to acknowledge the main subscribe back when this operation will finish. Second is with the loop and rx which is not efficient as it should generate multiple observables and then process it.
Any idea to improve this logic is welcome.
This was a fun puzzle.
The "special sauce" that solves the problem is in this line:
.flatMap {
Observable.combineLatest($0.map {
Observable.combineLatest(
Observable.just($0.0),
URLSession.shared.rx.data(request: $0.1)
.materialize()
)
})
}
The map before the line creates an Observable<[(URL, URLRequest)]> and the line in question converts it to an Observable<[(URL, Event<Data>)]>.
The line does this by:
Set up the network call to create an Observable<Data>
Materialize it to create an Observable<Event<Data>> (this is done so an error in one download won't shutdown the entire stream.)
Lift the URL back into an Observable which gives us an Observable<URL>
Combine the observables from steps 2 & 3 to produce an Observable<(URL, Event<Data>)>.
Map each array element to produce [Observable<(URL, Event<Data>)>]
Combine the observables in that array to finally produce Observable<[(URL, Event<Data>)]>
Here is the code
// manipulatedUsers is for the code you commented out.
// users: Observable<Users>
let users = self.dataManager.getUsers()
.map(manipulatedUsers) // manipulatedUsers(_ users: Users) -> Users
.asObservable()
.share(replay: 1)
// this chain is for handling the users object. You left it blank in your code so I did too.
users
.observeOn(MainScheduler.instance)
.subscribe(onNext: { users in
})
.disposed(by: disposeBag)
// This navigates through the users structure and downloads the images.
// images: Observable<(URL, Event<Data>)>
let images = users.map { $0.categories ?? [] }
.map { $0.flatMap { $0.profiles ?? [] } }
.map { $0.compactMap { $0.thumbnail } }
.map { $0.compactMap { URL(string: $0) } }
.map { $0.map { ($0, URLRequest(url: $0)) } }
.flatMap {
Observable.combineLatest($0.map {
Observable.combineLatest(
Observable.just($0.0),
URLSession.shared.rx.data(request: $0.1)
.materialize()
)
})
}
.flatMap { Observable.from($0) }
.share(replay: 1)
// this chain filters out the errors and saves the successful downloads.
images
.filter { $0.1.element != nil }
.map { ($0.0, $0.1.element!) }
.map { ($0.0, UIImage(data: $0.1)!) }
.observeOn(MainScheduler.instance)
.bind(onNext: { url, image in
try? Disk.save(image, to: .caches, as: url.lastPathComponent)
return // need two lines here because this needs to return Void, not Void?
})
.disposed(by: disposeBag)
// this chain handles the download errors if you want to.
images
.filter { $0.1.error != nil }
.bind(onNext: { url, error in
print("failed to download \(url) because of \(error)")
})
.disposed(by: disposeBag)

How can I get XCTest to wait for async calls in setUp before tests are run?

I'm writing integration tests in Xcode 6 to go alongside my unit and functional tests. XCTest has a setUp() method that gets called before every test. Great!
It also has XCTestException's which let me write async tests. Also great!
However, I would like to populate my test database with test data before every test and setUp just starts executing tests before the async database call is done.
Is there a way to have setUp wait until my database is ready before it runs tests?
Here's an example of what I have do now. Since setUp returns before the database is done populating I have to duplicate a lot of test code every test:
func test_checkSomethingExists() {
let expectation = expectationWithDescription("")
var expected:DatabaseItem
// Fill out a database with data.
var data = getData()
overwriteDatabase(data, {
// Database populated.
// Do test... in this pseudocode I just check something...
db.retrieveDatabaseItem({ expected in
XCTAssertNotNil(expected)
expectation.fulfill()
})
})
waitForExpectationsWithTimeout(5.0) { (error) in
if error != nil {
XCTFail(error.localizedDescription)
}
}
}
Here's what I would like:
class MyTestCase: XCTestCase {
override func setUp() {
super.setUp()
// Fill out a database with data. I can make this call do anything, here
// it returns a block.
var data = getData()
db.overwriteDatabase(data, onDone: () -> () {
// When database done, do something that causes setUp to end
// and start running tests
})
}
func test_checkSomethingExists() {
let expectation = expectationWithDescription("")
var expected:DatabaseItem
// Do test... in this pseudocode I just check something...
db.retrieveDatabaseItem({ expected in
XCTAssertNotNil(expected)
expectation.fulfill()
})
waitForExpectationsWithTimeout(5.0) { (error) in
if error != nil {
XCTFail(error.localizedDescription)
}
}
}
}
Rather than using semaphores or blocking loops, you can use the same waitForExpectationsWithTimeout:handler: function you use in your async test cases.
// Swift
override func setUp() {
super.setUp()
let exp = expectation(description: "\(#function)\(#line)")
// Issue an async request
let data = getData()
db.overwriteDatabase(data) {
// do some stuff
exp.fulfill()
}
// Wait for the async request to complete
waitForExpectations(timeout: 40, handler: nil)
}
// Objective-C
- (void)setUp {
[super setUp];
NSString *description = [NSString stringWithFormat:#"%s%d", __FUNCTION__, __LINE__];
XCTestExpectation *exp = [self expectationWithDescription:description];
// Issue an async request
NSData *data = [self getData];
[db overwriteDatabaseData: data block: ^(){
[exp fulfill];
}];
// Wait for the async request to complete
[self waitForExpectationsWithTimeout:40 handler: nil];
}
There are two techniques for running asynchronous tests. XCTestExpectation and semaphores. In the case of doing something asynchronous in setUp, you should use the semaphore technique:
override func setUp() {
super.setUp()
// Fill out a database with data. I can make this call do anything, here
// it returns a block.
let data = getData()
let semaphore = DispatchSemaphore(value: 0)
db.overwriteDatabase(data) {
// do some stuff
semaphore.signal()
}
semaphore.wait()
}
Note, for that to work, this onDone block cannot run on the main thread (or else you'll deadlock).
If this onDone block runs on the main queue, you can use run loops:
override func setUp() {
super.setUp()
var finished = false
// Fill out a database with data. I can make this call do anything, here
// it returns a block.
let data = getData()
db.overwriteDatabase(data) {
// do some stuff
finished = true
}
while !finished {
RunLoop.current.run(mode: .default, before: Date.distantFuture)
}
}
This is a very inefficient pattern, but depending upon how overwriteDatabase was implemented, it might be necessary
Note, only use this pattern if you know that onDone block runs on the main thread (otherwise you'll have to do some synchronization of finished variable).
Swift 4.2
Use this extension:
import XCTest
extension XCTestCase {
func wait(interval: TimeInterval = 0.1 , completion: #escaping (() -> Void)) {
let exp = expectation(description: "")
DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
completion()
exp.fulfill()
}
waitForExpectations(timeout: interval + 0.1) // add 0.1 for sure `asyncAfter` called
}
}
and usage like this:
func testShouldDeleteSection() {
let tableView = TableViewSpy()
sut.tableView = tableView
sut.sectionDidDelete(at: 0)
wait {
XCTAssert(tableView.isReloadDataCalled, "Check reload table view after section delete")
}
}
The example above isn't complete, but you can get the idea. Hope this helps.
Swift 5.5 & iOS 13+
You could overridefunc setUp() async throws for instance:
final class MyTestsAsync: XCTestCase {
var mockService: ServiceProtocolMock!
override func setUp() async throws {
mockService = await {
//... some async setup
}()
}
override func tearDown() async throws {
//...
See Apple docs on concurrency note here

Resources