简体   繁体   中英

ReactiveCocoa 5, ReactiveSwift network sub-request handling & best practice

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

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.

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()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM