问题
I'm trying to figure out if network requests handling can be implemented suitable to my needs using ReactiveSwift and RAC5.
Under topic Migrate from RACSignal to ReactiveSwift or RAC5 I was told it can be done with SignalProducer, but digging deeper into it didn't give me expected results
So, I would want to have:
1. Each time text changes in textField send request (search by keyword).
2. Once user closes current ViewController, the current request should be cancelled automatically
3. Have an ability to cancel request once keyword is changed
Here is what I have
self.textField.reactive.continuousTextValues.skipNil().filter({ (value) -> Bool in
return value.characters.count > 0
}).observeValues { [unowned self] (value) in
self.fetchSignalDisposable?.dispose()
self.fetchSignal = self.producerFor(keyword: value).on(started: {
print("started")
}, failed: { (error) in
print("error")
}, completed: {
print("completed")
}, value: { [unowned self] (items) in
print("value")
self.items.append(contentsOf: items)
self.tableView.reloadData()
})
self.fetchSignalDisposable = self.fetchSignal!.start()
}
And here is producer initializer
return SignalProducer<Any, NSError> { (observer, disposable) in
let task: URLSessionDataTask? = NetworkClient.fetchRequestWith(uri: "test", parameters: ["keyword" : keyword], success: { response in
observer.send(value: response)
observer.sendCompleted()
}, failure: { error in
observer.send(error: error)
observer.sendCompleted()
})
disposable += {
task?.cancel()
}
}
Notes:
1. Sometimes I want to have kinda "both handler block" that would be called on both success and errors, so stuff like hiding loading indicators can be done under that block.
Few problems/questions here:
1. Once I close VC (dismiss action) observeValue handler is called one more time. It can be fixed by adding .skipRepeats()
, but I guess it is just a workaround and not an exact solution. I would want to not have this observer active anymore, if I close VC
2. completed
block not called in case of error, even if I call it manually right after calling send(error: error)
3. If request is still loading and I close VC, it is not getting disposed automatically, which looks strange to me. I thought dispose block will be called automatically once viewController lose reference to signalProducer. Even calling self.fetchSignalDisposable?.dispose()
in deinit
method of VC doesn't cancel request. It still finishes request and calls value
handler which leads to crash with Bad Access error
My personal needs are:
1. Have some kind of "both" block that will be called after both success and failed cases of request
2. All observers for textFields' text values must be removed and not be active anymore once I close VC
3. Network request must be cancelled right when I close VC
P.S.: Of course, thanks to everyone who read this huge post and spent time helping me!
回答1:
The "Making network requests" example from the ReactiveSwift readme is a good example for this type of thing. Rather than using observeValues
on your text field signal, typically you would use .flatMap(.latest)
to hook it up directly to your SignalProducer like so (note I haven't checked this code, but hopefully it gets the idea across):
self.textField.reactive.continuousTextValues
.skipNil()
.filter { (value) -> Bool in
return value.characters.count > 0
}
.flatMap(.latest) { [unowned self] value in
return self.producerFor(keyword: value)
// Handling the error here prevents errors from terminating
// the outer signal. Now a request can fail while allowing
// subsequent requests to continue.
.flatMapError { error in
print("Network error occurred: \(error)")
return SignalProducer.empty
}
}
.observe(on: UIScheduler())
.observe { [unowned self] event in
switch event {
case let .value(items):
print("value")
self.items.append(contentsOf: items)
self.tableView.reloadData()
case let .failed(error):
print("error")
case .completed, .interrupted:
print("completed")
}
}
Specifying .latest
causes the previous network request to automatically be cancelled when a new one starts, so there's no need to keep track of the current request in a global variable.
As for managing lifetime, it's hard to say what the best thing is without knowing your broader code structure. Typically I would add something like .take(during: self.reactive.lifetime)
to my signal to terminate the subscription when self
is deallocated, probably right before the call to observe
.
Error events terminate signals. There's no need to send a completed event after an error, and observers won't see it anyway. Basically, complete indicates that the signal terminated successfully while an error indicates that the signal terminated in failure.
来源:https://stackoverflow.com/questions/43490471/reactive-cocoa-5-and-reactiveswift-network-requests-handling