Can I get out of callback hell with RxSwift? - swift

I heard that I can get out of callback hell using RxSwift.
But I don't have an idea how to improve callback hell.
The samples below need to be called in the order getItem1() -> getItem2() -> getItem3()
Is there a way out of this callback hell using RxSwift?
class MyItem {
// MARK: - Public
// Callback hell
public func getMyItem(success: #escaping (String) -> Void, failure: #escaping (Error) -> Void) {
self.getItem1(success: { [weak self] item1 in
self?.getItem2(item1: item1, success: { [weak self] item2 in
self?.getItem3(item2: item2, success: { item3 in
success(item3)
}, failure: { err3 in
print(err3)
failure(err3)
})
}, failure: { err2 in
print(err2)
failure(err2)
})
}, failure: { err1 in
print(err1)
failure(err1)
})
}
// MARK: - Private
private func getItem1(success: #escaping (String) -> Void, failure: #escaping (Error) -> Void) {
// Request to sever
}
private func getItem2(item1: String, success: #escaping (String) -> Void, failure: #escaping (Error) -> Void) {
// Request to sever
}
private func getItem3(item2: String, success: #escaping (String) -> Void, failure: #escaping (Error) -> Void) {
// Request to sever
}
}
I'm waiting for your answer to teach me.

Your sample, using RxSwift, would look like this:
class MyItem {
// MARK: - Public
public func getMyItem() -> Observable<String> {
getItem1()
.flatMap(getItem2(item1:))
.flatMap(getItem3(item2:))
.do(onError: { print($0) })
}
}
private func getItem1() -> Observable<String> {
// Request to server
}
private func getItem2(item1: String) -> Observable<String> {
// Request to server
}
private func getItem3(item2: String) -> Observable<String> {
// Request to server
}
There are seven different systems used in iOS code to push data from one object to another:
closures
target/action (UIControl aka IBAction)
delegates
notifications (NotificationCenter)
KVO
setting variables
sub-classing abstract base classes
A lot of the complexity of writing an application comes from trying to integrate these various systems into a unified whole. Each of the systems individually is simple to use, but weaving them together makes our code complex. One of the benefits of using RxSwift is that it wraps all of the above systems into a single powerful mechanism, thus making our code less complex overall.
Knowing the above gives a clue on how to go about integrating RxSwift into an existing project. If RxSwift can replace all those other technologies, then to integrate means to replace them with it. Once this is done, we will be able to make the code more declarative and less complex.
From Integrating RxSwift into Your Brain and Code Base

If for some reason you don't want to change the signatures of any of the functions you have, then you could do something crazy like this:
public func getMyItem(success: #escaping (String) -> Void, failure: #escaping (Error) -> Void) {
_ = rx_(self.getItem1)
.flatMap(rx_(self.getItem2))
.flatMap(rx_(self.getItem3))
.do(onError: { print($0) })
.subscribe(onNext: success, onError: failure)
}
The above uses the following support functions (all of these are free functions. Do not put them in a class.)
func rx_<A, B>(_ fn: #escaping (A, #escaping (B) -> Void, #escaping (Error) -> Void) -> Void) -> (A) -> Observable<B> {
{ input in
.create(observe(curry(fn, input)))
}
}
func rx_<A>(_ fn: #escaping (#escaping (A) -> Void, #escaping (Error) -> Void) -> Void) -> Observable<A> {
.create(observe(fn))
}
func curry<A, B>(_ fn: #escaping (A, #escaping (B) -> Void, #escaping (Error) -> Void) -> Void, _ a: A) -> (#escaping (B) -> Void, #escaping (Error) -> Void) -> Void {
{ success, failure in
fn(a, success, failure)
}
}
func observe<A>(_ fn: #escaping (#escaping (A) -> Void, #escaping (Error) -> Void) -> Void) -> (AnyObserver<A>) -> Disposable {
{ observer in
fn(singleObserve(observer), observer.onError)
return Disposables.create()
}
}
func singleObserve<A>(_ observer: AnyObserver<A>) -> (A) -> Void {
{ element in
observer.onNext(element)
observer.onCompleted()
}
}
I don't recommend this sort of thing for production code. IMO, something like the above trades callback hell for abstraction hell, but it's a fun exercise.

Related

Making custom Deffered Future Publisher in Swift Combine?

