Generic perform Request, using Generics - swift

I would like to make a perform request function in swift using Generics. I want to make the call and switch on my enum Result based on what I get back. However, I don't understand the : 'cannot invoke performRequest with an argument list of type (NSURLRequest, (Result<__>) -> ())' Why can't I have an unnamed parameter here? I have also tried something like the following : r<MyStruct> --- but I then get an expected expression error. Any help explaining the above Result<_> error would be greatly appreciated. Thanks.
enum Result<A> {
case Value
case Error
}
func performRequest<A>(request:NSURLRequest, callback:(Result<A>) -> ()) {
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) -> Void in
callback(parseResponse(data, response: response, error: error))
}
task.resume()
}
class SampleClass {
let request = NSURLRequest(URL: NSURL(string: "www.google.com")!)
init() {
performRequest(request) { r in -------- errors out
switch r {
case .Value:
case .Error:
}
}
}

The problem is that when you use performRequest, you have not given the compiler enough information about the generic parameter you intend to use. The critical part that is missing is that parseResponse needs to return a Result that is parameterised in the same way as the callback. However, in the snippet you provided, parseResponse is not generic.
I believe this will do what you intend. In this scenario, I've parameterised the Result with String, but you can substitute any other type.
// multi-purpose (generic) Result type
enum Result<A>
{
case Value(A) // because you parameterised the enum, you might as well take advantage of the type
case Error
}
// this is a custom parser, you may substitute your own that returns a different type
func parseString( data:NSData?, response:NSURLResponse?, error:NSError? ) -> Result<String> {
if let _ = error {
return Result.Error
}
return Result.Value("Success")
}
// this function is completely generic, but the parser and callback need to be compatible
func performRequest<A>( request:NSURLRequest,
parser:( NSData?, NSURLResponse?, NSError? ) -> Result<A>,
callback:( Result<A> ) -> Void ) {
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
( data, response, error ) -> Void in
callback( parser( data, response, error ) )
}
task.resume()
}
let request = NSURLRequest(URL: NSURL(string: "www.google.com")!)
// actual invocation, now I need to pass in a concrete parser and callback with a specific type
performRequest( request, parser: parseString ) { // parseString returns a Result<String>
r in
switch r {
case .Value( let value ):
// because I passed in a parser that returns a Result<String>, I know that "value" is a String here
print( "Succeeded with value: \(value)" )
break;
case .Error:
print( "an error occurred" )
break;
}
}

Related

Getting error when trying to use Result type with delegate

Im tring to make a network call and instead of using callback I try to use delegate instead.using Result type where .Sucsess is T: Decodable and .failure is Error. passing my model in the .Sucsess is working but when trying to pass an error I get a compile error "Generic parameter 'T' could not be inferred" what am I missing ?
protocol NetworkServiceDelegate: class {
func decodableResponce<T: Decodable>(_ result: Result<T, NetworkError>)
}
let dataTask:URLSessionTask = session.dataTask(with: url) { (dataOrNil, responceOrNil, errOrNil) in
if let error = errOrNil {
switch error {
case URLError.networkConnectionLost,URLError.notConnectedToInternet:
print("no network connection")
self.delegate?.decodableResponce(Result.failure(.networkConnectionLost))
case URLError.cannotFindHost, URLError.notConnectedToInternet:
print("cant find the host, could be to busy, try again in a little while")
case URLError.cancelled:
// if cancelled with the cancelled method the complition is still called
print("dont bother the user, we're doing what they want")
default:
print("error = \(error.localizedDescription)")
}
return
}
guard let httpResponce:HTTPURLResponse = responceOrNil as? HTTPURLResponse
else{
print("not an http responce")
return
}
guard let dataResponse = dataOrNil,
errOrNil == nil else {
print(errOrNil?.localizedDescription ?? "Response Error")
return }
do{
//here dataResponse received from a network request
let decoder = JSONDecoder()
let modelArray = try decoder.decode([Movie].self, from:
dataResponse) //Decode JSON Response Data
DispatchQueue.main.async {
self.delegate?.decodableResponce(Result.success(modelArray))
}
} catch let parsingError {
print("Error", parsingError)
}
print("http status = \(httpResponce.statusCode)")
print("completed")
}
this line generates the error, it dosnt metter if I pass my enum that cumfirms to Error or trying to pass the error from the dataTask
self.delegate?.decodableResponce(Result.failure(.networkConnectionLost))
Well, you have two problems, having to do with the question "what type is this?" Swift is very strict about types, so you need to get clear about that.
.networkConnectionLost is not an Error. It is an error code. You need to pass an Error object to a Result when you want to package up the error. For example, URLError(URLError.networkConnectionLost) is an Error.
The phrase Result<T, NetworkError> makes no sense. Result is already a generic. Your job is to resolve the generic that it already is. You do that by specifying the type.
So for example, you might declare:
func decodableResponce(_ result: Result<Decodable, Error>)
It is then possible to say (as tests):
decodableResponce(.failure(URLError(URLError.networkConnectionLost)))
or (assuming Movie is Decodable):
decodableResponce(.success([Movie()]))
That proves we have our types right, and you can proceed to build up your actual code around that example code.

Specifying custom error type within the Result type in Swift 5

I am trying to create a Result variable with a custom error type with the builtin Result type in Foundation for Swift 5, but I can't quite get the type system to understand the kind of error I want to throw.
The code below does not compile.
import Foundation
enum CustomError: String, Error {
case somethingBadHappened
}
struct Model {
let value: Int
}
class Request {
func execute(number: Int, completion: #escaping (Result<Model, CustomError>) -> Void) {
let result = Result { () throws -> Model in
if (number < 20) {
throw CustomError.somethingBadHappened
} else {
return Model(value: number)
}
}
// compiler complains here about: Cannot convert value of type 'Result<Model, Error>' to expected argument type 'Result<Model, CustomError>'
completion(result)
}
}
let request = Request()
request.execute(number: 19) { result in
switch result {
case .success(let value): print("Succeded with \(value)")
case .failure(let error): print("Failed with \(error)")
}
}
Changing the signature of the completion closure to completion: #escaping (Result<Model, Error>) -> Void works, but then I am not using the custom error type.
How can I make the type system understand I would like to use the custom error type?
Apologies for giving a second answer, but there needs to be a corrective for fphilipe's answer.
You can use init(catching:) to form the Result and yet return it as a Result<Model, CustomError>. That is what mapError is for! Like this:
enum CustomError: String, Error {
case somethingBadHappened
}
struct Model {
let value: Int
}
class Request {
func execute(number: Int, completion: #escaping (Result<Model, CustomError>) -> Void) {
let result = Result { () throws -> Model in
if (number < 20) {
throw NSError()
} else {
return Model(value: number)
}
}.mapError { err in
return CustomError.somethingBadHappened
}
completion(result)
}
}
I have to throw something in order to form the initial Result<Model, Error>, so I just throw an NSError as a kind of placeholder. But then mapError comes along and transforms this into a Result<Model, CustomError>. The power of mapError is that it changes only what happens in case of failure.
Thus we are able to keep the original form of the code.
Changing the signature of the completion closure to completion: #escaping (Result<Model, Error>) -> Void works, but then I am not using the custom error type.
Yes, you are! Change the signature in exactly that way, so that you compile, and then run your code. When we get to this line:
case .failure(let error): print("Failed with \(error)")
... we print "Failed with somethingBadHappened". That proves that your CustomError.somethingBadHappened instance came through just fine.
If the problem is that you want to separate out your CustomError explicitly, then separate it out explicitly as you catch it:
case .failure(let error as CustomError): print(error)
default : fatalError("oops, got some other error")
Or if you want to winnow it down still further and catch only the .somethingBadHappened case, specify that:
case .failure(CustomError.somethingBadHappened): print("Something bad happened")
default : fatalError("oops, got some other error")
Those examples are artificial, but they demonstrate what they are intended to demonstrate — that your CustomError instance is coming through with full integrity.
Just create Result manually:
let result: Result<Model, CustomError>
if (number < 20) {
result = .failure(.somethingBadHappened)
} else {
result = .success(Model(value: number))
}
completion(result)

Alamofire and PromiseKit returning a Promise<[T]>

