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)
}
Related
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)
}
}
}
I'm trying to wrap my head around generic type constraints in Swift. Here is my starting point:
class Promise<T> {
func resolve(_ block:#escaping (T) ->Void) {}
func fulfill(_ result:T) {}
}
A Promise is just that, something that can be fulfilled in the future. This becomes very useful when it is used with Swift's Result type when handing results back from a background queue to the main queue:
let promise = Promise<Result<String, Error>>()
promise.fulfill(.success("Hello"))
promise.fulfill(.failure(NSError()))
Now I would like to add an extension to all Promise instances that use Result to add these helper methods:
extension Promise where T == Result<X, Error> {
⬆︎ Here's the problem ⚡️
func failure(_ error:Error) {
fulfill(.failure(error))
}
func success(_ result:X) {
fulfill(.success(result))
}
}
// Shorter:
let promise = Promise<Result<String, Error>>()
promise.success("Hello")
promise.failure(NSError())
Only problem is that the above code does not compile, because X is not defined. What I want to express is this:
Extend Promise when its generic type T is of type Result<X,Z> where X can be anything and Z must be of type Error → Result<*, Error>. Is this possible?
What you want is possible, it's just that the syntax is slightly verbose. You can't put the where constraint on the extension. You have to put it on each method.
extension Promise {
func failure<U>(_ error: Error) where T == Result<U, Error> {
fulfill(.failure(error))
}
func success<U>(_ result: U) where T == Result<U, Error> {
fulfill(.success(result))
}
}
You can also do it with a protocol, but for enums I find this very klunky, because enum cases can't be treated as conforming methods.
protocol ResultType {
associatedtype Success
associatedtype Failure: Error
static func makeFailure(_: Failure) -> Self
static func makeSuccess(_: Success) -> Self
}
extension Result: ResultType {
static func makeFailure(_ failure: Failure) -> Result<Success, Failure> { .failure(failure) }
static func makeSuccess(_ success: Success) -> Result<Success, Failure> { .success(success) }
}
extension Promise where T: ResultType {
func failure(_ error: T.Failure) {
fulfill(T.makeFailure(error))
}
func success(_ result: T.Success) {
fulfill(T.makeSuccess(result))
}
}
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.
I have an interesting compile error when using one generic to call another. This post is a bit long but hopefully, it's not longer than needed to describe the problem.
I have defined a generic func that works great. I use it a lot and the pattern of use is often the same. I was trying to implement a new generic func that nests the existing generic func, but I'm getting a compile-time error.
For some context, here is how my API works with the generic now. My REST API makes a call the films() func, which hits the StarWarsAPI (swapi.co) and returns a list of all the Star Wars films they have in their database, as follows:
StarWarsAPI.shared.films(){ (films, error) in
for film in films {
print(film.title)
}
}
The films() function calls a generic (restCall()) which works great. Here is the definition of films():
public func films(completion: #escaping (_ films:[Film]?, _ error:StarWarsError?) -> Void) {
guard StarWarsAPI.isOperational else {
return completion(nil,StarWarsError.starWarsAPINotOperational)
}
restCall(fetchUrl: filmsUrl!, modelType: FilmResult()) { (filmResults, error ) in
completion(filmResults?.results, error)
}
}
Where the restCall (the generic) is defined as follows: (Note I'm using the Swift 4 Codable API)
public func restCall<T: Codable>(fetchUrl: URL, modelType: T, completion: #escaping (_ modelObject: T?, _ error:StarWarsError?) -> Void){
var fetchRequest = URLRequest(url: fetchUrl, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
fetchRequest.httpMethod = "GET"
fetchRequest.allHTTPHeaderFields = [
"content-type": "application/json",
"cache-control": "no-cache",
]
let session = URLSession.shared
let fetchDataTask = session.dataTask(with: fetchRequest) { (data, response, error) in
DispatchQueue.main.async { // return to main thread
var modelObject:T?
do {
let jsonDecoder = JSONDecoder()
modelObject = try jsonDecoder.decode(T.self, from: data)// FIXME: Something about decoding the People object is going wrong.
return completion(modelObject, nil)
}catch let error as NSError {
completion(nil, StarWarsError.decodingModelError(error: error))
}
}
}
fetchDataTask.resume()
}
So the above works great. I use it for rest functions films(), people(), ships(), etc. I use the same pattern for each rest call. I want to create a generic that I can use instead of explicit films(), people(), etc.
I've been trying to get the following to work with little success:
public func fetchAll<T: Result>(result:T, completionAll: #escaping (_ result:T?, _ error:StarWarsError?) -> Void) {
restCall(fetchUrl: result.urlPath!, modelType: T) { (finalResults, error ) in
completionAll(finalResults!, error)
}
}
Where Result type is the base type and is defined as follows:
public class Result {
var urlPath:URL?
}
public class FilmResult: Result, Codable {
var count:Int?
var next:String?
var previous:String?
var results:[Film]?
}
The error I'm getting is shown in the screenshot below - hopefully, it's clear.
Any help you can provide would be much appreciated!
Your call has to be
this was T previously ───┐
restCall(fetchUrl: result.urlPath!, modelType: result) { (finalResults, error ) in
Note the result instead of T.
Relatively minimal code to reproduce:
public func restCall<T>(fetchUrl: URL, modelType: T, completion: #escaping (_ modelObject: T?, _ error:String?) -> Void) { }
public func fetchAll<T>(result:T, completionAll: #escaping (_ result:T?, _ error:String?) -> Void) {
┌── should be result
restCall(fetchUrl: URL(string: "asdasd")!, modelType: T) { (finalResults, error ) in
completionAll(finalResults, error)
}
}
I am trying to implement a small future (promises) library with Swift 3 inspired by this talk here is my implmentation :
public enum Result<T, E: Error> {
case Success(T)
case Error(E)
}
public struct Future<T, E: Error> {
public typealias ResultType = Result<T, E>
public typealias Completion = (ResultType) -> Void
public typealias AsyncOperation = (Completion) -> Void
private let operation: AsyncOperation
public init(result: ResultType) {
self.init(operation: { completion in
completion(result)
})
}
public init(value: T) {
self.init(result: .Success(value))
}
public init(error: E) {
self.init(result: .Error(error))
}
public init(operation: #escaping (Completion) -> Void) {
self.operation = operation
}
public func start(completion: Completion) {
self.operation() { result in
completion(result)
}
}
}
//: ### Error handeling
enum UserInfoErrorDomain: Error {
case UserDoesNotExist
case UserRequestFailure
case NetworkRequestFailure
}
and here is my usage:
func downloadFile(URL: NSURL) -> Future<NSData, UserInfoErrorDomain> {
return Future(operation: { completion in
DispatchQueue.main.async( execute: {
print("Async2")
let result: Result<NSData, UserInfoErrorDomain>
if let data = NSData(contentsOf: URL as URL) {
result = Result.Success(data)
}
else {
result = Result.Error(.NetworkRequestFailure)
}
completion(result) // ERROR here Closure use of non-escaping parameter 'completion' may allow it to escape
})
})
}
but I get in the line of completion(result) and error of Closure use of non-escaping parameter 'completion' may allow it to escape
But the closure is already marked as #escaping in the method public init(operation: #escaping (Completion) -> Void) but maybe because it's a closure that takes a closure as argument and returns void needs another annotation, so to do that in Swift 3 because it seems that the code used to work in Swift 2
[...] but maybe because it's a closure that takes a closure as argument and returns void needs another annotation [...]
You're right. Completion is of type (ResultType) -> Void, which, as it's a parameter to your AsyncOperation function type, means that it's non-escaping by default – meaning that you cannot capture your completion parameter in an escaping closure (such as one passed to DispatchQueue.main.async).
Therefore you need to annotate Completion as #escaping:
public typealias AsyncOperation = (#escaping Completion) -> Void
and you'll want your init(operation:) and start(completion:) functions to look like this:
public init(operation: #escaping AsyncOperation) {
self.operation = operation
}
// the completion: parameter needs to be escaping as it's going to be called after
// an async operation has completed.
public func start(completion: #escaping Completion) {
self.operation { result in
completion(result)
}
}