Alamofire request coming up nil - ios

I'm developing an iOS app which user WebServices and I find Alamofire just perfect for what I'm doing but I'm having a problem; the app asks the user to login which is an Alamofire call and does it just fine.
The problem is, it has to create a collection view based on the content of another Alamofire request but is always nil.
func getJSON(URLToRequest: String) -> JSON {
let comp:String = (prefs.valueForKey("COMPANY") as? String)!
let params = ["company":comp]
var json:JSON!
let request = Alamofire.request(.POST, URLToRequest, parameters: params).responseJSON {
response in
switch response.result {
case .Success:
if let value = response.result.value {
json = JSON(value)
}
default:
json = JSON("");
}
}
debugPrint(request.response)
return json;
}
The same codeblock works perfect for the Login but doesn't in this case BTW the debug Print always print nil

You're trying to access to request.response before it has been set, remember that Alamofire works asynchronously, so you have to return in your case the JSON using closures, but remember that Alamofire also returns an error, so I strongly recommend use the following code instead:
func getJSON(URLToRequest: String, completionHandler: (inner: () throws -> JSON?) -> ()) {
let comp:String = (prefs.valueForKey("COMPANY") as? String)!
let params = ["company":comp]
let request = Alamofire.request(.POST, URLToRequest, parameters: params).responseJSON {
response in
// JSON to return
var json : JSON?
switch response.result {
case .Success:
if let value = response.result.value {
json = JSON(value)
}
completionHandler(inner: { return json })
case .Failure(let error):
completionHandler(inner: { throw error })
}
}
The trick is that the getJSON function takes an additional closure called 'inner' of the type () throws -> JSON?. This closure will either provide the result of the computation, or it will throw. The closure itself is being constructed during the computation by one of two means:
In case of an error: inner: {throw error}
In case of success: inner: {return json}
And then you can call it like in this way:
self.getJSON("urlTORequest") { (inner: () throws -> JSON?) -> Void in
do {
let result = try inner()
} catch let error {
print(error)
}
}
I hope this help you.

Related

how to determine the specific 409 error from an AFError?

I have a method that returns a Single<(HTTPURLResponse, Any)> doing a call to a webservice.
This call returns an 409 for multiple reasons and this reason is passed as a JSON in the response.
I know the JSON is in the data attribute of the DataResponse object but I would like to have it in the AFError that I pass when an error occurs. I want to display the specific 409 error message related to the JSON response to the user to allow him understand what happened.
How could I do that ?
I searched for that in Stackoverflow and also on the github of Alamofire but couldn't find any help to my case.
return Single<(HTTPURLResponse, Any)>.create(subscribe: { single in
let request = self.sessionManager.request(completeURL, method: httpMethod, parameters: params, encoding: encoding, headers: headers)
request.validate().responseJSON(completionHandler: { (response) in
let result = response.result
switch result {
case let .success(value): single(.success((response.response!, value)))
case let .failure(error): single(.error(error))
}
})
return Disposables.create { request.cancel() }
})
I'm working with Alamofire 4.9.1
request.validate().responseJSON { (response) in
let statusCode = response.response?.statusCode ?? 0
guard statusCode != 409 else {
if let data = response.data, let errorJson = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
let errorMessage = errorJson["message"] as? String
let customError = CustomError(message: errorMessage)
single(.error(customError))
}
return
}
let result = response.result
switch result {
case let .success(value): single(.success((response.response!, value)))
case let .failure(error): single(.error(error))
}
}
I guess you can achieve your requirement by this way. create a custom Error class to pass the error to completion. dont forget to call completion if errorJson is not serialised.
class CustomError: Error {
var localizedDescription: String { message ?? "" }
var message: String?
init(message: String?) {
self.message = message
}
}

Swift 4 - Call nests with PromiseKit