I used Alamofire and PromiseKit as separate Cocoapod installs. I can retrieve the JSON data using Alamofire, but I am receiving the error below when configuring PromiseKit. The error below appears in the line where 'fulfill, reject' are in.
Error message: Contextual closure type '(Resolver<_>) -> Void' expects 1 argument, but 2 were used in closure body
I am using Xcode 9.2 and IOS 11.2 inside of the Simulator. Thank you for your advice in advance!
func wantToReturnAnArrayOfActor() -> Promise<[Actor]> {
return Promise { fulfill, reject in
Alamofire.request(ApiUrl.url.rawValue).responseJSON { (response) in
switch(response.result)
{
case .success(let responseString): print("my response string = \(responseString)")
let actorResponse = ActorApiResponse(JSONString: "\(responseString)")//converts all of the data into the ActorApiResponse model class
return when(fulfilled: actorResponse)
DispatchQueue.main.async {
print("actorResponse = \(String(describing: actorResponse))")
}
case .failure(let error): print("alamofire error = \(error)")
}
}
}
}
Should it rather be like this,
func wantToReturnAnArrayOfActor() -> Promise<[Actor]> {
return Promise() { resolver in
Alamofire.request(ApiUrl.url.rawValue).responseJSON { (response) in
switch(response.result)
{
case .success(let responseObject):
let actorResponse = ActorApiResponse(jsonObject: responseObject)
let actors = actorResponse.getActors()
resolver.fulfill(actors)
case .failure(let error):
resolver.reject(error)
}
}
}
}
The initializer closure for Promise takes in single argument, which is of type Resolver, which is what your error says. Then, you would want to resolve your promise with result which is of type [Actor] when the promise execution is finished or then reject with error if error occurred during the execution.
Few points to note here:
Alamofire.request(_).responseJSON returns json object not json string.
If your ActorApiResponse is the object which transforms the json to [Actor], you should have proper method to convert json object to actual data type ie. [Actor].
You could have your ActorApiResponse something like this,
struct ActorApiResponse {
init(jsonObject: Any) {
}
func getActors() -> [Actor] {
// calculate and return actors
return []
}
}
Then, you can call it from else where,
wantToReturnAnArrayOfActor().done {
// do something with [Actor here]
// You can also chain the multiple promise using .then instead of using done
}.catch { error in
print("Error occurred \(error)")
}

Swift Response template without additional casting