Like the title says I would like to make custom publisher that will basically function like deffered future. Normally when I want to encapsulate code in some Future, but want it to execute on subscription, I would need to write something like this:
Deffered {
Future { promise in
}
}
Now I was thinking of making custom publisher, something along the lines DefferedFuture that will have exact same functionality as Future, but will execute promise only on subscription?
The most obvious answer is this:
func single<Output, Failure>(_ promise: #escaping (#escaping (Result<Output, Failure>) -> Void) -> Void) -> Deferred<Future<Output, Failure>> where Failure: Error {
Deferred {
Future<Output, Failure>(promise)
}
}
If it absolutely must be a type rather than a function then:
extension Publishers {
struct Single<Output, Failure>: Publisher where Failure: Error {
let promise: (#escaping (Result<Output, Failure>) -> Void) -> Void
func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input {
Deferred { Future(promise) }
.subscribe(subscriber)
}
}
}

Shorthand conversion from Result to Future in Swift?

I'd like to provide Combine counterparts to completion closures which is becoming very cumbersome. Is there a shorter way or extension that can convert the following:
extension MyType {
func send(with request: URLRequest, completion: #escaping (Result<MyResponse, MyError>) -> Void) {
// Logic
}
func send(with request: URLRequest) -> Future<MyResponse, MyError> {
Future<MyResponse, MyError> { promise in
send(with: request) { result in
switch result {
case let .success(response):
promise(.success(response))
case let .failure(error):
promise(.failure(error))
}
}
}
}
}
The Future method is just a wrapper to the completion closure method. I was hoping to do at least something like this:
Future<MyResponse, MyError> { send(with:request, completion: $0) }
Is there a more elegant way to do this since this will be applied in a lot of places in my library.
Note that the completion parameter of the first send overload has the type:
Result<MyResponse, MyError>) -> Void
Which is exactly the same type as promise, which is
Future<MyResponse, MyError>.Promise
Promise is just a type alias for (Result<Output, Failure>) -> Void.
So you can just do:
Future<MyResponse, MyError> { promise in
send(with: request, completion: promise)
}

Is there a way of using a completion handler passed through as an argument to detect when a long request is completed

I'm having trouble understanding how to use a closure to handle completed events when passing in a function as a parameter.
Here's a very contrived example:
class MessageService {
func sendMessage(s: String) {
print(s)
}
var messenger: Messenger {
createMessenger(completion: sendMessage(s:))
}
}
func createMessenger(completion: #escaping (String) -> Void) -> Messenger {
return Messenger { completion("This is a hardcoded message.") }
}
struct Messenger {
let sendMessage: () -> Void
init(sendMessage: #escaping () -> Void) {
self.sendMessage = sendMessage
}
}
let service = MessageService()
let messenger = service.messenger
messenger.sendMessage()
I want to find out when sendMessage is finished (if for example it was performing something like a network request), so is there a way of having a completion handler for sendMessage so that I could write something along the lines of:
messenger.sendMessage {
print("I finished sending a message!")
}
I've tried adding a completion handler like this in the service class:
func sendMessage(s: String, completion: #escaping () -> Void) {
MessageRequest(with: s) {
completion()
}
}
But things started getting very confusing when I'm trying to use the createMessenger method, because the above function has some crazy type of (String, () -> ()) -> () which I don't know how to handle. Any help would be greatly appreciated, thanks.
So, it sounds like what you want is an arbitrary Messenger type, whose creator tell it what action to do, and once the action is done, it invokes its caller's completion handler.
It helps if you use typealias with descriptive names to keep track of all the closures. And if you don't mind, I'll name it more generically as Agent:
struct Agent {
typealias Completion = () -> Void
typealias Action = (Completion) -> Void
private let action: Action
static func create(action: #escaping Action) -> Agent {
Agent(action: action)
}
func execute(_ completion: #escaping Completion) {
action(completion)
}
}
So, Agent can be created with an arbitrary action that accepts a completion handler to signal when it's done:
let agent = Agent.create { completion in
print("started executing action")
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { completion() }
}
agent.execute { print("done") }
Now, you can adapt it to your MessengerService class:
class MessageService {
func sendMessage(s: String) {
print(s)
}
var messenger: Agent {
Agent.create { completion in
sendMessage("This is a hardcoded message.")
completion()
}
}
}

Convert closure Result to Rx generically?

