I have a Rails api that I am using to develop my iOS application with. I am currently using alamofire to perform HTTP requests and am running into an issue when trying to access dictionary keys.
Type of the response from Alamofire is __NSDictionaryI.
AF.request("\(url)/wishlist", method: .post, parameters: parameters, encoding: URLEncoding.default).responseJSON { response in
switch response.result {
case .failure(let err):
print(err)
case .success(let res):
print(type(of: res))
print(res["message"])
}
}
I am coming from Ruby where we access hash keys like hash["key"] but am running into issues when trying to do that here. The response from this request prints
{
message = "Care was created";
status = ok;
}
I was hoping I could do res["message"] to access that value to pass to a toast message. However, I am unable to compile due to the following error
Value of type 'Any' has no subscripts
Can anybody explain what is happening here and why I am unable to capture this value?
Your first step should be to learn Swift and forget about any assumptions into which Ruby may have led you. Ruby and Swift are opposites. Ruby has no typing ("duck typing") and any variable can adopt any value. Swift has strict typing and a variable must declare its type at the outset and can never change that type.
You can supply a type by casting the response result to a definite type. For instance, you might cast it to [String:Any]. That's a dictionary (similar to Ruby hash) and can be subscripted.
It would be better, however, to decode the response content into a struct with a message property and status property, each of those being properly typed, rather than settling for the typeless responseJSON. You shouldn't say Any unless you really have to, and you rarely have to.
Related
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed last year.
I am building a mobile app with swift, and am having some syntax issues as I am not a developer. The structure and logic of the application is really rough and surely incorrect, however we just need something that functions. (It is a school project and my team got no devs).
Anyways, we have a MySQL database that will be used as a middleman between our badge server/admin app, and our mobile app. Currently when you go to https://gatekeeperapp.org/service.php , you will see the current database data, taken by a php script and hosted there as JSON. Currently in Swift I have a struct with a function that takes this JSON data, and maps it to variables. The idea is to then pass these pulled variables into a separate set of functions that will check the pulled long/lat against the mobile devices location, and then return whether they match or not. This value would be updated, re-encoded to JSON, and pushed to a web service that would go about changing the values in the database so the badge server could use them.
Where I am currently I can see that values are being pulled and mapped and I can set a variable in a separate function to the pulled value, but then I can only seem to output this value internally, rather than actually use it in the function. I get a type error saying that the pulled values are of type (). How can I properly use these values? Ultimately I think I would want to convert the () to a double, so I could properly compare it to the Long/Lat of the device, and then will need to re-encode the new values to JSON.
Swift Code -- struct function
Swift code -- JSON struct
Swift code -- using pulled data
Your closure is called asynchronously, which means that the outer function where you are expecting to use the values has already returned by the time the closure is called. Instead, you probably need to call some other function from the closure, passing the values you've received.
class MyClass {
func fetchUserData() {
UserData().fetchUser { [weak self] user, error in
DispatchQueue.main.async {
if let user = user {
self?.handleSuccess(userID: user)
} else if let error = error {
self?.handleError(error)
}
}
}
}
private func handleSuccess(userID: String) {
print(userID)
// Do something with userID. Maybe assign it to a property on the class?
}
private func handleError(_ error: Error) {
print(error)
// Handle the error. Maybe show an alert?
}
}
I'm trying to check if error.localizedDescription contains a certain string but i keep getting a crash
if error.localizedDescription.contains("\"api.error.cardRejected.2000\"") {
failCompletion()
}
I have even tried to even use another way
if let description = (error! as NSError).userInfo[NSLocalizedDescriptionKey] as? String {
if description.contains("api.error.cardRejected.2000") {
failCompletion()
}
}
I still keep getting the same crash in the logs saying
-[__NSDictionaryM domain]: unrecognized selector sent to instance 0x60000046b520
It works when i check using the debugDescription but i would like to check using the localizedDecription since the debug one only works when debugging
NSError localized description is autogenerated from what's inside, here is what API tells:
/* The primary user-presentable message for the error, for instance for NSFileReadNoPermissionError: "The file "File Name" couldn't be opened because you don't have permission to view it.". This message should ideally indicate what failed and why it failed. This value either comes from NSLocalizedDescriptionKey, or NSLocalizedFailureErrorKey+NSLocalizedFailureReasonErrorKey, or NSLocalizedFailureErrorKey. The steps this takes to construct the description include:
1. Look for NSLocalizedDescriptionKey in userInfo, use value as-is if present.
2. Look for NSLocalizedFailureErrorKey in userInfo. If present, use, combining with value for NSLocalizedFailureReasonErrorKey if available.
3. Fetch NSLocalizedDescriptionKey from userInfoValueProvider, use value as-is if present.
4. Fetch NSLocalizedFailureErrorKey from userInfoValueProvider. If present, use, combining with value for NSLocalizedFailureReasonErrorKey if available.
5. Look for NSLocalizedFailureReasonErrorKey in userInfo or from userInfoValueProvider; combine with generic "Operation failed" message.
6. Last resort localized but barely-presentable string manufactured from domain and code. The result is never nil.
*/
open var localizedDescription: String { get }
so, it is crashed (probably at step 6.) then this NSError is incorrectly constructed - so find who & how constructed it, possibly at some layer on underlying errors some key of userInfo unexpectedly is set as NSDictionary instead of NSError.
I am using the below code to download data from a server. According to Crashlytics, we see a crash occurred (EXC_BREAKPOINT) on the conditional evaluation (the 'if' statement). I suspect it is because the code unpacking the optional member "statusCode" - I am new to Swift (10 years doing Obj-C) - and I am not certain what the best, safest way is to unpack this variable without causing a crash.
Note that this app its using SwiftyJSON, though I do not think that is relevant.
Alamofire.request(url).responseJSON { (response) in
if (response.response?.statusCode)! >= 200 && (response.response?.statusCode)! < 300
Alamofire.request(url).validate().responseJSON { response in
switch response.result {
case .success(let json):
// do something with json
case .failure(let error):
// handle error
}
}
The validate() method replaces your line checking the statusCode. It defaults to using acceptableStatusCodes which are 200..<300.
I think that's the best way to handle this specific case.
For more general cases, you should avoid force unwrapping. Unwrap the optional using guard or if let. The Swift docs explain that in detail.
I use SKProductsRequest to download product infos from App Store.
When I test a connectivity loss on my device, the request fails, but my app crashes within the SKRequestDelegate when I try to NSLog the error:
What am I doing wrong ? Another curious thing to me is that Expression Inspector is able to display NSError.debugDescription...
It fails on the first request, so there is no possible bug relative to multiple uses of productRequest variable (which is a strong ref in my swift class).
I finally found the reason. It is not related to SKProductsRequest!
I think there is a nasty bug with NSLogand string interpolation because when I replace:
NSLog("Failed: \(error.debugDescription)")
by
print("Failed: \(error.debugDescription)")
all is fine!
Apparently, the content of the error message can provoke a EXC_BAD_ADDRESS in NSLog (even without string interpolation in fact: NSLog(error.debugDescription) fails too).
Related anwser: https://stackoverflow.com/a/29631505/249742
NSLog("%#", error.debugDescription)
seems to work fine in every cases.
Perhaps NSLog(variable) is a misuse of NSLog, but I think NSLog(\(variable)) should be interpreted like NSLog("%#", variable). Else, there is no reliable way to interpolate strings with NSLog using the swift way \().
I know you can add a status code and content type validators, but I'd really love to be able to write my own validator based on the result content - basically I want to make sure the json I'm getting back contains some fields, and that their value is valid.
The way the app I'm working on is currently designed is there's a Server class that handles all the api calls, and the response object is returned to whoever called it, so they can do their logic / update ui, etc.
Now I have a status code validator on all the requests, so I don't need to have it on all external, but I have several apis, that require that custom validation logic, which means I have to add it in all the places that call it, AND that I can't use this amazing syntax:
switch resp.result {
case .Success(let value):
print("yay")
case .Failure:
print("nay")
}
I'd love any answer/pointer that can help me find a solution,
Thank you all so much in advance! :)
I wound up having this exact same question and found out what you want to do is write your own response serializer and stop using .validate().
The serializer I'm using is very, very close to the out-of-the-box JSONResponseSerializer, except I make a check for an error.
The only change I make to the stock serializer is within the do-catch statement:
do {
let JSON = try NSJSONSerialization.JSONObjectWithData(validData, options: options)
if let responseDict = JSON as? NSDictionary, apiError = NSError.APIErrorFromResponse(responseDict) {
return .Failure(apiError)
}
return .Success(JSON)
} catch {
return .Failure(error as NSError)
}
APIErrorFromResponse is simply an extension method on NSError that checks the JSON for an error dictionary and populates a custom NSError out of that.
Hopefully this points you in the right direction if you haven't already found a solution!