Do somebody know how to achieve Template on Response but without extra casting? Now if I do so Xcode returns me error that I can't override T dynamically. But I really believe I on right way but missed something. No?
Now it's looks like: func didReciveResponse(request: Request, response: Response<Any>)
enum Response<T> {
case success(response: T)
case failured(error: Error)
}
func pseudoResponse() {
let time: Timeinterval = 3
// somehow cast T (compiler shows error that I can't do this)
let response = .success<Timeinterval>(time)
didReciveResponse(.time, response)
}
// delegate method
func didReciveResponse(request: Request, response: Response) {
switch request {
case .time:
switch response {
// response without additional casting (as?)
case .success(let value): time = value
}
}
}
As the compiler cannot infer the generic type you have to annotate the type in this case
func pseudoResponse() {
let time: TimeInterval = 3.0
let response : Response<TimeInterval> = .success(response: time)
didReciveResponse(request: .time, response: response)
}
and you have to specify the Response type in the delegate method
func didReciveResponse(request: Request, response: Response<TimeInterval>) { ...
However if you want to make didReciveResponse (actually didReceiveResponse) also generic you need to write
func didReciveResponse<T>(request: Request, response: Response<T>) {

Best way to handle errors from async closures in Swift 2?

I'm using a lot of async network request (btw any network request in iOS need to by async) and I'm finding way to better handle errors from Apple's dataTaskWithRequest which not supports throws.
I have code like that:
func sendRequest(someData: MyCustomClass?, completion: (response: NSData?) -> ()) {
let request = NSURLRequest(URL: NSURL(string: "http://google.com")!)
if someData == nil {
// throw my custom error
}
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
// here I want to handle Apple's error
}
task.resume()
}
I need to parse my possible custom errors and handle possible connection errors from dataTaskWithRequest. Swift 2 introduced throws, but you can't throw from Apple's closure because they have no throw support and running async.
I see only way to add to my completion block NSError returning, but as I know using NSError is old-style Objective-C way. ErrorType can be used only with throws (afaik).
What's the best and most modern method to handle error when using Apple network closures? There is no way no use throws in any async network functions as I understand?
there are many ways you can solve this, but i would recommend using a completion block which expects a Result Enum. this would probably be the most 'Swift' way.
the result enum has exactly two states, success and error, which a big advantage to the usual two optional return values (data and error) which lead to 4 possible states.
enum Result<T> {
case Success(T)
case Error(String, Int)
}
Using the result enum in a completion block finishes the puzzle.
let InvalidURLCode = 999
let NoDataCode = 998
func getFrom(urlString: String, completion:Result<NSData> -> Void) {
// make sure the URL is valid, if not return custom error
guard let url = NSURL(string: urlString) else { return completion(.Error("Invalid URL", InvalidURLCode)) }
let request = NSURLRequest(URL: url)
NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
// if error returned, extract message and code then pass as Result enum
guard error == nil else { return completion(.Error(error!.localizedDescription, error!.code)) }
// if no data is returned, return custom error
guard let data = data else { return completion(.Error("No data returned", NoDataCode)) }
// return success
completion(.Success(data))
}.resume()
}
because the return value is a enum, you should switch off of it.
getFrom("http://www.google.com") { result in
switch result {
case .Success(let data):
// handle successful data response here
let responseString = String(data:data, encoding: NSASCIIStringEncoding)
print("got data: \(responseString)");
case .Error(let msg, let code):
// handle error here
print("Error [\(code)]: \(msg)")
}
}
another solution would be to pass two completion blocks, one for success and one for error. something along the lines of:
func getFrom(urlString: String, successHandler:NSData -> Void, errorHandler:(String, Int) -> Void)
It's very similar to Casey's answer,
but with Swift 5, now we have Result (generic enumeration) implementation in standard library,
//Don't add this code to your project, this has already been implemented
//in standard library.
public enum Result<Success, Failure: Error> {
case success(Success), failure(Failure)
}
It's very easy to use,
URLSession.shared.dataTask(with: url) { (result: Result<(response: URLResponse, data: Data), Error>) in
switch result {
case let .success(success):
handleResponse(success.response, data: success.data)
case let .error(error):
handleError(error)
}
}
https://developer.apple.com/documentation/swift/result
https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md
There's an elegant approach utilising a JavaScript-like Promise library or a Scala-like "Future and Promise" library.
Using Scala-style futures and promises, it may look as follows:
Your original function
func sendRequest(someData: MyCustomClass?, completion: (response: NSData?) -> ())
may be implemented as shown below. It also shows, how to create a promise, return early with a failed future and how to fulfill/reject a promise:
func sendRequest(someData: MyCustomClass) -> Future<NSData> {
guard let url = ... else {
return Future.failure(MySessionError.InvalidURL) // bail out early with a completed future
}
let request = ... // setup request
let promise = Promise<NSData>()
NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
guard let error = error else {
promise.reject(error) // Client error
}
// The following assertions should be true, unless error != nil
assert(data != nil)
assert(response != nil)
// We expect HTTP protocol:
guard let response = response! as NSHTTPURLResponse else {
promise.reject(MySessionError.ProtocolError) // signal that we expected HTTP.
}
// Check status code:
guard myValidStatusCodeArray.contains(response.statusCode) else {
let message: String? = ... // convert the response data to a string, if any and if possible
promise.reject(MySessionError.InvalidStatusCode(statusCode: response.statusCode, message: message ?? ""))
}
// Check MIME type if given:
if let mimeType = response.MIMEType {
guard myValidMIMETypesArray.contains(mimeType) else {
promise.reject(MySessionError.MIMETypeNotAccepted(mimeType: mimeType))
}
} else {
// If we require a MIMEType - reject the promise.
}
// transform data to some other object if desired, can be done in a later, too.
promise.fulfill(data!)
}.resume()
return promise.future!
}
You might expect a JSON as response - if the request succeeds.
Now, you could use it as follows:
sendRequest(myObject).map { data in
return try NSJSONSerialization.dataWithJSONObject(data, options: [])
}
.map { object in
// the object returned from the step above, unless it failed.
// Now, "process" the object:
...
// You may throw an error if something goes wrong:
if failed {
throw MyError.Failed
}
}
.onFailure { error in
// We reach here IFF an error occurred in any of the
// previous tasks.
// error is of type ErrorType.
print("Error: \(error)")
}