james james - 6 months ago 38
Swift Question

Ambiguous use of subscript?

I can make a Facebook SDK Graph Request to get a user's likes, but I'm having trouble taking the returned values and storing one of the keys in an array of Strings. The request returns an NSDictionary of keys/values. Then, using

objectForKey
I can get the
data
key which returns what I want: the id and name of the "liked" page on Facebook.

Data returns elements like this:

{
id = 486379781543416;
name = "Star Wars Movies";
},


I specifically want only the "name" of all of these objects and to throw them into an array
[String]
. I tried to loop through the objects but I'm getting error
ambiguous use of subscript
. Here's the relevant code:

request.startWithCompletionHandler{(connection:FBSDKGraphRequestConnection!, result:AnyObject!, error:NSError!) -> Void in
let resultdict = result as! NSDictionary
let likes = resultdict.objectForKey("data") as! NSArray
print("Found \(likes.count) likes")
print(likes)

for object in likes{

let name = object["name"] as! String //error: ambiguous use of subsript
print(name)
}

}


After doing some research it looks like the issue is with the NSArray and that I should instead use Swift data types. I tried casting it to a Swift array but I got different errors.

What's the best way to handle this error?

Thanks!

update: Here is what the facebook API request returns:

{
data = (
{
id = 111276025563005;
name = "Star Wars (film)";
},
{
id = 115061321839188;
name = "Return of the Jedi";
}
);
paging = {
cursors = {
after = MTE1MDYxMzIxODM5MTg4;
before = Mjc0NzYzODk2MTg4NjY5;
};
next = "https://graph.facebook.com/v2.5/10155262562690368/likes?access_token=<redacted>";
};
}

Answer

You should always use the native Swift collection types wherever possible as NSArray and NSDictionary are really type-inspecific, and therefore can easily trigger "ambiguous use of subscript" errors.

You'll also want to avoid force down-casting, in case you receive data that's in the wrong format, or no data at all. This situation would be more elegantly handled with a guard, in order to prevent a crash. If your program depends on the force down-casting succeeding, and therefore should crash – then you can always call fatalError in the guard, with a descriptive error message in order to assist you in debugging the problem.

If I understand your data structure correctly, the request returns an AnyObject that should be a [String:AnyObject] (A dictionary of strings to any objects). In the case of the "data" key, the AnyObject value is then a [[String:AnyObject]] (An array of dictionaries of strings to any objects).

Therefore you'll want to do your casting in two stages. First, using a conditional downcast on your result to cast it as a [String:AnyObject]. If this fails, then the else clause of the guard will be executed and the code will return. You'll then want to get out your "data" value (your 'likes' array), and conditionally downcast it to a [[String:AnyObject]]. Both of these statements will handle the possibility of resultDict or resultDict["data"] being nil.

guard let resultDict = result as? [String:AnyObject] else {return}
guard let likes = resultDict["data"] as? [[String:AnyObject]] else {return}

You can put whatever error handling logic you want in the brackets of these statements to handle cases in which the results dictionary doesn't exist, was the wrong format, or there wasn't a 'likes' array in it.

You can then get an array of 'like' names through using flatMap.

let likeNames = likes.flatMap{$0["name"] as? String}

This will create an array of the like names of each dictionary – if the like names don't exist or aren't strings, then they won't be added. Because the compiler knows for certain that likes is a [[String:AnyObject]] – there's no ambiguity in subscripting its elements.

If you want a more general approach such as you're doing in your question, you can use a guard statement within a for loop.

for object in likes {
    guard let name = object["name"] as? String else {continue}
    print(name)
}

Again, you can put whatever error handling you wish in the brackets of the guard.

Comments