Return a value from a Alamofire closure - ios

i have this function called sendRequest in a class called A and this sendRequest function returns a Any object and in first line i created a variable like this var serverResponse = JSON() and i have this code to request
upload.uploadProgress(closure: { (Progress) in
})
upload.responseJSON { response in
print(response.response?.statusCode as Any)
if(response.result.isSuccess){
serverResponse = JSON(response.result.value!)
print("Success\(serverResponse)")
}else{
serverResponse = JSON(response.result.value!)
print("No Success\(serverResponse)")
}
}
the above code is inside the sendRequest function and at the end/outside of this closure i have a return statement returning the server response return serverResponse
and i am accessing this function from class B i created an object of class A now i have this code in class B
var response: Any = request.sendRequest("url", parameters: body, headers: [:])
now the problem is the response variable here will always have an empty JSON object and i think thats because in the closure the api call is being processed in the background so before the result comes from the server the return statement gets executed giving me an empty JSON object on class B
and i tried returning the serverResponse in the if statement where i checked if its successful or not and gives me an error like this:
Unexpected non-void return value in void function
so how can i solve this problem?

You are correct. It is being returned empty because it is being run on the background thread. When making network requests we therefor tend to use completionBlocks Swift 5 implemented the new typ of Result<Any, Error> which is really convenient.
Try implementing completion((Result<Any, Error>) -> ()) in your function params instead. When you get the response you unwrap it my writing:
switch result {
case .succeses(let data):
//Do something
break
case .failure(let error):
//Do something
break
}
As you are inside the actual block you can't execute a return statement. That's the error you are getting. The block itself doesn't ask for a return. Unlike map, filter, reduce etc.

Related

Cast value to enum that inherits from Error

