Proper Error handling in Swift - ios

Hey stackoverflow members,
I work really hard on getting better on Swift, now I have a trivial problem. I'm a former C# developer so Error Handling until now was try {}... catch {}... Message... Done!
Now I'm developing an App which uses some JSON APIs. It's all working, downloading JSON Data, pack them into my Objects but there is one problem. The proper Error Handling..
I have the following code to download & parse JSON:
//Download & Parse JSON
func getJSON(urlToRequest: String) -> NSDictionary {
var url: NSURL = NSURL(string: urlToRequest)
var jsonRequest: NSURLRequest = NSURLRequest(URL: url)
var jsonResponse: AutoreleasingUnsafePointer<NSURLResponse?> = nil
var error: NSError?
var dataValue: NSData = NSURLConnection.sendSynchronousRequest(jsonRequest, returningResponse: jsonResponse, error:&error)
if error.description.isEmpty {
var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(dataValue, options: NSJSONReadingOptions.MutableContainers, error: &error) as NSDictionary
if error.description.isEmpty {
return jsonResult
}
else {
return NSDictionary(object: "Error: Something with parsing went wrong :(", forKey: "error")
}
}
else {
return NSDictionary(object: "Error: There was an error with your connection :(", forKey: "error")
}
}
The error part is just temporary, the problem is my ViewController just calls one function to get the whole data for the week (days, matchups and so on)
I call it this way:
var rWrapper = RiotWrapper()
let lcsWeek: Week = rWrapper.getWeek("2014-07-07")
My getWeek method calls 3-4 functions which all parse JSON data in relation to the previous responses.
Ok, long story short question: I want to abort all Tasks if JSON or HTTP fails and fill my TableView just with an error message, how can I achieve this?
Something like: if error occurred -> Stop whatever you are doing -> return for example a null erm.. nil for week -> print error
Can someone help me? If someone have some lecture according this topic it will be fine also :D
Thanks in advance!
Btw: sorry for my "bad" english

All of your JSON parsing methods including getWeek should return a tuple with an optional return value and an error. If at any point you get an error in one of your methods from the JSON parsing, just immediately return the error with nil for the return value. Each method up the chain should check for an error from the previous methods and immediately return the error if it finds one:
func getWeek(string : String) -> (Week?, NSError) {
let (result, error) = self.otherMethod()
if error {
return (nil, error)
}
// continue happily ...
}

I would suggest having your function return an optional. It is easy to test and functions can be chained and shortcut.
func getWeek(string : String) -> Week? {
...
if error {
return nil
}
}
This can be quickly evaluated like this:
if let week = getWeek("2014-07-07") {
// handle success case
} else {
// handle nil
}
and check here for how to string together a number of functions that return optional:
https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html

Related

NSOperationQueue NSOperation(QOS_Legacy) issue

I deployed my code before Swift 2 came out and with Crashlytics I get about 2-3 crashes a day supposedly in this func. I can't, for the life of me, figure out what's going on. I have thousands of users but out of the myriad that use the app I end up getting about 8-10 a day where Crashlytics reports something wrong.
The error is:
_TFZFC11 GProxy8GetAsyncFMS0_U_S_14Deserializable__FTGCS_10
GRequestQ__8callbackFGCS_11 GResponseGSaQ0___
T__T_U_FTGSQCSo6NSData_GSQCSo13 NSURLResponse_GSQCSo7 NSError__T_
(GProxy.swift:141)
I am making a request to my server and processing an array back and doing something with it in my callback (further up the chain somewhere else in my app).
Here is the code:
class func GetAsync<R, T: Deserializable>(request: GRequest<R>, callback: (GResponse<Array<T>>) -> ())
{
var list = Array<T>()
var response = GResponse<Array<T>>()
let serverRequest = NSMutableURLRequest(URL: NSURL(string: API_URL + request.Url)!)
serverRequest.HTTPMethod = "GET"
let task = NSURLSession.sharedSession().dataTaskWithRequest(serverRequest,
completionHandler: {
data, r, error in
if error != nil {
response.Status = .ERROR
} else {
do
{
let responseArray = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSArray
for item: AnyObject in responseArray {
let row = T(dict: item as! NSDictionary)
list.append(row)
}
response = GResponse<Array<T>>(status: ResponseCode.OK, value: list)
}
catch let err as NSError{
//I now log here (this is new code, before I was not)
}
}
callback(response)
})
task.resume()
}
Are there any major faux pas here where a crash may occur? See any glaring errors or potential concerns? I don't have the actual source anymore of when I deployed (I made changes to this class so I don't recall what line 141 is, but the crash message seems to do with NSURLResponse and the like. Any thoughts?
Thanks so much!

Parse iOS - How to capture the findObjects() error being thrown?

