does swift 4 support exception handling? - swift

My Swift program is crashing, to handle the crash is their any way, i tried Exception handling i didn't got it, Does swift 4 supports Exception handling, what i want to do is i don't want my app to be crashed
here is the code i want to catch when ever i don't got the price value
let srt = self.Coin[indexPath.row] as! NSDictionary
var sr2=NSDictionary()
var barr = NSArray()
if srt.value(forKey: "Price") != nil{
do{
barr = try srt.value(forKey: "Price") as! NSArray
if (barr.count>0) {
sr2 = barr[0] as! NSDictionary
}
} catch {
print("Price is empty")
}
}

Not all errors can be caught in Swift. do-catch blocks can only handle errors thrown by throwable functions, whose declarations are marked using the throws keyword and errors that are explicitly thrown from inside those functions using the throw keyword.
All other errors in Swift are uncatchable, especially runtime errors caused by force unwrapping, force casting or force try (try!). When you use ! to force something, you tell the compiler that you know what you are doing and that a runtime error should be thrown in case an error occurs.
You shouldn't use force unwrapping/casting unless you are absolutely sure they won't fail or if you want to catch programming errors early in the development stage. Instead of using useless do-catch blocks with nested forced statements, simply use optional binding to safely cast/unwrap optional values.
Moreover, don't use NSDictionary and NSArray in Swift, use their native equivalents, Dictionary and Array. It seems like you are parsing JSON, so use [String:Any] instead of NSDictionary and [[String:Any]] instead of NSArray.
if let srt = self.Coin[indexPath.row] as? [String:Any] {
if let barrs = srt["Price"] as? [[String:Any]], let barr = barrs.first {
let sr2 = barr
}

As David said, I'd avoid force unwrapping and force casting.
But instead of an if let statement, I'd rather use a guard let in order to avoid the pyramid of doom, like this:
guard let srt = self.Coin[indexPath.row] as? [String:Any], let barr = srt["Price"] as? [[String:Any]], let sr2 = barr.first as? [String:Any] else {
print("Price is empty")
//Need to return, throw or whatever
}
And we can even remove your intermediate variables, but it's much less readable, like so:
guard let sr2 = ((self.Coin[indexPath.row] as? [String : Any])?["Price"] as? [[String : Any]])?.first as? [String : Any] else {
print("Price is empty")
//Need to return, throw or whatever
}
If you'd rather go with the if let statement (in case you don't want to return or throw in your guard statement's else clause) you can also do it with a single if statement, like that
if let srt = self.Coin[indexPath.row] as? [String:Any], let barr = srt["Price"] as? [[String:Any]], let sr2 = barr.first {
//Do something with sr2
}
And a shorter version without intermediate variables:
if let sr2 = ((self.Coin[indexPath.row] as? [String:Any])?["Price"] as? [[String:Any]])?.first {
//Do something with sr2
}
Whatever you choose... Don't force unwrap. Don't force try. Don't force cast. It will come back to bite you some day.

Related

Getting nil value when read JSON data in Swift

if let postString = NSString(data:data!, encoding: NSUTF8StringEncoding) as? String {
guard let jsonData = postString.dataUsingEncoding(NSASCIIStringEncoding) else {
fatalError()
}
guard let jsonObjects = try? NSJSONSerialization.JSONObjectWithData(jsonData,options: [])
,let JSONArray = jsonObjects as? [[String: AnyObject]]
else {
fatalError()
}
print(JSONArray)
}
In postString constant, I am getting "[{\"Name\":\"ABC\",\"Age\":35},{\"Name\":\"CDE\",\"Age\":36‌​}]"
and when I run this code then fatalError() code call.
A couple of other people have explained what you did wrong in this case (Tried to cast the output of deserialization to a dictionary when it actually contains an array.)
Stepping back from the details, when something fails, you need to break your code into smaller pieces and then trace through it to see what's failing.
The "as?" cast says "Try to cast this object to another type. Wrap the results in an Optional." If the cast fails, the result is nil. If it succeeds, the optional contains the new type.
If you rewrote your code as:
let jsonObject = try NSJSONSerialization.JSONObjectWithData(data!,
options: NSJSONReadingOptions.AllowFragments)
guard let jsonDict = jsonObject as [String: Any] else {
print("Cast failed")
return
}
Then the print statement would fire and you'd know that the cast was the problem.
EDIT:
I just noticed that your JSON data contains an array of dictionaries of type [String: Int]. In Swift Ints are not an Object type, so you need to cast your results to [[String:Any]], not [[String:AnyObject]]. I've fixed my code above.
I Wrote the following code in a playground and it works:
let jsonString = "[{\"Name\":\"ABC\",\"Age\":35},{\"surveyName\":\"CDE\",\"Age\":36}]"
guard let jsonData = jsonString.data(using: .ascii) else {
fatalError()
}
guard let jsonObjects = try? JSONSerialization.jsonObject(with: jsonData, options: []),
let JSONArray = jsonObjects as? [[String: Any]]
else {
fatalError()
}
print(String(describing: jsonObjects))
It gives the output:
(
{
Age = 35;
Name = ABC;
},
{
Age = 36;
surveyName = CDE;
}
)
Which is what I would expect.
EDIT #2:
Actually, on further investigation I'm stumped as to why your code isn't working. I just tested it, and the as [[String: AnyObject]] works. It turns out that in Swift 3 if you cast an Int to AnyObject and you've included Foundation (or UIKit) then it gets silently converted to an NSNumber, which IS an Object type.
You're going to need to show your actual JSON data and the code that converts it to an object if you need help debugging it.
EDIT #3:
Below is code I wrote and tested in Swift 2.3:
func parseJSONTest() {
let jsonString = "[{\"Name\":\"ABC\",\"Age\":35},{\"surveyName\":\"CDE\",\"Age\":36}]"
guard let jsonData = jsonString.dataUsingEncoding(NSASCIIStringEncoding) else {
fatalError()
}
//I'm not sure why you take JSON data, convert it to a string, and convert
//It back to NSData, but to prove a point, this is your code being fed
//well-formed JSON data in an NSData object:
if let postString = NSString(data:jsonData,
encoding: NSASCIIStringEncoding) as? String {
guard let jsonData = postString.dataUsingEncoding(NSASCIIStringEncoding) else {
fatalError()
}
guard let jsonObjects = try? NSJSONSerialization.JSONObjectWithData(jsonData,options: []),
let JSONArray = jsonObjects as? [[String: AnyObject]]
else {
fatalError()
}
print(JSONArray)
}
}
In order to provide a complete test I first take a string containing JSON data and convert it to NSData. I then convert that NSData back to JSON objects and cast them to the desired type, and it works. The code above displays:
[["Name": ABC, "Age": 35], ["surveyName": CDE, "Age": 36]]
Which matches the structure you have (an array of dictionaries).
The above is technically not an json object it is a json array.
Try casting as [AnyObject] instead of [String: AnyObject]
Then use the array to access the item you need. Then cast that to
[String:AnyObject]
because they are json objects within a json array

Swift 3.0 - Core Data / Unexpectedly found nil

I am not able to figure this one out by my self. I am retrieving some settings stored in Core Data, and print these setting to some UITextFields. This works fine in another VC in the same project but here I get "unexpexpectedly found nil while unwrapping optional value".
I XCode I can see that the values are there? Why do I get this crash?
Please see attached screenshot.
This is the current code I am down to now. Still the same error message in XCode
func getSettingsFromCoreData() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "DeathMatchSettings")
do{
let results = try context.fetch(request)
let managedObject = results as! [NSManagedObject]
let getDMSettings = managedObject[0]
guard let playerOne = getDMSettings.value(forKey: "playerOne") else {
return
}
print(playerOne)
txtPlayerOne.text = String(describing: playerOne)
}catch{
fatalError("Error in retreiving settings from CoreData")
}
}
Player1 can be nil. You are trying to force downcast it to a value, but it is a fatal error in swift. Use an if let statement to test the value:
if let playerOne = getDMSSettings.value(forKey: "playerOne") as? String {
print(playerOne)
txtPlayerOne.text = playerOne
}
Read more about type casting in docs:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html
You can also use guard statement to unwrap your optional variable. It is better to use forced unwrapping only if you are confident that variable has non-optional value.
do {
guard let playerOne = getDMSettings.value(forKey:"playerOne") else {
return
}
print(playerOne)
txtPlayerOne.text = playerOne
}

