I want to change the API request code written using the closure to RxSwift.
For example, I would like to make rxGetList() function using getList() function.
// This function cannot be modified.
func getList(success: #escaping ([String]) -> Void,
failure: #escaping (Error) -> Void) {
// Request to Server...
}
func rxGetList() -> Observable<String> {
// Using getList() function
// TODO
}
What code should I write in TODO section?
Please give me some advice.
The easiest way to meet your expectations is to use something like this:
func rxGetList() -> Observable<String> {
return Observable.create { observer in
getList(success: { result in
for everyString in result {
observer.onNext(everyString)
}
observer.onCompleted()
}, failure: { error in
observer.onError(error)
})
return Disposables.create() {
// specify any action to be performed on observable dispose (like cancel URL task)
}
}
}
Note that you have [String] specified as an input type of your success closure. If it's not a typo then above code fits. If you want one String instead, it's as simple as this:
func rxGetList() -> Observable<String> {
return Observable.create { observer in
getList(success: { result in
observer.onNext(result)
observer.onCompleted()
}, failure: { error in
observer.onError(error)
})
return Disposables.create() {
// specify any action to be performed on observable dispose (like cancel URL task)
}
}
}
Petr Grigorev's answer is the correct one, but if you want to have fun with some extreme function composition, here's a more advanced way to handle it:
let rxGetList = Observable.create(rx_(getList(success:failure:)))
.flatMap { Observable.from($0) }
func rx_<A>(_ fn: #escaping (#escaping (A) -> Void, #escaping (Error) -> Void) -> Void) -> (AnyObserver<A>) -> Disposable {
{
fn(singleObserve($0), $0.onError)
return Disposables.create()
}
}
func singleObserve<A>(_ observer: AnyObserver<A>) -> (A) -> Void {
{
observer.onNext($0)
observer.onCompleted()
}
}
I'm not sure about actually using the above, but if you have a lot of functions that you want to wrap, it may help reduce the amount of code you have to write.
Related
I have a problem understanding how to use GCD when using asynchronous, recursive calls to the API.
Below is one of the three similar methods that contains the same logic, just for different data and API endpoint. If there is no next page request the method should finish and next method should start.
How would I make sure that fetchItems2 gets called after fetchItems1 finishes, and fetchItems3 after fetchItems2?
private func fetchItems1(completion: #escaping (Error?) -> Void) {
var _items = [Item]()
func handleReceivedItemsPage(_ page: PagingObject<Item>, _completion: ((Error?) -> Void)?) {
let newItems = page.items!
_tracks.append(contentsOf: newTracks)
if page.canMakeNextRequest {
page.getNext(success: { nextPage in
handleReceivedItemsPage(nextPage)
}) { nextError in
_completion?(nextError)
}
} else {
// Finished, next method can now start
self.items = _items
_completion?(nil)
}
}
API.getSavedItems(success: { page in
handleReceivedItemsPage(page, _completion: completion)
}, failure: completion)
}
private func fetchItems2(completion: #escaping (Error?) -> Void)) { ... }
private func fetchItems3(completion: #escaping (Error?) -> Void)) { ... }
You can keep an extra variable that keeps track of when the API calls are complete. In the completion block, increment this variable. Then, when the variable reaches the amount of API calls complete, perform your task.
I would use DispatchGroup:
public void FetchItems(completion: #escaping (Error?) -> Void) {
let group = DispatchGroup()
group.enter()
fetchItems1() { error in
completion(error)
group.leave()
}
group.wait()
group.enter()
fetchItems2() { error in
completion(error)
group.leave()
}
// 3rd function call
}
Code after group.wait() is not called until the number of group.enter() and group.leave() invocations is equal.
I have the following problem:
func doSomething() -> Promise<Bool> {
let completionHandler = { (result: Bool) in
// How can I fulfill the promise here -- Promise { fulfill, _ in fulfill(result) }
}
someLibrary.doSomeTasks(handler: completionHandler)
// What do I return for this function?...
}
Currently I don't know what to return / how to return a Promise<Bool> because the bool value isn't available until the completion handler is finished. someLibrary.doSomeTasks doesn't support PromiseKit so I'm stuck with using the completion handler like shown. Thanks!
this has been updated in promiseKit 6 to:
func doSomething() -> Promise<Bool> {
return Promise<Bool> { seal in
someLibrary.doSomeTask(handler: { value in
seal.fullfill(value)
// we also have seal.reject(error), seal.resolve(value, error)
})
}
}
Here is the general form to do what you want:
func doSomething() -> Promise<Bool> {
return Promise { fulfill, reject in
someLibrary.doSomeTask(handler: { value in
fulfill(value)
})
}
}
ReactiveSwift has this great function called flatMapError that allows you to respond with an event stream when an error occurs. A simple example might look like:
authenticationProducer.flatMapError { _ in self.reauthenticate() }
Whenever an error occurs, that error gets mapped into a producer that attempts to re-authenticate.
How would I build a similar operator using PromiseKit? The function signature would look like:
func flatMapError<U>(_ transform: #escaping (Error) -> Promise<U>) -> Promise<U>
My implementation so far:
func flatMapError<U>(_ transform: #escaping (Error) -> Promise<U>) -> Promise<U> {
return Promise<U> { resolve, reject in
self.catch { error in
let promise = transform(error)
let _ = promise.then { value in
resolve(value)
}
}
}
}
Use recover, it behaves as you request.
https://github.com/mxcl/PromiseKit/blob/master/Sources/Promise.swift#L254-L278
I am trying to get user data from a server. The application does not have to show any views until the data is loaded.
I read about typealias and I don't understand how to use it.
What I want: when data is loaded, move on to next step. If failed, load data again.
Here's how I declare typealias
typealias onCompleted = () -> ()
typealias onFailed = () -> ()
Here is my request code
func getUserData(_ completed: #escaping onCompleted, failed: #escaping onFailed){
let fullURL = AFUtils.getFullURL(AUTHURL.getUserData)
AFNetworking.requestGETURL(fullURL, params: nil, success: {
(JSONResponse) -> Void in
if let status = JSONResponse["status"].string {
switch status{
case Status.ok:
completed()
break
default:
failed()
break
}
}
})
}
But how could I use this on my view controller when calling getUserData?
Assuming your custom AFNetworking.requestGETURLs completion handler is called on the main queue:
func viewDidLoad() {
super.viewDidLoad()
getUserData({
//do somthing and update ui
}) {
//handle error
}
}
Edit:
How I understand your comment, you actually want to name your completion and error block parameters. If so, change the method to :
func getUserData(completion completed: #escaping onCompleted, error failed: #escaping onFailed){ ... }
and call it like this:
getUserData(completion: {
//do somthing and update ui
}, error: {
//handle error
})
My app is heavily dependent on the data that is coming. I want it to run the activity indicator and disable user interaction on the view while the data is being downloaded.
Is there a way to check or return something when the completion handler is done?
typealias CompletionHandler = (success:Bool) -> Void
func downloadFileFromURL(url: NSURL,completionHandler: CompletionHandler) {
**download code**
let flag = true
true if download succeed,false otherwise
completionHandler(success: flag)
}
How to use it.
downloadFileFromURL(NSURL(string: "url_str")!, { (success) -> Void in
**When download completes,control flow goes here.**
if success {
} else {
}
})
Define a property, which keeps completion handler, and call it when all the data is obtained:
var didObtainAllData: (() -> Void)?
func obtainData() {
....
// When data is obtained.
didObtainAllData?()
}
You can write
func processingTask(condition: String, completionHandler:(finished: Bool)-> Void) ->Void {
}
Use
processingTask("test") { (finished) in
if finished {
// To do task you want
}
}