How to get a non-observable value from a function? - rx-swift

I have this function which doesn't work, because immediately returns without setting data
func fetchedData() -> String {
var data: String
networkRequest.suscribe(
onNext: {
data = "successful"
},
onError: {
data = "unsuccesful"
}).addDisposableTo(self.disposeBag)
return data
}
How can I make it work? Sorry I am fairly new to RxSwift

Hope this will helps you.
func fetchedData(isSuccess: #escaping ((Bool) -> Void)) {
networkRequest.suscribe(
onNext: {
isSuccess(true)
},
onError: {
isSuccess(false)
}).addDisposableTo(self.disposeBag)
}
fetchedData { (isSuccess) in
if isSuccess {
print("Successfull")
} else {
print("Unsuccessfull")
}
}

Well, actually what you are trying to achieve can be done with semaphores for example. But your approach is really bad, as I believe you misunderstood the concepts of RxSwift and reactive programming in general.
The first thing you need to understand is that everything in RxSwift is an observable sequence or something that operates on or subscribes to events emitted by an observable sequence.
Arrays, Strings or Dictionaries will be converted to observable sequences in RxSwift. You can create an observable sequence of any Object that conforms to the Sequence Protocol from the Swift Standard Library.
You should not use such function in your code func fetchedData() -> String. In your case you may want to use something like:
func fetchedData() -> Observable<String> {
return networkRequest.map { _ -> String in
return "Successfull"
}.catchErrorJustReturn("Unsuccessfull")
}
Then you have your code aligned with reactive principles. You may bind this sequence to some variables, transform it, share and so on.

Related

is there a better way to code for two dependent observables

In the method below I want to call two observables. After the data from first observable (getUnrecoveredGearsExt- a http req) is returned I want to pass the data to the second observable (createUpdate- persist to indexDB). Is there a cleaner way to achieve this maybe using some of the rxjs operators. thanks
Note: after the successful completion of the second observable I want to return the data from the first Observable. The use case is get data from the backend and store locally in indexDB and if successful return data or error
public getAndUpdateUnrecoveredGears(cfr: string, maxResults?: number, excludeTripid?: string) : Observable<GearSet[]> {
return Observable.create((observer) => {
this.getUnrecoveredGearsExt(cfr,maxResults,excludeTripid).subscribe(
(gears: GearSet[]) => {
this.createUpdate(gears).subscribe(
() => {
observer.next(gears);
observer.complete();
},
(error) => {
observer.error(error);
}
);
},
(error) => {
observer.error(error);
}
);
});
}
Having nested .subscribe() methods is an anti-pattern of RxJS and can cause many issues. So it's a strong signal of when you need to use operators. Fortunately, there is one which simplifies your code.
public getAndUpdateUnrecoveredGears(cfr: string, maxResults?: number, excludeTripid?: string) : Observable<GearSet[]> {
return this.getUnrecoveredGearsExt(cfr,maxResults,excludeTripid).pipe(
concatMap((gears:GearSet[])=>this.createUpdate(gears))
);
}
Because we're dealing with HTTP requests, they'll emit one value then complete. For this, we can use concatMap(). This operator will wait until getUnrecoveredGearsExt() completes, and then will subscribe to createUpdate() using the value emitted from getUnrecoveredGearsExt(). The operator will then emit any values coming from this "inner observable".
Assuming createUpdate() is also an HTTP request, it will automatically send a complete signal after emitting the response.
The solution below works for me. The final issue was how to pass the previous result 'gears' out as a final result. This is achieved by using the combineLatest to pass the two results to the next map operator, which can then pass gears out.
public getAndUpdateUnrecoveredGearsAlt2(cfr: string, maxResults?: number, excludeTripid?: string): Observable<GearSet[]> {
return this.getUnrecoveredGearsExt(cfr,maxResults,excludeTripid).pipe(
switchMap((gears: GearSet[]) => { return combineLatest(this.createUpdate(gears),of(gears));}),
map(([temp, gears]) => gears )
);
}

RxSwift: Using BehaviorRelay I get this error: Instance method 'concatMap' requires that '[Int]' conform to 'ObservableConvertibleType'

