I currently have a set of network requests to fire.
The problem is that I need to order the results of them to fit the order I fired them.
My current code is the following:
for url in config.fieldImages.map ({ URL(string: $0)! }) {
self.getWheelFieldImage(url: url)
.takeUntil(.inclusive, predicate: { (_) -> Bool in
images.count == config.fieldImages.count - 1
})
.subscribe(onNext: { (anImage) in
images.append(anImage)
}, onError: { (error) in
completion(nil, nil, error)
}, onCompleted: {
completion(images, false, nil)
self.lastUpdate = Date()
}, onDisposed: {
})
.disposed(by: self.disposeBag)
}
I'm wondering if there is an easy way to order these results in the same order I fired them, using RxSwift.
EDIT:
I try to explain the problem better. I have this array with N URLs and I fire the requests one after the other (1,2,3,4...).
I need to have back the result from these requests in the same order (R1, R2, R3, R4, where R1 is the response from request 1 etc...) to store the images in the resulting array.
I can wait all to finish. No problem.
Without much changes in your original code you can achieve this by use using enumerated() on your urls list as:
/// Prefill your images with nil for each image
var images = Array<Int?>(repeating: nil, count: config.fieldImages.count)
for (index, url) in config.fieldImages.map ({ URL(string: $0)! }).enumerated() {
self.getWheelFieldImage(url: url)
.takeUntil(.inclusive, predicate: { (_) -> Bool in
images.count == config.fieldImages.count - 1
})
.subscribe(onNext: { (anImage) in
images[index] = anImage /// Store on proper position
}, onError: { (error) in
completion(nil, nil, error)
}, onCompleted: {
completion(images, false, nil)
self.lastUpdate = Date()
}, onDisposed: {
})
.disposed(by: self.disposeBag)
}
Probably most RxWay will be use of zip operator as:
let streams = config.fieldImages
.map { URL(string: $0)! }
.map { self.getWheelFieldImage(url: $0) }
let images = Observable.zip(streams) // Observable<[UIImage]>
.subscribe(
onNext: { [weak self] images in
completion(images, false, nil)
self?.lastUpdate = Date()
},
onError: { error in
completion(nil, nil, error)
}
)
.disposed(by: self.disposeBag)
You can read more about zip in documentation
Related
I have two Publishers, I want to feed the second one with the result of first one, I could do what I wanted by calling the second one nested into the first one, it works but it does not feel good to look at, is there a better way to do it?
the first Publisher returns AnyPublisher<URL, Error> and the second one returns AnyPublisher<JsonModel, Error>
func upload(input: URL, output: URL) {
let converter = MP3Converter()
converter.convert(input: input, output: output)
.sink { [weak self] result in
if case .failure(let error) = result {
ConsoleLogger.log(error)
self?.uploadedRecordingURL = nil
}
} receiveValue: { url in
guard let data = try? Data(contentsOf: url) else {
return
}
self.repository.uploadCover(data: data)
.sink(receiveCompletion: { [weak self] result in
if case .failure(let error) = result {
ConsoleLogger.log(error)
self?.uploadedRecordingURL = nil
}
}, receiveValue: { [weak self] response in
self?.uploadedRecordingURL = response.fileURL
}).store(in: &self.disposables)
}.store(in: &disposables)
}
I think you've already deduced this, but just do be clear: you are using sink and its receiveValue completely wrong. Don't start a new chain or do any significant work here at all! There should be a simple sink at the end, followed by store to anchor the chain, and that's the end.
You are looking for flatMap. That is how you chain publishers. (See my https://www.apeth.com/UnderstandingCombine/operators/operatorsTransformersBlockers/operatorsflatmap.html.) You may have to give some thought to exactly what needs to pass from the first publisher and its chain down into the flatMap closure and what needs to pass on down the chain from there.
You could use flatmap to chain your publishers together.
// the code would roughly look like this
converter.convert(input: input, output: output).flatMap { data in
return self.repository.uploadCover(data: data)
}.sink(receiveCompletion: { [weak self] result in
if case .failure(let error) = result {
ConsoleLogger.log(error)
self?.uploadedRecordingURL = nil
}
}, receiveValue: { [weak self] response in
self?.uploadedRecordingURL = response.fileURL
}).store(in: &self.disposables)
Here's an example in playgrounds that compiles
import Combine
import Foundation
enum SomeError: Error {
}
let subject0 = CurrentValueSubject<Data, SomeError>(Data())
let pub0 = subject0.eraseToAnyPublisher()
func repoUpload(data: Data) -> AnyPublisher<URL, SomeError> {
// do the real work, this is just to get it to compile
let subject1 = CurrentValueSubject<URL, SomeError>(URL(fileURLWithPath: "Somepath"))
return subject1.eraseToAnyPublisher()
}
var disposables = Set<AnyCancellable>()
pub0.flatMap { data in
return repoUpload(data: data)
}.sink(receiveCompletion: { result in
print(result)
}, receiveValue: { response in
print(response)
}).store(in: &disposables)
Given an array of urls, is there a way to run those at once simultaneously? But in such a way so as soon as there is one success move to the next request using that successful url.
So far I tried chaining concatMap, and failed with zip.
func updateAccountInfo() -> Single<Bool> {
var disposable: Disposable? = nil
return Single<Bool>.create { observer in
do {
var urls = try self.settings.domains.value()
disposable = Observable.from(urls)
.enumerated()
.concatMap { index, url -> Single<URL> in
return self.verifyUrl(url)
}
.concatMap { url -> Single<Account> in
return self.apiManager.loadAccountInfo(from: url)
}
.observeOn(MainScheduler.instance)
.do(onNext: { (account: AccountInfo) in
// use account unfo here
disposable!.dispose()
})
.subscribe()
} catch {
observer(.error(error))
}
return Disposables.create()
}
}
Tried like so too:
disposable = Observable.from(urls)
.enumerated()
.concatMap { index, url -> Single<(Bool, URL)> in
return self.verifyUrl(url)
}
.subscribe(onNext: { reachable, url in
if reachable {
self.apiManager.loadAccountInfo(from: url)
.subscribe(onSuccess: { accountInfo in
// use account info here
}, onError: { error in
})
.disposed(by: self.bag)
disposable!.dispose()
} else {
}
}, onError: { error in
}, onCompleted: {
})
Maybe I use zip but how would I create an array of verifyUrl(url) calls? Does zip accept arrays of Observable at all?
let obs = Observable.from(urls)
.enumerated()
.concatMap { index, url -> Single<URL> in
return self.verifyUrl(url)
}
let test = Observable
.zip(obs).map { [urls] in
return [urls]
}
If I understand the question correctly, you are looking for something like this:
func example() throws {
let urls = try self.settings.domains.value()
Observable.merge(urls.map { verifyUrl($0).asObservable() })
.flatMap { [apiManager] url in
apiManager!.loadAccountInfo(from: url)
}
.observe(on: MainScheduler.instance)
.subscribe(onNext: { account in
// use account unfo here
})
.disposed(by: self.disposeBag)
}
But it's hard to tell. Your code samples are a bit jumbled. Putting all your code in a Single.create is odd. Returning a Disposables.create() from the closure when you have a disposable to return is odd. Calling dispose() on a disposable inside the do block is odd. So much weirdness... I suggest you post some code on https://codereview.stackexchange.com or look at sample code.
I'm a begginer with RxSwift and this is my issue, my app must do a 3 requests, the 3 are gets, my work team suggests me use a flatmap to do this a three request in row, but I don't know how I should use flatmap.
these are my requests
public func login(param: [String:String]) -> Observable<messageModel>{
return Observable.create { observer -> Disposable in
self.alamoFireManager!.request(self.urlServer!+endPoints.login.login, method: .post, parameters: param, encoding: URLEncoding.default, headers: nil, interceptor: nil).responseDecodable { (res: DataResponse<messageModel,AFError>) in
if let error = res.error {
observer.onError(error)
} else if let valueEntitie = res.value {
observer.onNext(valueEntitie)
}
observer.onCompleted()
}
return Disposables.create()
}
}
public func me() -> Observable<meModel>{
return Observable.create { observer -> Disposable in
self.alamoFireManager!.request(self.urlServer!+endPoints.login.me, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil, interceptor: nil).responseDecodable { (res: DataResponse<meModel,AFError>) in
if let error = res.error {
observer.onError(error)
} else if let valueEntitie = res.value {
observer.onNext(valueEntitie)
}
observer.onCompleted()
}
return Disposables.create()
}
}
public func entitie(entityId: String) -> Observable<entitieModel>{
return Observable.create { observer -> Disposable in
self.alamoFireManager!.request(self.urlServer!+endPoints.login.entities+"/"+entityId, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil, interceptor: nil).responseDecodable { (res: DataResponse<entitieModel,AFError>) in
if let error = res.error {
observer.onError(error)
} else if let valueEntitie = res.value {
observer.onNext(valueEntitie)
}
observer.onCompleted()
}
return Disposables.create()
}
}
the first endpoint is login with its parameters, then me, function me response a id, this id its necessary for the third request, that is entitie.
I'm doing the flatmap as this way.
networkManagerShareCore.share.login(param: param)
.flatMap { resMessageModel in
//saveData(resMessageModel)
networkManagerShareCore.share.me()
.flatMap { resMeModel in
//saveData(resMessageModel)
networkManagerShareCore.share.entitie(entityId: "\(resModelMe.data.personId!)")
}
}.subscribe(onNext: { (model) in
print(model)
}, onError: { (error) in
self.errorMsg.accept(error.localizedDescription)
self.isSuccess.accept(false)
}, onCompleted: nil) {
print("Disposed")
}
the code works, but in the line //saveData(resMessageModel) I can't call it the function, I want to save the model, but if I try a call a function, Xcode show me this error: Unable to infer complex closure return type; add explicit type to disambiguate
so, how can I resolve this?
RxSwift flatMap expects a return value of some Observable.
The FlatMap operator transforms an Observable by applying a function
that you specify to each item emitted by the source Observable, where
that function returns an Observable that itself emits items.
You need to add a return statement within each flatMap call. In addition, you need to explicitly define the return type for the closures.
So the inner most flatMap call should look something like this:
networkManagerShareCore.share.me()
.flatMap { resMeModel -> Observable<entitieModel> in
//saveData(resMessageModel)
return networkManagerShareCore.share.entitie(entityId: "\(resModelMe.data.personId!)")
You wouldn't normally embed flatMap within flatMap, but just compose them sequentially to make the code clearer. And don't forget disposed(by:). The final result would look something like this:
networkManagerShareCore.share.login(param: param)
.flatMap { resMessageModel -> Observable<meModel> in
//saveData(resMessageModel)
return networkManagerShareCore.share.me()
}
.flatMap { resMeModel -> Observable<entitieModel> in
//saveData(resMessageModel)
return networkManagerShareCore.share.entitie(entityId: "\(resModelMe.data.personId!)")
}
.subscribe(
onNext: { (model) in
print(model)
},
onError: { (error) in
self.errorMsg.accept(error.localizedDescription)
self.isSuccess.accept(false)
},
onCompleted: nil) {
print("Disposed")
}
.disposed(by: disposeBag)
Dale's answer works but I would be inclined to move the saves into do operators instead. Like this:
networkManagerShareCore.share.login(param: param)
.do(onNext: { [weak self] resMessageModel in
self?.saveData(resMessageModel)
})
.flatMap { _ in
networkManagerShareCore.share.me()
}
.do(onNext: { [weak self] resMeModel in
self?.saveData(resMeModel)
})
.flatMap { resMeModel in
networkManagerShareCore.share.entitie(entityId: "\(resMeModel.data.personId!)")
}
.subscribe(
onNext: { (model) in
print(model)
},
onError: { (error) in
self.errorMsg.accept(error.localizedDescription)
self.isSuccess.accept(false)
},
onCompleted: nil,
onDisposed: {
print("Disposed")
}
)
.disposed(by: disposeBag)
I am using DispatchGroup to download data from 3 different APIs, once this is done I want to return the new created consolidated object from my function. Now Although DispatchGroup is working fine and I am getting data but I am not able to return it to calling function. Following is my function:
func getHowToInfo(materialNo: String) -> Observable<HowToInfo> {
return Observable.create{ observer in
let dispatchGroup = DispatchGroup()
_ = self.getMaterialInfo(materialNo: materialNo).subscribe(onNext:{ material in
let howto = HowToInfo(videos: [], documents: [], applications: [])
if (material.documentTargetId?.count)! > 0 {
dispatchGroup.enter()
_ = self.materialRepo?.API1(targetIDs: material.documentTargetId!).subscribe(onNext:{documents in
howto.documents = documents
dispatchGroup.leave()
}, onError: { (error) in
dispatchGroup.leave()
})
}
if (material.applicationDescription?.count)! > 0 {
dispatchGroup.enter()
_ = self.materialRepo?.API2(materialNo: materialNo).subscribe(onNext:{applications in
howto.applications = applications
dispatchGroup.leave()
}, onError: { (error) in
dispatchGroup.leave()
})
}
if ((material.videoApplicationTargetId?.count) != nil && (material.videoApplicationTargetId?.count)! > 0) {
dispatchGroup.enter()
_ = self.materialRepo?.API3(targetIDs: material.videoApplicationTargetId!).subscribe(onNext:{videos in
howto.videos = videos
dispatchGroup.leave()
}, onError: { (error) in
dispatchGroup.leave()
})
}else if ((material.videoSupportTargetId?.count) != nil && (material.videoSupportTargetId?.count)! > 0) {
dispatchGroup.enter()
_ = self.materialRepo?.API4(targetIDs: material.videoSupportTargetId!).subscribe(onNext:{videos in
howto.videos = videos
dispatchGroup.leave()
}, onError: { (error) in
dispatchGroup.leave()
})
}
dispatchGroup.notify(queue: .main, execute: {
print("All functions complete 👍")
observer.onNext(howto)
observer.onCompleted()
})
})
return Disposables.create()
}
}
calling function:
func loadHowToUseList(materialNo: String){
self.serviceMaterial.getHowToInfo(materialNo: materialNo).subscribe({
howToUse in
print(howToUse)
}).disposed(by: DisposeBag())
}
I am not able to get my object in subscribe method above, it never runs.
Try adding
dispatchGroup.wait()
After your lines
dispatchGroup.notify(queue: .main, execute: {
print("All functions complete 👍")
observer.onNext(howto)
observer.onCompleted()
})
And also, why don't just use Rx operators itself?
Every one of this could be an observer.onNext, then you try to observe on three events of this observable, and there is no need for onCompleted
I think you can achieve desired behaviour using combineLatest and skipWhile operators. Roughly implementation would be like this:
let api1 = Observable.of(["documents"]) //Replace with observable to download docs
let api2 = Observable.of(["applications"]) //Replace with observable to download apps
let api3 = Observable.of(["videos"]) //Replace with observable to download videos
Observable.combineLatest(api1, api2, api3){(docs, apps, videos) in
return (docs, apps, videos)
}.skipWhile{ (docs, apps, videos) in
return docs.count == 0 && apps.count == 0 && videos.count == 0
}.subscribe(onNext:{(docs, apps, videos) in
})
.disposed(by:disposeBag)
So basically I have two actions I need to execute:
first is login
second is get user profile
They have to be done in right order because getting user profile cannot be done without logging in first.
So I had bunch of code that looked like this:
func signIn(signinParameters: SignInParameters) -> Observable<SignInResult> {
return Observable<SignInResult>.create { [unowned self] observer in
self.signinParameters = signinParameters
self.apiConnector
.signIn(with: signinParameters)
.do(onNext: { [weak self] signinResult in
self!.apiConnector
.get()
.do(onNext: { user in
let realm = RealmManager.shared.newRealm()!
let realmUser = RealmUser()
realmUser.configure(with: user, in: realm)
try? realm.write {
realm.add(realmUser, update: true)
}
self!.setState(.authenticated)
observer.onNext(signinResult)
}, onError: { (error) in
observer.onError(error)
}, onCompleted: {
observer.onCompleted()
}).subscribe()
}, onError: { error in
observer.onError(error)
}, onCompleted: {
print("completed")
observer.onCompleted()
}).subscribe()
return Disposables.create()
}
I know this is not right because I cannot send onNext signal with signin result when both actions are finished. I've been reading and I figured out i need to flatmap both actions, combine them into one signal and then manipulate signinresult but I dont have a clue how to do that. So any help would be nice.
Thank you
EDIT 1:
so I've refactored code to look something like this, but there is still problem that I can't send signal when BOTH actions are finished, or am I wrong?
func signIn(signinParameters: SignInParameters) -> Observable<SignInResult> {
return Observable<SignInResult>.create { [unowned self] observer in
self.signinParameters = signinParameters
self.apiConnector
.signIn(with: signinParameters)
.do(onNext: { (result) in
}, onError: { (error) in
}, onCompleted: {
})
.flatMap({ (result) -> Observable<User> in
self.apiConnector.get().asObservable()
})
.do(onNext: { (user) in
}, onError: { (error) in
}, onCompleted: {
}).subscribe()
return Disposables.create()
}
}
Your code is not very clean and it is hard to understand what is going on (my opinion).
If you need two actions to be executed you can create two functions:
struct Parameters{}
struct Profile{}
struct User{}
func login(parameters: Parameters) -> Observable<User> {
// get user
}
func profile(user: User) -> Observable<Profile> {
// get profile
}
func serial(parameters: Parameters) -> Observable<Profile> {
return login(parameters: parameters).flatMap({ profile(user: $0) })
}
login function or profile function can be also split into smaller functions if required:
func profileStored(user: User) -> Observable<Profile?> {
// get stored profile
}
func profileRequested(user: User) -> Observable<Profile> {
// get profile from network
}
func profile(user: User) -> Observable<Profile> {
let observable = profileStored(user: user)
.shareReplayLatestWhileConnected()
let observableStored = observable
.filter({ $0 != nil })
.map({ $0! })
.shareReplayLatestWhileConnected()
let observableRequested = observable
.filter({ $0 == nil })
.flatMap({ _ in profileRequested(user: user) })
.shareReplayLatestWhileConnected()
return Observable
.of(observableStored, observableRequested)
.merge()
.shareReplayLatestWhileConnected()
}
As a result you can mix smaller functions with flatMap or any other operator.
That is how I do it. Hope it'll be helpful