Swift 3 error: Type 'Any' has no subscript members

So I know this question has been asked and answered numerous times before, but I just migrated my project to Swift 3 and Im getting a ton of these errors in my code that parses JSON and I couldn't quite find answers that made me understand how to resolve my specific issue.
guard let result = try JSONSerialization.jsonObject(with: data!, options: []) as? [String:AnyObject] else {
return
}
guard let responseData = result["Data"] else { return }
guard let userData = responseData["UserProfile"] else { return }
var userProfileFieldsDict = [String: String]()
if let sessionToken = userData!["CurrentSessionToken"] as? NSString {
userProfileFieldsDict["sessionToken"] = String(sessionToken)
}
}
The if let sessionToken line throws the aforementioned error, but not quite sure how you're supposed to deal with this in Swift 3? Could someone explain and suggest a best practice fix?
Thanks a bunch!
If responseData["UserProfile"] is also a dictionary you'll probably want to cast it as such in you guard by saying guard let userData = responseData["UserProfile"] as? [String : AnyObject] else { return }. I suspect this will solve your problem.
As a small aside, you don't need to force unwrap userData in your if let, because you've already unwrapped it in the guard.

Combine Multiple NSMutableDictionary

I have a recursive function that's adding the previous dictionary to the new dictionary but using addEntriesFromDictionary gives me error
Here's my sample code (EDITED)
func fetchAllSWApiList(strUrl: String, strArrayCallback: NSMutableDictionary){
let swApiHandler = SwApiHandler()
let url = strUrl
swApiHandler.requestSWPApi(url, completionHandler: {(response, error) in
if let responseDictionary = response as? Dictionary<String, AnyObject> {
print("Did Detect a dictionary")
strArrayCallback.addEntriesFromDictionary(responseDictionary)
}
guard let nextPage: String = response!["next"]! as? String else {
print("End Of Page")
print(strArrayCallback.description)
return
}
print(nextPage)
self.fetchAllSWApiList(nextPage, strArrayCallback: strArrayCallback)
})
}
If you have a better solution in my code that will return the combined list from the fetched data recursively the better.
EDIT: here's the error
NSDictionary is not implicitly convertible to '[NSObject: AnyObject]';
did you mean to use 'as' to explicitly convert?
after that my code will become should I leave it as it is or edit the NSpbject part?
strArrayCallback.addEntriesFromDictionary(response! as [NSObject : AnyObject])
EDIT2:
incase you're wondering what's the JSON model looks like here's a pic of it
You should cast your response to the Dictionary type that you're expecting, by doing something like:
if let responseDictionary = response as? Dictionary<String, AnyObject> {
strArrayCallback.addEntriesFromDictionary(responseDictionary)
}