I am coding in Swift and attempting to use the findObjects() function for the Parse iOS SDK. However, I can't seem to figure out what type of error is being thrown by Parse if this function call fails. I'm a novice in Swift so that may be my issue. I attempt to call the function in a do->catch block and use the try keyword on the function call however I'm not sure what to catch. I can catch the error using the _ but I would like to grab the description from the error. Thanks!
P.S. I don't want to use the findObjectsInBackground() method.
do {
let object = try query.getFirstObject()
// do something with the object
} catch _ {
// this is where I would like to print out the error description
}
In Obj-C, which I assume will be similar, I print out the error.userInfo[#"error"] parameter of the NSError that is returned.
All you need is print(error). An example here:
func getReferenceNumberAsStringSync() -> String? {
let query = PFQuery(className: "PropertyCount")
do {
let object = try query.getFirstObject()
if let referenceNumber = object["count"] as? Int {
return String(referenceNumber)
}
} catch {
print(error)
}
return nil
}

save session in http request swift

in my app I'm using JSON and I made a session recently so if I would like to make some http request to get data for a specific user, the user must log in before (also used by http request).
in the safari when I entering the url's of login and then the url of receive data, it does that as needed.
but in my app, I first call login and then the url for getting data, but it's probably starting a new session in every url request which leads me to get an error and not receive the data.
my url request function is:
static func urlRequest (adress: String, sessionEnded: (NSDictionary->Void)?){
println(adress)
var urli = NSURL(string: adress)
var request = NSURLRequest(URL: urli!)
var rVal = "";
self.task = NSURLSession.sharedSession().dataTaskWithURL(urli!) {(data, response, error) in
var parseError: NSError?
let parsedObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.AllowFragments,
error:&parseError)
let po = parsedObject as NSDictionary
if let a = sessionEnded{
sessionEnded!(po)
}
}
task!.resume()
}
thanks in advance!!
You have shared only half of the puzzle with us, the client code. We can't comment on why the app isn't working with a clearer picture of what the server API. For example, once you "log in", how do subsequent queries confirm that the request is coming from valid session. Furthermore, you report that "every url request which leads me to get an error". Well, what error do you receive? You have to be far more specific regarding the precise errors/crashes you are receiving. BTW, are you logging on to some service with a well-defined API or are you writing that code yourself, too?
Having said that, I might suggest a few refinements to this method:
The sessionEnded (which I've renamed completionHandler to conform to informal standard naming conventions), probably should return an optional NSError object, too, so the caller can detect if there was an error.
Your unwrapping of the sessionEnded completion handler can be simplified to use ?.
When you parse the object, you should feel free to perform the optional cast, too.
You probably want to detect a network error (in which case data would be nil) and return the network NSError object.
Minor point, but I'd probably also rename the function to conform to Cocoa naming conventions, using a verb to start the name. Perhaps something like performURLRequest.
This is your call, but I'd be inclined to have the method return the NSURLSessionTask, so that the caller could use that task object if it wanted to (e.g. save the task object so that it could cancel it later if it wanted to).
Thus, that yields something like:
func performURLRequest (address: String, completionHandler: ((NSDictionary!, NSError!) -> Void)?) -> NSURLSessionTask {
let url = NSURL(string: address)
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
if data == nil {
sessionEnded?(nil, error)
} else {
var parseError: NSError?
let parsedObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error:&parseError) as? NSDictionary
completionHandler?(parsedObject, parseError)
}
}
task.resume()
return task
}
And you'd invoke it like:
performURLRequest("http://www.example.com/some/path") { responseDictionary, error in
if responseDictionary == nil {
// handle error, e.g.
println(error)
return
}
// use `responseDictionary` here
}

Parse JSON in SWIFT

