How do I safely unpack optionals (AlamoFire response object) in Swift - ios

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.

Related

Swift access dictionary value

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.

Swift 3 and JSON – Updating the database by running a URL in the background

1. Clicking the link causes a database update.
There is a certain link I have access to (let's pretend it's www.google.com), such that when I open it up in my browser, it updates a certain section of the JSON code in my database. Based on the numbers that make up a portion of the link, it adjusts a certain value in the data.
2. How do I run this link in the background of my iOS app?
I need to be able to "open" this link within the app, without actually opening up a UIWebview and visually visiting the site. I just need this JSON data inside the database to update on its own (with the user unaware that it even happened).
The problem I'm having here is that I simply don't know how this is done. How do I cause this link to be visited without opening up a Safari browser?
The best approach I've found for such functions is to treat them as if they were "AJAX" (or "REST", "API", etc.) - while these terms are often used (and for more seasoned programmers instantly give a certain thought), the end result is that they take information from your 'originator' and send to the 'server' for processing, which then replies with a 'response'. Once you get that concept in your head, this becomes a fairly simple activity.
(for our example, I will call this "API", as that does really suit {as #Mortiz suggested} this question best)
For Swift 3, there are several ways to do this, I'll show you two I've found and use for various functions:
DispatchQueue
For a 'one-time shot to a url that I know exists and will connect reliability', this is a good one to use (think of it as a 'quick-n-dirty' if you like....!)
DispatchQueue.global().async {
let data = try? Data(contentsOf: theURL!) //make sure your url does exist, otherwise unwrap in a if let check / try-catch
DispatchQueue.main.async {
// do stuff here with the data if you need
// you can get the response from the server and parse it out, set buttons in the app, etc.
}
}
Alamofire
For Swift 3, Alamofire is extremely popular and does a lot of great stuff. Check it out if you haven't already!
Alamofire.request("\(theURL!)").responseJSON { response in
print("result is ", response.result)
switch response.result {
case .success(let value):
// do stuff with the returned data
// like updating your internal database, etc.
print(value)
case .failure(let error):
print("There was an error")
// you can see the error response in various ways....
print(requested)
print(error)
print(response)
print(response.result)
}
}
Once you have your buttons in place (from your description it sounds like that is what your #1 is about), then in the function you call when it is clicked, drop in the code from above and 'do stuff' as you need.
This will make the update to the server automatically in the background (answering your #2) - the user won't notice anything unless there are connection issues to the internet, etc. (much too complex to get into here, though if you expect to have much of it, Alamofire is a great choice as it automatically retries, etc. (part of the great features you should check out)
A key piece of this is that you can take the response from the URL (send various bits of JSON data back from the server, then break it back down in the phone) and do 'whatever' with it.
Some things you can do (to hopefully give you more ideas - which is just about anything.....):
update data in the app (local storage, local variables, etc.)
update text (color, background) inside Buttons or Labels
process Alerts to the user (not your case, but sometimes you want to let them know what went on - certainly if it was an error in updating your server)
change Images (various things)
switch Views
Well, the list is as long as "things you can do in an app", so decide for yourself what you need to mod/update - this is "the" way to do it!
You could also use the UIWebView without ever showing it, like this (Swift 3):
func webView() {
let theWebView: UIWebView
theWebView = UIWebView(frame: UIScreen.main.bounds)
theWebView.delegate = self
if let theURL = URL(string: "your URL") {
let request = URLRequest(url: theURL)
theWebView.loadRequest(request)
}
}
Just don't add it to the view.

Why SKProductsRequestDelegate/SKRequestDelegate didFailWithError throws EXC_BAD_ACCESS on NSError?

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 \().

Custom, content based validator for Alamofire (in Swift)

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!

What is wrong with this line of Swift iOS Code?

I have created an iOS app using Swift and everything is working fine and dandy on the simulator. I get no errors or crashes at all, but when I submit my app to put up on the app store Apple rejects it and lets me know that it crashes when the user makes a selection. I cannot recreate this error/crash. I took the crash logs and symbolicated them. This line of code came up as the culprit for the crashes:
linksToPass = getLinks(season) as [String:[String]]
This line is trying to store the resulting Dictionary from the getLinks() function I created. It for sure is getting a dictionary and if there is no dictionary to send back I create a dictionary which has error information in it, so it is for sure returning a dictionary in that format no matter what. Seeing as I cannot recreate the crash, I am just trying to error check this line of code in any way possible so it does't crash when I resubmit to Apple.
I tried checking if the resulting dictionary was nil like so:
if(getLinks(seasons) != nil){
linksToPass = getLinks(season) as [String:[String]]
}
This is not valid though, and XCode lets me know that UInt8 is not compatible with NSDictionary or something of that nature.
I then fixed that line and changed it to this:
if(getLinks(seasons) != ["":[""]]){
linksToPass = getLinks(season) as [String:[String]]
}
I am just not sure if this is even a good way to check for errors. I was wondering if there were any suggestions on how I may go about making sure this line does not fail and result in a crash. Thank you very much.
EDIT:
Here is my getLinks() function if that helps add more info to the problem:
var season = ""
let hymn_links = Hymn_Links()
func getLinks (nameofseason:String) -> NSDictionary
{
switch (nameofseason)
{
default:
return ["Maps Not Found": []]
}
}
EDIT #2:
This is my updated getLinks() function with the use of optionals.
func getLinks (nameofseason:String) -> NSDictionary?
{
switch (nameofseason)
{
default:
return nil
}
}
Also in my statement of linksToPass I changed it to:
if let links = getLinks(season) as? [String:[String]]
{
linksToPass = links
hymnnames = [String] (linksToPass.keys)
}
There are some known issues with the Swift optimiser. Some people have resorted to shipping with debug builds.
My suggestion would be to test with an optimised build to see if you can reproduce it. You can then try shipping a debug build to the App store.
General Code Comments
Why are you returning an NSDictionary rather than a Swift dictionary anyway? Without knowing the contents and creation method for your hymn_links object I can't be sure how good it is.
I would avoid as casts until Swift 1.2 and stick to using as? and then handling the nil case. At least in your "Edit 2" a nil will cause a crash as nil cannot be cast to [String:[String]] although [String:[String]]? should be possible.
Can you guarantee that all of the items returned by the switch statement will never under any circumstances be nil? If not getLinks should return an Optional.
Note that is is virtually impossible for getLinks to know that one of the items will never be nil and in Swift un-handed nils are a crash waiting to happen. Unless all these methods correctly handle nil.
Return an Optional and handle that in the statement that calls getLinks.
Languages handle nils differently, Objective-C handles them rather well, Java and Swift by crashing. But Swift has a mechanism to handle nils without crashing: Optionals, use it.

Resources