I'm trying to use concatMap on BehaviorRelay but I'm getting this error:
Instance method 'concatMap' requires that '[Int]' conform to 'ObservableConvertibleType'
This is my implementation:
class MyClass{
var disposeBag = DisposeBag()
var subject: BehaviorRelay<[Int]> = BehaviorRelay(value: [1,2,3,4,5])
func doSomething() {
subject.asObservable().concatMap { $0 }
.subscribe { print($0) }
.disposed(by: disposeBag)
}
}
I'm getting the error on this line:
subject.asObservable().concatMap { $0 }
Any of you knows why I'm getting this error or how can fix this error on my implementation ?
I'll really appreciate your help.
The problem here is conceptual. It doesn't make any sense to use concatMap on an array of Ints. The error is basically telling you that an array of Ints is not an Observable. There aren't any Observables to concat here.
You need to go back and think about what you are trying to accomplish and find the right operator for the job.

IOS Rxswift use Kingfisher to prefetch cell Image

I'm trying to implement Kingfisher prefetch feature inside an Rxswift project. The problem is with these 2 function
collectionView.rx.prefetchItems
collectionView.rx.cancelPrefetchingForItems
The instruction at Kingfisher github is quite short
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.prefetchDataSource = self
}
extension ViewController: UICollectionViewDataSourcePrefetching {
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
let urls = indexPaths.flatMap { URL(string: $0.urlString) }
ImagePrefetcher(urls: urls).start()
}
}
How can we implement with Rxswift? anyway to get the models and then the urls of models from array of indexpath. Thank.
I will walk you thru how I figured out the solution to help you figure out future solutions...
I'm assuming that the struct that holds the URL strings is called Item and that you have an Observable<[Item]> that you are currently using to load up the collection view. I'm also assuming that you only have one section in your collection.
First, we know that something needs to happen when the prefetchItemsAt sends an event so we start with that:
let foo = collectionView.rx.prefetchItems
Now inspect the type of foo to see that it is a ControlEvent<[IndexPath]>. A ControlEvent is a kind of observable. We just need the items part of the IndexPaths, so lets map that:
let foo = collectionView.rx.prefetchItems
.map { $0.map { $0.item } }
(The double map is an unfortunate consequence of Swift not supporting higher kinded types) Now inspect the type of foo. It is an Observable array of ints. Those ints are indexes into our items array. So we need access to the most recently emitted items:
(as above)
.withLatestFrom(items) { indexes, elements in
indexes.map { elements[$0] }
}
So withLatestFrom is like combineLatest except it only fires when the primary observable emits a value, not when the secondary observable does.
Now, inspecting the type of foo will find that it's an Observable array of Items. The exact items who's urls' we want to send to the ImagePrefetcher. So we need to extract the urlStrings into URLs.
(as above)
.map { $0.compactMap { URL(string: $0.urlString) } }
And with that, we have the array of URLs we want ImagePrefetcher to consume. Since it consumes data, it needs to be wrapped it in a subscribe block.
(as above)
.subscribe(onNext: {
ImagePrefetcher(urls: $0).start()
})
At this point, foo is a disposable so it just needs to be collected in our dispose bag... Here is the entire block of code.
collectionView.rx.prefetchItems
.map { $0.map { $0.item } }
.withLatestFrom(items) { indexes, elements in
indexes.map { elements[$0] }
}
.map { $0.compactMap { URL(string: $0.urlString) } }
.subscribe(onNext: {
ImagePrefetcher(urls: $0).start()
})
.disposed(by: bag)
One last bit to make everything clean... Everything between prefetchItems and the subscribe can be moved into the ViewModel if you have one.
The key takeaway here is that you can use the types to guide you, and you need to know what operators are available to manipulate Observables which you can find at http://reactivex.io/documentation/operators.html

Converting callback hell to observable chain