function return too early

Can anyone explain why the completion returns empty array?
The function:
import Foundation
class IMBD{
func searchMovies(searchText:String, completion: (result: [Movies]) -> Void){
var movies = [Movies]()
let replacedMovieTitle = searchText.stringByReplacingOccurrencesOfString(" ", withString: "+")
let URLString = "http://www.omdbapi.com/?s=\(replacedMovieTitle)&y=&r=json"
let URL = NSURL(string: URLString)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(URL!, completionHandler: {(data, response, error) -> Void in
do{
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSDictionary
if let search = jsonData["Search"] as? [[String : AnyObject]]{
for hit in search{
guard let title = hit["Title"] as? String else{
print("returna title")
return
}
guard let year = hit["Year"] as? String else{
print("returna year")
return
}
guard let imbdID = hit["imdbID"] as? String else{
print("returna imbd")
return
}
guard let poster = hit["Poster"] as? String else{
print("returna poster")
return
}
let movie = Movies(title: title, released: year, poster: poster, imbdID: imbdID)
movies.append(movie)
}
}
}catch{
}
}).resume()
completion(result: movies)
}
}
The call:
imbd.searchMovies(searchtext!, completion: { (result) -> Void in
self.movieList = result
})
You have to call your completion handles inside the dataTaskWithURL closure, not after it. This runs asynchronously, so if you call your completion outside of the closure, it would be called before the asynchronous request had a chance to retrieve anything.
Also, remember that this closure doesn't run on the main thread, so you likely want to also dispatch this to the main queue (from within the dataTaskWithURL).
For example:
class IMDB {
func searchMovies(searchText:String, completion: (result: [Movie]?, error: NSError?) -> Void) -> NSURLSessionTask {
var movies = [Movie]()
let allowedCharacters = NSCharacterSet.alphanumericCharacterSet().mutableCopy() as! NSMutableCharacterSet
allowedCharacters.addCharactersInString("-._* ")
let replacedMovieTitle = searchText.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacters)!
.stringByReplacingOccurrencesOfString(" ", withString: "+")
let URLString = "http://www.omdbapi.com/?s=\(replacedMovieTitle)&y=&r=json"
let URL = NSURL(string: URLString)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(URL!) { data, response, error in
guard error == nil && data != nil else {
dispatch_async(dispatch_get_main_queue()) {
completion(result: nil, error: error)
}
return
}
do {
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSDictionary
if let search = jsonData["Search"] as? [[String : AnyObject]]{
for hit in search{
guard let title = hit["Title"] as? String else{
print("returna title")
continue
}
guard let year = hit["Year"] as? String else{
print("returna year")
continue
}
guard let imdbID = hit["imdbID"] as? String else{
print("returna imbd")
continue
}
guard let poster = hit["Poster"] as? String else{
print("returna poster")
continue
}
let movie = Movie(title: title, released: year, poster: poster, imdbID: imdbID)
movies.append(movie)
}
}
dispatch_async(dispatch_get_main_queue()) {
completion(result: movies, error: nil)
}
} catch let error as NSError {
dispatch_async(dispatch_get_main_queue()) {
completion(result: nil, error: error)
}
}
}
task.resume()
return task
}
}
A couple of other changes in the above code snippet include:
Add guard in case there was a fundamental network error (e.g. remote server down, no Internet access, etc.)
In the guard statements that are checking for nil values, rather than performing a return (in which case no further results will be gathered), you might want to just continue (i.e. skip to the next record). You generally see guard in conjunction with return, but in this case, continue is probably more appropriate.
Frankly, you might want to take this a step further and consider whether some of these might be optional, rather than discarding the whole record. Notably, poster strikes me as something that might be nil if there was no poster available. Maybe some of the others should be optional, too, (e.g. if a movie hasn't been released yet, might it not have a release date?).
The occurrences of "imbd" have been replaced with "imdb".
The Movies class has been renamed to Movie (since each instance is a single movie, not a collection of them).
I changed the completion block to make [Movie] optional and to return the NSError. Without that, you don't have a way to differentiate between "couldn't find a title of that name" and "whoops, something went wrong".
When we call the completion closure from within the dataTaskWithURL, it can be very useful to have searchMovies dispatch completion calls back to the main queue, like above. This is because UI updates must always happen on the main thread, and frequently when you write routines like this, it is so you can update UI or the model with results.
This is not always necessary to do it like this (you might want to just have this call completion directly from the background thread and let the routine that called searchMovies manually dispatch stuff to the main thread itself), but I often find it useful to have this search method just dispatch the completion back to the main thread and be done with it.
As a matter of practice, I always return the NSURLSessionTask when performing requests. You might not need it now, but at some future date, you might want the ability to cancel an on-going request, and having a reference to the task can be useful. It doesn't hurt to return it, and it can be useful.
You probably should be percent escaping the values you add to the URL. Notably the presence of & or + characters could be problematic. Note, in this case, it looks like this site isn't handling it appropriately, anyway, but it's good to get in the habit of properly percent-escaping values in a query.
Personally, I keep this percent escaping logic in a String extension, but I wanted to keep this simple, so I embedded it right in this method, but hopefully it illustrates the idea.