Good morning everyone!
I'm doing an app with Swift 4.2 with Xcode10 and calls I manage with Alomofire together with PromiseKit.
At one point I need to load a screen at the beginning of the app with the terms of use, but only if they have updated it. And if the version has changed, that same call has the URL with the new endpoint. That endpoint contains the text to be displayed.Therefore, in that last case. We would have to make two calls.
Then I explain what I want to do:
To know if they have updated the version number I make a first call and check with the last one that I have saved in the device (NSUsserDefaults).The first call returns the following:
{ "legal_version": "1",
"legal_URL": "http://statics.....html" }
If it's the same version, I do not show the screen.
If the version has changed, I want to make a second call (with the URL that carries the answer of that first call "legal_URL")
he problem is that I do not know how to make that double call blocking. So do not load the main screen without checking the version number. And without knowing if I have to show or not the screen of legal terms again.
And all this is PromiseKit and nested calls.
Many thanks, for your help.
[Code updated]
let legalWarningRepository = LegalWarningRepository()
firstly {
legalWarningRepository.get(endpoint: "http://myserver.com/version")
}.then { json in
if let remoteVersion = json["version"] as? String, let
legalUrl = json["legal_URL"] as? String,
remoteVersion != localVersion {
return legalWarningRepository.get(endpoint: legalUrl)
}
}.done { json in
if json == nil {
// display main screen
}
else {
// display legal terms
}
}.catch { err in
print(err)
}
And inside "legalWarningViewController" I have the get method that you have passed me:
func get(endpoint: String) -> Promise<[String: Any]> {
return Promise { seal in
Alamofire.request(endpoint)
.validate()
.responseJSON { response in
switch response.result {
case .success(let json):
guard let json = json as? [String: Any] else {
return seal.reject(AFError.responseValidationFailed(reason:
.dataFileNil))
}
seal.fulfill(json)
case .failure(let error):
seal.reject(error)
}
}
}
}
On your first screen you can display an activity indicator while waiting for responses. When you have your responses you can display the appropriate view then.
In order to keep things simple you can create a generic method that "promisify" the Alamofire call:
func get(endpoint: String) -> Promise<[String: Any]> {
return Promise { seal in
Alamofire.request(endpoint)
.validate()
.responseJSON { response in
switch response.result {
case .success(let json):
guard let json = json as? [String: Any] else {
return seal.reject(AFError.responseValidationFailed(reason: .dataFileNil))
}
seal.fulfill(json)
case .failure(let error):
seal.reject(error)
}
}
}
}
Then in your the viewDidLoad or viewWillAppear of your home screen you can do this kind of logic:
firstly {
get(endpoint: "http://myserver.com/version")
}.then { json in
if let remoteVersion = json["version"] as? String, let legalUrl = json["legal_URL"] as? String, remoteVersion != localVersion {
return get(endpoint: legalUrl)
}
}.done { json in
if json == nil {
// display main screen
}
else {
// display legal terms
}
}.catch { err in
print(error.localizedDescription)
}
In your case a single call may be possible if you display the legal term in a WKWebView.
firstly {
get(endpoint: "http://myserver.com/version")
}.done { json in
if let remoteVersion = json["version"] as? String, let legalUrl = json["legal_URL"] as? String, remoteVersion != localVersion {
// display legal terms in a web view
}
// display main screen
}.catch { err in
print(error.localizedDescription)
}

cannot parse response alamofire swift 3

I'm trying to make a Get request with Alamofire for swift. When I run in Simulator or device with iOS 10 it works fine. When I run my app on Devices with iOS 9.x or prior, I'm getting "cannot parse response". The JSON response is right. I checked in postman.
There's my code:
lass func getStores(latitude:Float, longitude: Float, completion : #escaping (Array<Store>?, NSError?) -> ()) {
let latString: String = "\(latitude)"
let lonString: String = "\(longitude)"
var listStores: Array<Store> = []
let urlFull : String = ConstantHelper.kUrlStore
Alamofire.request(urlFull, method: .get, parameters: ["latitude":latString, "longitude": lonString], encoding: JSONEncoding(options: []), headers: [:]).validate()
.responseJSON { response in
switch response.result {
case .success:
if let repoJSON = response.result.value {
print(repoJSON)
let jsonArray = repoJSON as! NSArray
for item in jsonArray {
guard let store = Store(json: item as! JSON) else
{
print("Issue deserializing model")
return
}
listStores.append(store)
}
completion(listStores, nil)
}
break
case .failure(let error):
completion(nil, error as NSError?)
break
}
}
}
}
I solved my problem with the following code without JSON enconding in request:
Alamofire.request(urlFull, method: .get, parameters: ["latitude":latString, "longitude": lonString]).validate()
I don't know why works in Swift 2.x and in Swift 3.0 with iOS 10 with encoding and don't in ios 9...but the code above works in all situations. And its really no needed to enconding this request

Returning a Promise from PromiseKit/Alamofire

