Swift: If let statement failing to handle empty array - ios

I have an application that is using the Foursquare API to download JSON data. I am using NSURLSession and the dataTaskWithRequest with completion block method to fetch the data. I am getting the data fine but sometimes a nested array named groups can be empty. And when I am parsing through the JSON like below, for some reason my conditional statement is not handling the empty array like I am expecting it should. Instead of evaluating the array as empty and proceeding to the "else" portion of the if let...else statement if instead throughs a runtime error stating: index 0 beyond bounds of empty array
if let response: NSDictionary = data["response"] as? [String: AnyObject],
groups: NSArray = response["groups"] as? NSArray,
// Error here \|/ (sometimes groups array is empty)
dic: NSDictionary = groups[0] as? NSDictionary,
items: NSArray = dic["items"] as! NSArray {
}
else {
// Never gets here. Why?
// IF the groups array is empty, then the if let should return
// false and drop down to the else block, right?
}
I am relatively new to Swift, can someone tell me why this is happening and what I can do to fix this? Thanks

You have to check explicitly outside an if letstatement if the array is empty, because
An empty array is never an optional
if let response = data["response"] as? [String: AnyObject], groups = response["groups"] as? NSArray {
if !groups.isEmpty {
if let dic = groups[0] as? NSDictionary {
items = dic["items"] as! NSArray
// do something with items
println(items)
}
}
} else ...
You can omit all type annotations while downcasting a type
However, you can perform the check with a where clause, this works in Swift 1.2 and 2
if let response = data["response"] as? [String: AnyObject],
groups = response["groups"] as? [AnyObject] where !groups.isEmpty,
let dic = groups[0] as? NSDictionary,
items = dic["items"] as? NSArray {
// do something with items
println(items)
} else {...

Use && operator between statments.
For example this won't crash:
var a = 4;
var c = 5;
var array = Array<Int>();
if a > 2 && c > 10 && array[0] != 0 {
}

Make sure the array is not empty before trying to access it:
if let response: NSDictionary = data["response"] as? [String: AnyObject],
let groups: NSArray = response["groups"] as? NSArray where groups.count > 0 {
if let dic: NSDictionary = groups[0] as? NSDictionary,
let items: NSArray = dic["items"] as? NSArray {
// Do something..
return // When the 2nd if fails, we never hit the else clause
}
}
// Do logic for else here
...

Related

How can I make swift code that accesses arrays better?

I have the following code
if let unwrappedDict = snapshot.value as? NSDictionary {
for (index, dd) in unwrappedDict {
let dd = dd as? NSDictionary ?? [:]
id = dd["id"] as? String ?? ""
let ccode = dd["code"] as? String ?? ""
if (ccode == code) {
if id.count > 0 {
found = true;
}
}
}
}
How can I make this code better? I'm specifically talking about this line let dd = dd as? NSDictionary ?? [:]?
You can make the code more compact, Swift-like and functional by doing something like this:
let found = (snapshotValue as? NSDictionary)?.compactMap { $1 as? NSDictionary }.contains { $0["code"] as? String == ccode && ($0["id"] as? String)?.isEmpty == false } ?? false
Explaining piece by piece:
(snapshotValue as? NSDictionary)?.compactMap { $1 as? NSDictionary } // This tries to cast snapshotValue as an NSDictionary, and only if that succeeds, takes the array of (key, value)'s from the dictionary and tries to cast each value as an NSDictionary. The output of this segment is an array of only values that succeeded in being cast to NSDictionary.
.contains { $0["code"] as? String == ccode && ($0["id"] as? String)?.isEmpty == false } // In the array of NSDictionary values that are also NSDictionaries themselves, check if any of those child dictionaries meet the condition of having the right code and a non-empty id string. If any one of them do, return true and early exit the loop
?? false // If the original conditional cast of snapshotValue as? NSDictionary fails, return false for the value found

Cannot convert value of type 'NSDictionary.Iterator.Element' (aka '(key: Any, value: Any)') to expected argument type 'NSDictionary'

I am facing error on 7th line it's giving "Cannot convert value of type'NSDictionary.Iterator.Element' (aka '(key: Any, value: Any)') to expected argument type 'NSDictionary'".
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
ref = snapshot.ref
let snapshotValue = snapshot.value as! [String: AnyObject]
if (snapshotValue["visitor"] != nil) {
for item in snapshotValue["visitor"] as! NSDictionary {
visitor = UserVisitor.init(visitorData: item)
}
}
snapshotValue["visitor"] is obviously an array so cast it to Swift Array containing dictionaries:
if let visitors = snapshotValue["visitor"] as? [[String:Any]] {
for item in visitors {
visitor = UserVisitor(visitorData: item)
}
}
Try a pure Swift approach like this :
// snapshotValue is a dictionary
if let snapshotValue = snapshot.value as? [String:Any] {
// The content of the "visitor" key is a dictionary of dictionaries
if let visitorDictionary = snapshotValue["visitor"] as? [String:[String:Any]] {
for (visitorId, visitorData) in visitorDictionary {
// This assumes that your initializer for UserVisitor is
// init(visitorData: [String:Any]) { ... }
visitor = UserVisitor.init(visitorData: visitorData)
}
}
}
Try not using NSDictionary or NSArray unless you really need to. Native Swift types work better in Swift...
You can write the else statements to handle data that is not in the expected format.

Not mapping JSON right in loops swift 3

This is in Swift 3
I am receiving an array of "segments" as JSON.
I am trying to map my data in a array of an object.
It seems that I am doing it wrong. I am getting the same amount in "duration" in every element of the array, which it's not.
I am not sure if it is the way I am running it through a loop, or what the problem is. Can anyone plz help?
Thanks a lot!
if let segmentsArray = resultData["Segments"] as? [[String: AnyObject]] {
var segments = [Segment]()
for segment in segmentsArray {
if let duration = segment["Duration"] as? NSInteger {
if let key = segment["Key"] as? String {
if let legIndexes = segment["LegIndexes"] as? NSArray {
if let stops = segment["Stops"] as? NSInteger {
let thisSegment = Segment.init(duration: duration, key: key, legIndexes: legIndexes as! [NSInteger], stops:stops)
segments.append(thisSegment)
}
}
}
}
}

error using valueForKey in swift

Why do I get an error when I used valueForKey... I am using same trick like in objectiveC ...
In ObjectiveC, the code is
self.strSubscribe =[responseObject[#"subscribe"] valueForKey:#"subscribe_ids"];
In Swift , the code is
self.strSubscribe = responseObject["subscribe"].valueForKey["subscribe_ids"] as! String
I declare the variables like
var arraySubCategory : NSMutableArray! = NSMutableArray()
var strSubscribe:String!
And I tried to access the value from below response
{
subscribe =
{
"subscribe_ids" = "1,14";
}
}
Edit
It works using Amit and Eric's solution but now for following data
{
data = (
{
"subscribe_ids" = "1,14";
}
);
}
let dictionary = responseObject["data"][0] as! Dictionary<String,AnyObject>
self.strSubscribe = dictionary["subscribe_ids"] as! String
OR//
if let dic = responseObject["data"][0] as? [String:String], let ids = dic["subscribe_ids"] {
self.strSubscribe = ids
}
but it gives me error:
could not find member 'subscript'
Swift doesn't know the type of responseObject["subscribe"], you have to help the compiler a bit; for example:
if let dic = responseObject["subscribe"] as? [String:String], let ids = dic["subscribe_ids"] {
self.strSubscribe = ids // "1,14"
}
UPDATE:
It's still the same problem: the compiler doesn't know the type of responseObject["data"], so when you try to access the subscript there's an error (because you know it's a dictionary inside the array, but the compiler doesn't).
One solution is to give the type to the compiler by declaring an array of dictionaries in the if let condition:
if let arr = responseObject["data"] as? [[String:String]], let ids = arr[0]["subscribe_ids"] {
self.strSubscribe = ids
}
Notice that it's [[String:String]] (array of dictionaries), not [String:String] (dictionary).
Write like this.
let dictionary = responseObject["subscribe"] as! Dictionary<String, AnyObject>
self.strSubscribe = dictionary["subscribe_ids"] as! String
Since responseObject["subscribe"] will give a AnyObject? output and AnyObject does not have any member called valueForKey.

How to assign an array element which can be null in Swift?

In my Swift app, I'm querying an api returning a json object like this :
{
key1: value1,
key2: value2,
array : [
{
id: 1,
string: "foobar"
},
{
id: 2,
string: "foobar"
}
]
}
Facts :
The array value can be empty.
I want to read the first array element,
present or not.
In Swift i'm doing :
if let myArray: NSArray = data["array"] as? NSArray {
if let element: NSDictionary = myArray[0] as? NSDictionary {
if let string: NSString = element["string"] as? NSString {
// i should finally be able to do smth here,
// after all this crazy ifs wrapping
}
}
}
It works if the array and the first element exist, but i'm having a crash with "index 0 beyond bounds for empty array" even if the element assignment is within an if let wrapping.
What i am doing wrong here ? I'm getting crazy with Swift optionals, typing and crazy if let wrapping everywhere...
The error is not concerning about Optional. If you use subscription([]) for array, you have to check the length of that.
if let myArray: NSArray = data["array"] as? NSArray {
if myArray.count > 0 { // <- HERE
if let element: NSDictionary = myArray[0] as? NSDictionary {
if let string: NSString = element["string"] as? NSString {
println(string)
}
}
}
}
But we have handy .firstObject property
The first object in the array. (read-only)
If the array is empty, returns nil.
Using this:
if let myArray: NSArray = data["array"] as? NSArray {
if let element: NSDictionary = myArray.firstObject as? NSDictionary {
if let string: NSString = element["string"] as? NSString {
println(string)
}
}
}
And, we can use "Optional Chaining" syntax:
if let str = (data["array"]?.firstObject)?["string"] as? NSString {
println(str)
}
You can use this
var a = [Any?]()
a.append(nil)
If you have a non-optional array with AnyObject you can use NSNull (like in Obj-C)

Resources