I have been working with a convention where my functions return observables in order to achieve a forced sequential series of function calls that each pass a returned value to their following "callback" function. But After reading and watching tutorials, it seems as though I can do this better with what I think is flatmap. I think I am close with this advice https://stackoverflow.com/a/34701912/2621091 though I am not starting with a promise. Below I have listed and example that I am hoping for help in cleaning up with advice on a nicer approach. I am very grateful for help you could offer:
grandparentFunction().subscribe(grandparentreturnobj => {
... oprate upon grandparentreturnobj ...
});
grandparentFunction() {
let _self = this;
return Observable.create((observer) => {
...
_self.parentFunction().subscribe(parentreturnobj => {
...
_self.childFunction( parentreturnobj ).subscribe(childreturnobj => {
...
observer.next( grandparentreturnobj );
observer.complete();
});
});
});
}
parentFunction() {
let _self = this;
return Observable.create((observer) => {
...
observer.next( parentreturnobj );
observer.complete();
}
}
childFunction() {
let _self = this;
return Observable.create((observer) => {
...
observer.next( childreturnobj );
observer.complete();
}
}
The general rule-of-thumb in RxJS is that you should really try to avoid creating hand-made, custom Observables (i.e., using Observable.create()) unless you know what you're doing, and can't avoid it. There are some tricky semantics that can easily cause subtle problems if you don't have a firm grasp of the RxJS 'contract', so it's usually better to try to use an existing Observable creation function. Better yet, create Observables via applying operators on an existing Observable, and return that.
In terms of specific critiques of your example code, you're right that you should be using .flatMap() to create Observable function chains. The nested Observable.create()s you currently have are not very Rx-like, and suffer from the same problems 'callback hell'-style code has.
Here's an example of doing the same thing your example does, but in a more idiomatic Rx style. doStuff() is our asynchronous function that we want to create. doStuff() needs to call the asynchronous function step1(), chain its result into the asynchronous function step2(), then do some further operations on the result, and return the final result to doStuff()'s caller.
function doStuff(thingToMake) {
return step1(thingToMake)
.flatMap((step1Result) => step2(step1Result))
.map((step2Result) => {
let doStuffResult = `${step2Result}, and then we're done`;
// ...
return doStuffResult;
});
}
function step1(thingToMake) {
let result = `To make a ${thingToMake}, first we do step 1`;
// ...
return Rx.Observable.of(result);
}
function step2(prevSteps) {
let result = `${prevSteps}, then we do step 2`
// ...
return Rx.Observable.of(result);
}
doStuff('chain').subscribe(
(doStuffResult) => console.log(`Here's how you make a chain: ${doStuffResult}`),
(err) => console.error(`Oh no, doStuff failed!`, err),
() => console.debug(`doStuff is done making stuff`)
)
Rx.Observable.of(x) is an example of an existing Observable creator function. It just creates an Observable that returns x, then completes.

struggling with asynchronous patterns using NSURLSession

I'm using Xcode 7 and Swift 2 but my question isn't necessarily code specific, I'll gladly take help of any variety.
In my app I have a list of favorites. Due to API TOS I can't store any data, so I just keep a stub I can use to lookup when the user opens the app. I also have to look up each favorite one by one as there is no batch method. Right now I have something like this:
self.api.loadFavorite(id, completion: { (event, errorMessage) -> Void in
if errorMessage == "" {
if let rc = self.refreshControl {
dispatch_async(dispatch_get_main_queue()) { () -> Void in
rc.endRefreshing()
}
}
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.viewData.append(event)
self.viewData.sortInPlace({ $0.eventDate.compare($1.eventDate) == NSComparisonResult.OrderedDescending })
self.tableView.reloadData()
}
} else {
// some more error handling here
}
})
in api.loadFavorite I'm making a typical urlSession.dataTaskWithURL which is itself asynchronous.
You can see what happens here is that the results are loaded in one by one and after each one the view refreshes. This does work but its not optimal, for long lists you get a noticeable "flickering" as the view sorts and refreshes.
I want to be able to get all the results then just refresh once. I tried putting a dispatch group around the api.loadFavorites but the async calls in dataTaskWith URL don't seem to be bound by that group. I also tried putting the dispatch group around just the dataTaskWithURL but didn't have any better luck. The dispatch_group_notify always fires before all the data tasks are done.
Am I going at this all wrong? (probably) I considered switching to synchronous calls in the background thread since the api only allows one connection per client anyway but that just feels like the wrong approach.
I'd love to know how to get async calls that make other async calls grouped up so that I can get a single notification to update my UI.
For the record I've read about every dispatch group thread I could find here and I haven't been able to make any of them work. Most examples on the web are very simple, a series of print's in a dispatch group with a sleep to prove the case.
Thanks in advance.
If you want to invoke your method loadFavorite asynchronously in a loop for all favorite ids - which executes them in parallel - you can achieve this with a new method as shown below:
func loadFavorites(ids:[Int], completion: ([Event], ErrorType?) -> ()) {
var count = ids.count
var events = [Event]()
if count == 0 {
dispatch_async(dispatch_get_global_queue(0, 0)) {
completion(events, nil)
}
return
}
let sync_queue = dispatch_queue_create("sync_queue", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0))
for i in ids {
self.api.loadFavorite(i) { (event, message) in
dispatch_async(sync_queue) {
if message == "" {
events.append(event)
if --count == 0 {
dispatch_async(dispatch_get_global_queue(0, 0)) {
completion(events, nil)
}
}
}
else {
// handle error
}
}
}
}
}
Note:
- Use a sync queue in order to synchronise access to shared array
events and the counter!
- Use a global dispatch queue where you invoke the completion handler!
Then call it like below:
self.loadFavorites(favourites) { (events, error) in
if (error == nil) {
events.sortInPlace({ $0.eventDate.compare($1.eventDate) == NSComparisonResult.OrderedDescending })
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.viewData = events
self.tableView.reloadData()
}
}
if let rc = self.refreshControl {
dispatch_async(dispatch_get_main_queue()) { () -> Void in
rc.endRefreshing()
}
}
Note also, that you need a different approach when you want to ensure that your calls to loadFavorite should be sequential.
If you need to support cancellation (well, who does not require this?), you might try to cancel the NSURLSession's tasks. However, in this case I would recommend to utilise a third party library which already supports cancellation of network tasks.
Alternatively, and in order to greatly simplify your asynchronous problems like those, build your network task and any other asynchronous task around a general utility class, frequently called Future or Promise. A future represents an eventual result, and is quite light wight. They are also "composable", that is you can define "continuations" which get invoked when the future completes, which in turn returns yet another future where you can add more continuations, and so force. See wiki Futures and Promises.
There are a couple of implementations in Swift and Objective-C. Ideally, these should also support cancellation. Unfortunately, I don't know any Swift library implementing Futures or Promises which support cancellation at this time - except my own library, which is not yet Open Source.
Another library which helps to solve common and also very complex asynchronous patterns is ReactiveCocoa, though it has a very steep learning curve and adds quite a lot of code to your project.
This is what finally worked for me. Easy once I figured it out. My problem was trying to take ObjC examples and rework them for swift.
func migrateFavorites(completion:(error: Bool) -> Void) {
let migrationGroup = dispatch_group_create()
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
// A lot of other code in there, fetching some core data etc
dispatch_group_enter(migrationGroup)
self.api.loadFavorite(id, completion: { (event, errorMessage) -> Void in
if errorMessage == "" {
if let rc = self.refreshControl {
dispatch_async(dispatch_get_main_queue()) { () -> Void in
rc.endRefreshing()
}
}
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.viewData.append(event)
self.viewData.sortInPlace({ $0.eventDate.compare($1.eventDate) == NSComparisonResult.OrderedDescending })
self.tableView.reloadData()
}
} else {
// some more error handling here
}
dispatch_group_leave(migrationGroup)
})
dispatch_group_notify(migrationGroup, queue) { () -> Void in
NSLog("Migration Queue Complete")
dispatch_async(dispatch_get_main_queue()) { () -> Void in
completion(error: migrationError)
}
}
}
The key was:
ENTER the group just before the async call
LEAVE the group as the last line in the completion handler
As I mentioned all this is wrapped up in a function so I put the function's completion handler inside the dispatch_group_notify. So I call this function and the completion handler only gets invoked when all the async tasks are complete. Back on my main thread I check for the error and refresh the ui.
Hopefully this helps someone with the same problem.

Resources