I have a service class that offers several methods that make calls to the backend, and a common theme for those methods is that I can pass callbacks for the success and error cases:
func makeCall(onSuccess: #escaping APIResponse, onError: #escaping APIError)
The type APIError is defined like so:
typealias APIError = (Error) -> Void
Furthermore I have an enum APICallError like this:
enum APICallError: Error {
case insufficientCredentials
case malformedResponse
case forbidden
}
So in my service methods I can call onError(response.result.error!) if the result contained an error object (the force unwrap is just for brevity, I'm not really doing that) and also pass my own enum value if the HTTP result is not specific enough, like onError(APICallError.insufficientCredentials).
This works fine.
The problem is, I can't figure out how to evaluate in my client code whether the error parameter that's coming in is of type APICallError and which one of those specifically. If I do:
if let callError = error as? APICallError {
if callError == .forbidden {
// ...
}
}
execution doesn't make it past the typecast. if error is APICallError also does not seem to work.
How can I cast the error parameter to my APICallError enum value that I know is in there, because when I print(error) it gives me myapp.APICallError.forbidden?
I tried to simulate what you have posted in your question in Playground, and what you are already doing should work fine for you.
if error is APICallError is also working. One possibility why the if let condition fails might be due to the error being nil. Check if that is the case by using breakpoints.
typealias APIError = (Error) -> Void
//The error type used in your question
enum APICallError: Error {
case insufficientCredentials
case malformedResponse
case forbidden
}
//A different error type for comparison
enum AnotherError: Error {
case error1
case error2
case error3
}
let apiCallError: Error = APICallError.insufficientCredentials
let anotherError: AnotherError = AnotherError.error1
//Closure definition
var onError: APIError? = { inputError in
if let apiError = inputError as? APICallError {
print("APICallError")
}
if let anotherError = inputError as? AnotherError {
print("AnotherError")
}
}
//Above defined closure is called here below...
onError?(apiCallError)
onError?(anotherError)
Console Output (Works as expected):
APICallError
AnotherError
You need to use the enum rawValue constructor.
if let callError = APICallError(rawValue: error) {
if callError == .forbidden {
...
}
} else {
// was not an APICallError value
}

Return JSON result of Alamofire request from function Using Swift 4

I have a function that returns the JSON result from the alamofire request. Initially the idea was to retrieve one of the values from the JSON result and store these in the array but each time I try to return either the JSON result or the populated int array, I get an empty result at the end of the function. The request is:
func getSeats()-> JSON{
var json : JSON = JSON()
Alamofire.request(url , method: .get)
.responseJSON{
response in
switch response.result {
case .success:
json = JSON(response.result.value!)
case .failure:
KRProgressHUD.showError(withMessage: "Could not retrieve reserved seats")
}
}
return json
}
Initially, looking at the AlamoFire documentation, I tried to make use of the option of returning an array from the json result so I modified my code like this:
func getSeats()-> [Int]{
var json : JSON = JSON()
Alamofire.request(url , method: .get)
.responseJSON{
response in
switch response.result {
case .success:
json = JSON(response.result.value!)
case .failure:
KRProgressHUD.showError(withMessage: "Could not retrieve reserved seats")
}
}
return json.arrayValue.map { $0["seat_no"].intValue }
}
But this also returned an empty array which is why I had to change the function. The format of the JSON I receive is as follows:
[
{
"id": 1,
"bus_id": 11,
"seat_no": 6,
"arrived_at": "2018-01-16 20:58:57"
},
{
"id": 2,
"bus_id": 11,
"seat_no": 27,
"arrived_at": "2018-01-16 21:40:29"
}
]
I am very new to Swift and this is taking me forever to understand and work it out.
Your functions are returning the empty json variable before it actually gets set to JSON(response.result.value!). You shouldn't be returning the json like that, or anything at all really. Your function should take a closure argument instead, and the closure argument itself can take your json argument. Then you can execute the closure when you get the response back from the server and just pass it the parsed json.
I would recommend learning more about synchronous vs. asynchronous programming. Networking is asynchronous by default and it takes time for the data you're requesting to actually return in a response from the server.
Go research basic asynchronous programming, and networking with closures and completion blocks. Any basic iOS networking tutorial will cover these things.

compare error object return by alamofire

I'm using Alamofire with EVReflection, in case responseObject fails to parse the raw response string into an object, an response.error will have some value, in case of a different error, a different value will be set.
Not sure how to compare those error values, to handle different error.
in case of JSON parsing error, print(error) will output
FAILURE: Error Domain=com.alamofirejsontoobjects.error Code=1 "Data could not be serialized. Input data was not json." UserInfo={NSLocalizedFailureReason=Data could not be serialized. Input data was not json.}
Alamofire.request(...)
.responseObject { (response: DataResponse<UserData>) in
guard response.error == nil else {
print(response.error)
return
}
}
When your request fails, you will get an error of type AFError from Alamofire. You can actually check AFError.swift file to get familiar with possible values. This file have really good documentation for every case.
Since AFError is an Error, which is of type enum, you can check like following:
switch err {
case .parameterEncodingFailed(let reason):
// do something with this.
// If you want to know more - check for reason's cases like
// switch reason {
// case .jsonEncodingFailed(let error):
// … // handle somehow
// case .propertyListEncodingFailed(let error):
// … // handle somehow
// }
case .responseValidationFailed(let reason):
// do something else with this
…
}
And for every reason you have some helper functions, so you can get even more info. Just check documentation.

How to handle the response of all types of requests in one handler, but also uniquely handle every request with Alamofire and Moya

In my app I use Moya and Alamofire (And also Moya/RxSwift and Moya-ObjectMapper) libraries for all network requests and responses.
I would like to handle the response of all types of requests in one handler, but also uniquely handle every request.
For example for any request I can get the response "Not valid Version", I would like to avoid to check in every response if this error arrived.
Is there an elegant way to handle this use case with Moya?
Apparently that is very simple, You just should create your own plugin. And add it to your Provider instance (You can add it in the init function)
For example:
struct NetworkErrorsPlugin: PluginType {
/// Called immediately before a request is sent over the network (or stubbed).
func willSendRequest(request: RequestType, target: TargetType) { }
/// Called after a response has been received, but before the MoyaProvider has invoked its completion handler.
func didReceiveResponse(result: Result<Moya.Response, Moya.Error>, target: TargetType) {
let responseJSON: AnyObject
if let response = result.value {
do {
responseJSON = try response.mapJSON()
if let response = Mapper<GeneralServerResponse>().map(responseJSON) {
switch response.status {
case .Failure(let cause):
if cause == "Not valid Version" {
print("Version Error")
}
default:
break
}
}
} catch {
print("Falure to prase json response")
}
} else {
print("Network Error = \(result.error)")
}
}
}
I suggest to use generic parametrized method.
class DefaultNetworkPerformer {
private var provider: RxMoyaProvider<GitHubApi> = RxMoyaProvider<GitHubApi>()
func performRequest<T:Mappable>(_ request: GitHubApi) -> Observable<T> {
return provider.request(request).mapObject(T.self)
}
}
DefaultNetworkPerformer will handle all requests from you Moya TargetType. In my case it was GitHubApi. Example usage of this implementation is:
var networkPerformer = DefaultNetworkPerformer()
let observable: Observable<User> = networkPerformer.performRequest(GitHubApi.user(username: "testUser"))
here you 'inform' network performer that response will contain User object.
observable.subscribe {
event in
switch event {
case .next(let user):
//if mapping will succeed here you'll get an Mapped Object. In my case it was User that conforms to Mappable protocol
break
case .error(let error):
//here you'll get MoyaError if something went wrong
break
case .completed:
break
}
}

Using a Swift closure to capture a variable

I have this bit of code and it obviously errors out because when I use FOO in the return statement it's outside of the scope of the function. I know (I think I know) I need to use a closure to capture the variable but I can't figure out how to do that. Using Alamofire & SwiftyJSON. Any help would be great! Thanks!
func getPlayerID(named: String) -> String {
Alamofire.request(.GET, "URL", headers: headers)
.responseJSON { response in
let json = JSON.self(response.result.value!)
for var index = 0; index < json.count; index++ {
if json[index]["Name"].stringValue == named {
var FOO = json[index]["FOO"].stringValue
} // If Statement End
} // For Loop End
} // Alamofire Request End
// Return Statement for getPLayerID Function
return FOO
} // getPlayerID Function End
} // Player Struct End
The basic idea is that getPlayerID should not return anything, but rather should just have a parameter which is a closure, and once you retrieve the value you want to "return", you call the closure using that value as a parameter.
But, I'd suggest all sorts of other refinements here:
Build an array of the strings and return that
Check to see if result is a .Failure because you have no control over what various server/network issues may arise
Change the closure to detect and report errors
But hopefully this illustrates the basic idea:
Personally, I'd (a) make the String parameter to the completionHandler optional; (b) add another optional error parameter; and (c) add error handling to the getPlayerID:
func getPlayerID(completionHandler: ([String]?, ErrorType?) -> Void) {
Alamofire.request(.GET, "URL", headers: headers)
.responseJSON { request, response, result in
switch (result) {
case .Success(let value):
let json = JSON.self(value)
// variable to hold all of the results
var strings = [String]()
// populate the array of strings
for var index = 0; index < json.count; index++ {
if json[index]["Name"].stringValue == named {
strings.append(json[index]["FOO"].stringValue)
}
}
// call the completion handler with the strings
completionHandler(strings, nil)
case .Failure(_, let error):
completionHandler(nil, error)
}
}
}
And then, when you want to call it:
getPlayerID() { strings, error in
// use `strings` here
}
// but not here
If you make an asynchronous request you can not return a value received in response in the same function cause it needs time for request to be sent over network to the server and back. The best way to solve this out is to add callback parameter to your function instead of return value.
func getPlayerID(named: String, callback:(foo:String)->()) {
Alamofire.request(.GET, "URL", headers: headers)
.responseJSON { response in
let json = JSON.self(response.result.value!)
for var index = 0; index < json.count; index++ {
if json[index]["Name"].stringValue == named {
var FOO = json[index]["FOO"].stringValue
callback(foo: FOO) // you fire callback here..
} // If Statement End
} // For Loop End
} // Alamofire Request End
} // getPlayerID Function End
Callback is a block object that will be fired when your response will be received. So if response is not coming (for example, internet connection went down) callback will never fired.
Example how to use this:
self.getPlayerID("ototo") { (foo) -> () in
print("foo received = \(foo)")
}
Also there is a time span between sending the request and receiving the response. So it is a good practice to add UIActivityIndicatorView in UI of your app until response is arrived (and handle timeout if internet connection suddenly went down).

Resources