I'm trying to do this seemingly trivial thing:
static func list() -> Promise<[Activity]> {
let endpoint = "\(self.baseUrl)/v1/activities"
return Promise { fulfill, reject in
self.fetchHeaders { (headers) in
return Alamofire.request(
endpoint,
method: .get,
parameters: nil,
encoding: JSONEncoding.default,
headers: headers
).validate().responseJSON().then() { response in
guard let json = response as? JSON else {
reject(ActivityError.parse("Malformed JSON"))
}
guard let jsonActivities = json["activities"] as? [JSON] else {
reject(ActivityError.parse("Missing field"))
}
var activities: [Activity] = []
for jsonActivity in jsonActivities {
guard let activity = Activity(json: jsonActivity) else {
reject(ActivityError.parse("Unable to parse an Activity object"))
}
activities.append(activity)
}
fulfill(activities)
}.catch { error in
reject(ActivityError.network("HTTP response failure"))
}
}
}
}
However, the compiler (rightfully) complains that:
'guard' body may not fall through, consider using 'return' or 'break'
to exit the scope
I understand I need to return a Promise here. I just can't figure out what exactly to put below the reject() and fulfill() calls.
There's nothing wrong with reject or fulfill calls. The issue is that after you reject in your guard statement, you also have to return to exit the closure:
guard let json = response as? JSON else {
reject(ActivityError.parse("Malformed JSON"))
return
}
guard let jsonActivities = json["activities"] as? [JSON] else {
reject(ActivityError.parse("Missing field"))
return
}
The key point is that you do not want to conflate the promise that is returned by this method (which is later satisfied by fulfill or reject), with the fact that within this closure, you have to immediately exit the closure with a return in the guard clause.
I cannot reproduce this issue with your code (because you haven't provided a MCVE and there are references here that I cannot resolve). But here is a simplified rendition of your code illustrating the use of guard:
So, if not using PromiseKit/Alamofire, you can do:
func list() -> Promise<[String: Any]> {
return Promise { fulfill, reject in
Alamofire.request(url)
.validate()
.responseJSON { response in
switch response.result {
case .success(let json):
guard let dictionary = json as? [String: Any] else {
reject(ActivityError.malformed("not a dictionary"))
return
}
fulfill(dictionary)
case .failure(let error):
reject(error)
}
}
}
}
As you can see, you are returning a Promise, but inside the Alamofire closure, you simply are exiting your guard statement.
If you're using PromiseKit/Alamofire and call then, you presumably want to create a promise that it can return, such as:
func list() -> Promise<String> {
return Alamofire.request(endPoint)
.validate()
.responseJSON()
.then { value in
return Promise { fulfill, reject in
guard let dictionary = value as? [String: Any], let name = dictionary["name"] as? String else {
reject(ActivityError.malformed("not dictionary"))
return
}
fulfill(name)
}
}
}
Or if that's too hairy, you can pull out that parsing of the value:
func list() -> Promise<String> {
return Alamofire.request(endPoint)
.validate()
.responseJSON()
.then { value in
self.parse(value)
}
}
func parse(_ value: Any) -> Promise<String> {
return Promise { fulfill, reject in
guard let dictionary = value as? [String: Any], let name = dictionary["name"] as? String else {
reject(ActivityError.malformed("not dictionary"))
return
}
fulfill(name)
}
}
But, either way, even when using PromiseKit/Alamofire, you still just return within the guard clause.

Return statement returns before information is received

I am having a little trouble with my swift code. The ending return statement runs before the the JSON value is stored so it keeps giving me nil. How can i do the return after the value been received?
func getArticleInfo(Id: String) -> String {
let url = val1 + val2 + val3
Alamofire.request(.GET, url).responseJSON { response in
switch response.result {
case .Success:
if let value = response.result.value {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let json = JSON(value)
let singleAsset = json["url"].string
}
}
case .Failure(let error):
print(error)
}
}
return singleAsset
}
Thanks for the help the other problem I’m having. SEE BELOW
I am trying to get the categories to populate with all the information then call the vc.displayCatName() after its done. But it does it late and i have to refresh the page before i can see the information.
Above that is just me assigning the JSON values to the keys that populate categories BELOW. But the vc.displayCatName() is a function from another view controller but it gets run before the category values are populated. So the only way i see the values is if i refresh the page manually using the Pull to Refresh. So i want the information to be populated then vc.displayCatName() should run
self.getAsset(id!) { (result) -> Void in
print("this is result \(result)")
let categories = Categories (categoryName: catName, imageId: id, catIdNumber: catIdNumber, imageUrl: result)
vc.cats.append(categories)
}
}
}
dispatch_async(dispatch_get_main_queue()) {
vc.displayCatName()
}
}
The reason for this is because the call that you are making is asynchronous in nature. Instead consider using a completion handler.
func getArticleInfo(Id: String, success: (String) -> Void ) {
let url = "www.Test.com"
Alamofire.request(.GET, url).responseJSON { response in
switch response.result {
case .Success:
if let value = response.result.value {
let json = JSON(value)
let singleAsset = json["url"].string
success(singleAsset!)
}
case .Failure(let error):
print(error)
success("TEST")
}
}
}
To Call:
getArticleInfo("test") { (asset) -> Void in
print(asset)
}

Resources