Hi i try to find a way to parse JSON in SWIFT, this works great for me but i run into a problem.
I let the user enter a username that is used for the JSON URL -> if the user type in a valid username all works fine.
But if he enter a wrong username my parsing fails, this is correct too, but for now my app only crashes and i looking for a way to make a work around.
This is my Code where it crashes,
let url0 = NSURL(string: newUrlPath!)
let session0 = NSURLSession.sharedSession()
let task0 = session0.dataTaskWithURL(url0!, completionHandler: {data, response, error -> Void in
if (error != nil) {
println(error)
} else {
let summonorID_JSON = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
The Xcode Error
Error Domain=NSURLErrorDomain Code=-1002 "The operation couldn’t be
completed. (NSURLErrorDomain error -1002.)" UserInfo=0x7c12d610
{NSErrorFailingURLKey=XX, NSErrorFailingURLStringKey=XX,
NSUnderlyingError=0x7c12c8d0 "The operation couldn’t be completed.
(kCFErrorDomainCFNetwork error -1002.)"} fatal error: unexpectedly
found nil while unwrapping an Optional value
All is fine cause this is the return page i get from my Request
https://br.api.pvp.net/api/lol/br/v1.4/summoner/by-name/smirknaitiax?api_key=5c7d4d4f-f320-43d5-8647-643c9f6ee5de
And yes he can't parse this into a NSDirectory as its no JSON that returns (as its normally is) is there a way to take care that if this page comes up (so the user entered a wrong username) that i can exit my loop/take a other way ;)?
You are using many operations which could all fail, and Swift is quite unforgiving about failure. Your code will crash if newURLPath is nil, if url0 is nil because newURLPath wasn't a valid URL.
So your URL request might return an error (the request itself failed), but you have the case that the URL request succeeded but gives unexpected results (not a JSON dictionary). Your code ending in "as NSDictionary" tells Swift: "I know I might not get a dictionary, but convert what you get to a dictionary and crash if this doesn't work". Just change this to
if let parsedJSON = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil)
{
// Will still crash if the server sends a valid JSON array
let summonorID_JSON = parsedJSON as NSDictionary
}
else
{
// data wasn't valid JSON, handle it.
}
The difference is that the optional value returned by the JSON parser will be accepted without crashing, and you check whether you received valid JSON or not.
Since you are getting 404 on this request, I assume that this will happen every time something is bad with username, you should handle server response to fit that. First thing will be to check what server returned:
let httpResp: NSHTTPURLResponse = response as NSHTTPURLRespons
At this point you can access statusCode property, that will tell you if request was good or not (404). Having that information you can decide what to do, and for example, you can modify your code something like this:
let url0 = NSURL(string: newUrlPath!)
let session0 = NSURLSession.sharedSession()
let task0 = session0.dataTaskWithURL(url0!, completionHandler: {data, response, error -> Void in
if (error != nil) {
println(error)
} else {
let httpResp: NSHTTPURLResponse = response as NSHTTPURLRespons
httpResp.statusCode != 404 {
let summonorID_JSON = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
} else {
// Handle error at this point, tell user to retype username etc.
}
})
NSURL is a failable initializer and exactly this happens when you give an invalid url: It fails to initialize.
So wrap your code in an conditional unwrap:
if let url0 = NSURL(string: newUrlPath!) {
...
}
The url0 becomes nil if user enter wrong data. If you use the nil value as url0! app will crash.
When you add a ! after a variable you tell the compiler the value will not be nil.
so to avoid the crash, you have to check for nil condition before calling
let task0 = session0.dataTaskWithURL(url0!, completionHandler: {data, response, error -> Void in

SLRequestHandler error using Swift

I am trying to mimic the app as in this youtube tutorial, but using Swift and I am facing a problem constructing the closure as shown in the code snippet below.
func twitterTimeline() {
let account = ACAccountStore()
let accountType = account.accountTypeWithAccountTypeIdentifier(ACAccountTypeIdentifierTwitter)
// take action
account.requestAccessToAccountsWithType(accountType, options: nil,
completion: { (granted, error) in
if (granted) {
// invoke twitter API
let arrayOfAccount: NSArray = account.accountsWithAccountType(accountType)
if (arrayOfAccount.count > 0) {
let twitterAccount = arrayOfAccount.lastObject as ACAccount
let requestAPI = NSURL.URLWithString("http://api.twitter.com/1.1/atuses/user_timeline.json")
var parameters = Dictionary<String, String>()
parameters["100"] = "count"
parameters["1"] = "include_entities"
let posts = SLRequest(forServiceType: SLServiceTypeTwitter, requestMethod: SLRequestMethod.GET, URL: requestAPI, parameters: parameters)
posts.account = twitterAccount
// This is the Error Prone Area
let handler: SLRequestHandler = { (response, urlResponse, error) in
self.array = NSJSONSerialization.JSONObjectWithData(data: response, options: NSJSONReadingOptions.MutableLeaves, error: &error) as NSArray
}
posts.performRequestWithHandler(handler)
}
} else {
// do something
}
}
)
}
The error I get is
Cannot convert expression's type '($T1, $T2, $T3) -> $T0' to type '()'
I have tried checking and explicitly casting the types with no much help. I believe the error is somewhere else. Could anyone help me with what exactly is the trouble? I am sorry, if this turns out to be a näive question.
Thanks in advance,
Nikhil
This looks like an interesting case of error propagation in the compiler — I'd suggest filing a bug report with Apple.
The error message you're getting says that you can't assign a closure (which takes three parameters and returns one value) to something that takes no parameters. What's actually going wrong is that the handler closure you're defining takes its error input parameter and tries to pass it to JSONObjectWithData(_:options:error:). That's problematic from a language perspective because the error you're getting in is an immutable (optional) reference to one error, and the parameter you're passing it to expects a mutable pointer for it to (potentially) write another error into.
It's also incorrect API usage. The error you receive as a parameter in the closure is a report of an error that happened in whatever procedure calls your closure. You should log this error, present it to the user, or examine it so your app can gracefully fail. The error parameter you pass to JSONObjectWithData is a place for you to receive reports of additional errors that occur when decoding JSON from your data — you should be handling this error, too. These are two separate places to receive errors, so you shouldn't be passing one to the other.
If you fix that, you'll find a more helpful compiler message saying that the data: label on the first parameter to that function should be omitted. Also, you can use type inference for the options: parameter. So, your handler definition should look something more like this:
let handler: SLRequestHandler = { (response, urlResponse, error) in
// check for error and do something about it if need be, then...
var err: NSError?
if let jsonArray = NSJSONSerialization.JSONObjectWithData(response, options: NSJSONReadingOptions.MutableLeaves, error: &err) as? NSArray {
self.array = jsonArray
} else {
// do something about err
}
}
(You can also probably use a Swift typed array instead of an NSArray if you know what to expect from your JSON. But that's another subject for another question. Actually, several questions.)

Resources