I have a bunch of functions with Result completion handlers that I’d like to convert to RxSwift.
They follow this convention:
func fetch(id: Int, completion: #escaping (Result<AuthorType, DataError>) -> Void) {...}
I could use the typical:
return Observable<AuthorType>.create { on(.next... }
Is there a more considerate generic way like PromiseKit does:
func fetch() -> Promise<AuthorType> {
return Promise { fetch(completion: $0.resolve) }
}
Anything like this possible in RxSwift?
There isn't a constructor like you are asking for out of the box, but it's easy enough to create:
extension ObservableType {
static func createFromResultCallback<E: Error>(_ fn: #escaping (#escaping (Result<Element, E>) -> Void) -> ()) -> Observable<Element> {
return Observable.create { observer in
fn { result in
switch result {
case .success(let value):
observer.onNext(value)
observer.onCompleted()
case .failure(let error):
observer.onError(error)
}
}
return Disposables.create()
}
}
}
For your example, it could be used like:
func fetch(id: Int) -> Observable<AuthorType> {
return .createFromResultCallback { fetch(id: id, $0) }
}
And if you have a function that only takes a callback like:
func shortFetch(_ completion: #escaping (Result<AuthorType, DataError>) -> Void)
Then you could create an Observable with the above by just doing:
Observable.createFromResultCallback(shortFetch)
Remember there is a major difference in behavior once you wrap the function in an Observable like this. Now it is cold which means it won't execute until after something subscribes to the observable and will execute every time something subscribes. This is unlike a Promise which will execute immediately and only once.

Swift "retry" logic on request

So i'm a bit lost on how to implement a retry logic when my upload request fail.
Here is my code i would like some guidance on how to do it
func startUploading(failure failure: (NSError) -> Void, success: () -> Void, progress: (Double) -> Void) {
DDLogDebug("JogUploader: Creating jog: \(self.jog)")
API.sharedInstance.createJog(self.jog,
failure: { error in
failure(error)
}, success: {_ in
success()
})
}
Here's a general solution that can be applied to any async function that has no parameters, excepting the callbacks. I simplified the logic by having only success and failure callbacks, a progress should not be that hard to add.
So, assuming that your function is like this:
func startUploading(success: #escaping () -> Void, failure: #escaping (Error) -> Void) {
DDLogDebug("JogUploader: Creating jog: \(self.jog)")
API.sharedInstance.createJog(self.jog,
failure: { error in
failure(error)
}, success: {_ in
success()
})
}
A matching retry function might look like this:
func retry(times: Int, task: #escaping(#escaping () -> Void, #escaping (Error) -> Void) -> Void, success: #escaping () -> Void, failure: #escaping (Error) -> Void) {
task(success,
{ error in
// do we have retries left? if yes, call retry again
// if not, report error
if times > 0 {
retry(times - 1, task: task, success: success, failure: failure)
} else {
failure(error)
}
})
}
and can be called like this:
retry(times: 3, task: startUploading,
success: {
print("Succeeded")
},
failure: { err in
print("Failed: \(err)")
})
The above will retry the startUploading call three times if it keeps failing, otherwise will stop at the first success.
Edit. Functions that do have other params can be simply embedded in a closure:
func updateUsername(username: String, success: #escaping () -> Void, failure: #escaping (Error) -> Void) {
...
}
retry(times: 3, { success, failure in updateUsername(newUsername, success, failure) },
success: {
print("Updated username")
},
failure: {
print("Failed with error: \($0)")
}
)
Update So many #escaping clauses in the retry function declaration might decrease its readability, and increase the cognitive load when it comes to consuming the function. To improve this, we can write a simple generic struct that has the same functionality:
struct Retrier<T> {
let times: UInt
let task: (#escaping (T) -> Void, #escaping (Error) -> Void) -> Void
func callAsFunction(success: #escaping (T) -> Void, failure: #escaping (Error) -> Void) {
let failureWrapper: (Error) -> Void = { error in
// do we have retries left? if yes, call retry again
// if not, report error
if times > 0 {
Retrier(times: times - 1, task: task)(success: success, failure: failure)
} else {
failure(error)
}
}
task(success, failureWrapper)
}
func callAsFunction(success: #escaping () -> Void, failure: #escaping (Error) -> Void) where T == Void {
callAsFunction(success: { _ in }, failure: failure)
}
}
Being callable, the struct can be called like a regular function:
Retrier(times: 3, task: startUploading)(success: { print("success: \($0)") },
failure: { print("failure: \($0)") })
, or can be circulated through the app:
let retrier = Retrier(times: 3, task: startUploading)
// ...
// sometime later
retrier(success: { print("success: \($0)") },
failure: { print("failure: \($0)") })
Here is an updated answer for swift 3. I also added a generic object in the success block so if you make an object after your network call is complete you can pass it along to the final closure. Here is the retry function:
func retry<T>(_ attempts: Int, task: #escaping (_ success: #escaping (T) -> Void, _ failure: #escaping (Error) -> Void) -> Void, success: #escaping (T) -> Void, failure: #escaping (Error) -> Void) {
task({ (obj) in
success(obj)
}) { (error) in
print("Error retry left \(attempts)")
if attempts > 1 {
self.retry(attempts - 1, task: task, success: success, failure: failure)
} else {
failure(error)
}
}
}
And here is how you would use it if you updated a user and wanted to get back a new user object with the updated info:
NetworkManager.shared.retry(3, task: { updatedUser, failure in
NetworkManager.shared.updateUser(user, success: updatedUser, error: failure) }
, success: { (updatedUser) in
print(updatedUser.debugDescription)
}) { (err) in
print(err)
}
Updated to swift 5, with Result type instead of success and failure blocks.
func retry<T>(_ attempts: Int, task: #escaping (_ completion:#escaping (Result<T, Error>) -> Void) -> Void, completion:#escaping (Result<T, Error>) -> Void) {
task({ result in
switch result {
case .success(_):
completion(result)
case .failure(let error):
print("retries left \(attempts) and error = \(error)")
if attempts > 1 {
self.retry(attempts - 1, task: task, completion: completion)
} else {
completion(result)
}
}
})
}
This is how we can use the retry function:
func updateUser(userName: String) {
retry(3, task: { (result) in
startUploadingWithResult(userName: userName, completion: result)
}) { (newResult) in
switch newResult {
case .success(let str):
print("Success : \(str)")
case .failure(let error):
print(error)
}
}
}
updateUser(userName: "USER_NAME")