Convert closure Result to Rx generically? - swift

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.

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

Can I get out of callback hell with RxSwift?

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.

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

Publisher emitting progress of operation and final value

Given I have an SDK which provides the functionality below
class SDK {
static func upload(completion: #escaping (Result<String, Error>) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
completion(.success("my_value"))
}
}
}
I am able to a create a wrapper around to make its usage more functional
class CombineSDK {
func upload() -> AnyPublisher<String, Error> {
Future { promise in
SDK.upload { result in
switch result {
case .success(let key):
promise(.success(key))
case .failure(let error):
promise(.failure(error))
}
}
}.eraseToAnyPublisher()
}
}
Now I'm trying to understand how my CombineSDK.upload method should look like if the SDK upload method also provides a progress block like below:
class SDK {
static func upload(progress: #escaping (Double) -> Void, completion: #escaping (Result<String, Error>) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
progress(0.5)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
progress(1)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
completion(.success("s3Key"))
}
}
}
We need an Output type for your publisher that represents either the progress, or the final value. So we should use an enum. Since the Foundation framework already defines a type named Progress, we'll name ours Progressable to avoid a name conflict. We might as well make it generic:
enum Progressable<Value> {
case progress(Double)
case value(Value)
}
Now we need to think about how the publisher should behave. A typical publisher, like URLSession.DataTaskPublisher, doesn't do anything until it gets a subscription, and it starts its work fresh for each subscription. The retry operator only works if the upstream publisher behaves like this.
So our publisher should behave that way, too:
extension SDK {
static func uploadPublisher() -> UploadPublisher {
return UploadPublisher()
}
struct UploadPublisher: Publisher {
typealias Output = Progressable<String>
typealias Failure = Error
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {
<#code#>
}
}
}
Creating the publisher (by calling SDK.uploadPublisher()) doesn't start any work. We'll replace <#code#> with code to start the upload:
extension SDK {
static func uploadPublisher() -> UploadPublisher {
return UploadPublisher()
}
struct UploadPublisher: Publisher {
typealias Output = Progressable<String>
typealias Failure = Error
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {
let subject = PassthroughSubject<Output, Failure>()
subject.receive(subscriber: subscriber)
upload(
progress: { subject.send(.progress($0)) },
completion: {
switch $0 {
case .success(let value):
subject.send(.value(value))
subject.send(completion: .finished)
case .failure(let error):
subject.send(completion: .failure(error))
}
}
)
}
}
}
Note that we call subject.receive(subscriber: subscriber) before we start the upload. This is important! What if upload calls one of its callbacks synchronously, before returning? By passing the subscriber to the subject before calling upload, we ensure that the subscriber has the chance to be notified even if upload calls its callbacks synchronously.
Note: started writing an answer that's has a largely similar intent to #robmayoff's answer, but using Deferred, so posting here for completeness.
Swift Combine only works with values and errors - there's no separate type for progress. But you can model the progress as part of the output, either as a tuple, as was suggested in another answer, or as a custom enum with both progress and result as cases, which would be my preferred approach.
class CombineSDK {
enum UploadProgress<T> {
case progress(Double)
case result(T)
}
func upload() -> AnyPublisher<UploadProgress<String>, Error> {
Deferred { () -> AnyPublisher<UploadProgress<String>, Error> in
let subject = PassthroughSubject<UploadProgress<String>, Error>()
SDK.upload(
progress: { subject.send(.progress($0)) },
completion: { r in
let _ = r.map(UploadProgress.result).publisher.subscribe(subject)
})
return subject.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}
EDIT
Based on #robmayoff's comment, the above solution doesn't handle synchronous case where subject.send is called before subject is returned.
The solution is largely the same, but it does introduce a small complication of having to capture these values, just in case. This can be done with Record, which will provide a temporary sink to subject
func upload() -> AnyPublisher<UploadProgress<String>, Error> {
Deferred { () -> AnyPublisher<UploadProgress<String>, Error> in
let subject = PassthroughSubject<UploadProgress<String>, Error>()
var recording = Record<UploadProgress<String>, Error>.Recording()
subject.sink(
receiveCompletion: { recording.receive(completion: $0) },
receiveValue: { recording.receive($0) })
SDK.upload(
progress: { subject.send(.progress($0)) },
completion: { r in
let _ = r.map(UploadProgress.result).publisher.subscribe(subject)
})
return Record(recording: recording).append(subject).eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
Here is possible approach
extension CombineSDK {
func upload() -> AnyPublisher<(Double, String?), Error> {
let publisher = PassthroughSubject<(Double, String?), Error>()
SDK.upload(progress: { value in
publisher.send((value, nil))
}, completion: { result in
switch result {
case .success(let key):
publisher.send((1.0, key))
publisher.send(completion: .finished)
case .failure(let error):
publisher.send(completion: .failure(error))
}
})
return publisher.eraseToAnyPublisher()
}
}