Reputation: 947
I'm trying to find best practice to handle multiple sub-requests per each value got from parent request. I'm trying to use the same logic as here - Reactive Cocoa 5 and ReactiveSwift network requests handling, but have some problems.
What we have and need:
1. TableView with infinite scrolling handler (SVPullToRefresh)
2. Fetch list of objects each time handler is called
3. Send "sub-request" for each object from response
Notes:
1. All requests (parent + sub-requests) should become cancelled once viewController is closed (deinit called)
2. I need to have an ability to cancel parent request at any time. This should also cancel all sub-requests.
What I currently have
I know what I do in "infinite handler" is kinda "duct tape", but I'm new with ReactiveSwift...
self.tableView.addInfiniteScrollingWithActionHandler { [unowned self] in
self.tempMutableProperty.value = true
}
self.tempMutableProperty.producer.skipNil().flatMap(.latest) { [unowned self] tempValueThatIDontNeed in
return self.producerForParentRequest(offset: self.offset)
.take(during: self.reactive.lifetime)
.on(
// handlers for errors, completed, etc
value: { [unowned self] items in
self.items.append(items)
self.tableView.reloadData()
self.offset += items.count
// SEND REQUEST #2 FOR EACH ITEM
}
).flatMapError { error in
return SignalProducer.empty
}
}.observe(on: UIScheduler().start()
So, as you see, I have pagination with tableView. I'm fetching list of objects for each page. Then for each item from response I need to fetch an additional information with request #2.
Flow and problems:
1. Of course I want to get rid of tempMutableProperty
and somehow start new parent request
without some kinda of proxy
2. Each sub-request
should be independent, which means I want to have value/error
handler called for each sub-request
separately, and NOT like it waits for all 10 sub-requests and then call success handler with all 10 responses collected. Also, fail on some specific sub-request should not affect on other sub-requests running
3. User can change his search request without waiting for whole request process beeing completed. This mean that once user changes some parameters, I will clear all items, and I need to cancel parent request
within all sub-requests
and start this all again.
4. In addition to #2, sometimes user can scroll down to fetch new portion of items. This will mean that new parent request
should start, but sub-requests
from previous response of parent request
should continue working
5. All requests should become cancelled on self.deinit
, so this all should work only during self.lifetime
, but I'm not sure what is the correct place to put this parameter
I'm not sure if this all is possible without storing disposable/signals as properties of self, so that's not a problem if sub-request
will be somehow stored as properties.
Thank you all for your help
Upvotes: 4
Views: 779
Reputation: 947
So, I will post here solutions for my problems.
For point #1 I've made this:
let disposable = SerialDisposable()
self.tableView.addInfiniteScrolling(actionHandler: { [unowned self] in
self.disposable.inner = nil // this is needed to force dispose current request before starting new one
self.disposable.inner = self.producer().take(during: self.reactive.lifetime)
.on(value: { [unowned self] value in
// handle as you want
}).start()
})
This helped me to get rid of tempMutableProperty
. Instead of flatMap
the SerialDisposable
is used.
So, this works fine for me, and it disposed request automatically when self
is getting destroyed
For other points I've made this:
The idea was that I'm loading some items for table, then I need to send additional request per item to fetch detailed information for it. So, I created class, having 3 properties - item
, itemDetail
, requestSent
.
Then in willDisplay cell
I have
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if !self.items[indexPath.row].requestSent {
self.items[indexPath.row].details <~ self.detailedProducerForItemID(self.items[indexPath.row].item.id)
self.items[indexPath.row].requestSent = true
}
}
Note: self.items[indexPath.row].details
is a MutableProperty<Details?>
In the cell itself for representing details I have something like:
let (detailsSignal, detailsObserver) = Signal<MutableProperty<Details?>, NoError>.pipe()
, where Details
is the name of class for item's details.
In cell's awakeFromNib
:
let details = self.detailsSignal.flatMap(.latest) { $0.producer }
self.detailsLabel.reactive.text <~ details.map { value -> String? in
// handling `Details`
}
And in cellForRow
I call cell.detailsObserver.send(value: self.items[indexPath.row].details)
And, when VC's deinit
is called, or I perform a new main
request, all requests are automatically getting cancelled, since when I use self.items.removeAll()
it dispose all requests.
This is rough flow. If someone is interested in more details, don't hesitate to ask.
Upvotes: 0
Reputation: 5941
For part 1, I would add an extension that turns the infinite scrolling action handling into a Signal:
extension Reactive where Base: UITableView {
public func infiniteScrollingSignal() -> Signal<Void, NoError>
{
return Signal { [unowned base = self.base] observer in
base.addInfiniteScrollingWithActionHandler {
observer.send(value: ())
}
return ActionDisposable {
// Unsubscribe the infinite scrolling action handler here if necessary
}
}
.take(during: self.lifetime)
}
}
Then you can hook up all of your logic to self.tableView.reactive.infiniteScrollingSignal()
Upvotes: 0