Pg 123: In Swift this snippet of code could be rewritten as:
func voiceNameForIdentifier(identifier: String) -> String? {
let attributes = NSSpeechSynthesizer.attributesForVoice(identifier)
return attributes?[NSVoiceName] as? String
}
No need for the if-else since the optional result captures both String and nil
Optional chaining was introduced on p. 117 (and before that I had noticed Xcode trying to insert question marks after variable names and wondered why!), so the authors could have started using optional chaining in that code example.
I was confused by why the unwrapping of the dictionary did not proceed using the same methodology as shown on the bottom of p. 57. I came to the conclusion that trying to cast the whole dictionary to [String:String] was inefficient(and might fail) when all you want to do is extract one value that might cast successfully.
if let validValue = value {
println("(validValue)") //=> “world”
}
var swiftDict = objcDict as? [String: String] //=> nil
[/code]
An NSDictionary can use any object for any key and any object for any value, which is to say that the keys can be all different types, and the values can be all different types (as mentioned at the top of p. 57). Therefore, casting an NSDictionary to [String:String] will fail unless ALL the keys in the NSDictionary have the type NSString and all the values in the NSDictionary have the type NSString.
Nevertheless, it may be possible to extract one value from an NSDictionary and successfully cast that value to a String, whereas trying to cast all the values in the NSDictionary to Strings will fail. Even if the cast to [String:String] would succeed, it’s inefficient to cast ALL the keys and ALL the values to Strings when all you care about is one key and one value. Doing things in your code that you don’t need to do has a cost: increased processing time.
Does the NSDictionary returned by NSSpeechSynthesizer.attributesForVoice() have keys and values that are all NSStrings? It turns out, if you check the docs to see what’s in the NSDictionary(as I just did!), you will discover that some of the values in the NSDictionary are not NSStrings (the docs list the keys for the NSDictionary and in the discussion for each key the docs mention the type of the corresponding value).
Hmm… thanks for tip, as it appears to be the right direction, however Xcode 7.2 isn’t accepting the ‘attributes?’ optional chaining either. With the book code I get the error message: “Initializer for conditional binding must have Optional type, not ‘[String : AnyObject]’” on the “if let” line. However, with the original posted code I get the error message: “Cannot use optional chaining on non-optional value of type ‘[String : AnyObject]’” on the “return attributes?” line. It does work once I remove the ‘?’ on the attributes variable.
However, I am unclear as to how to properly handle the fact that the ‘identifier’ parameter may be complete nonsense and not have a valid dictionary entry. The ‘if let’ book construct initially made sense to me, as that method seems to encompass the fact that there may not be a valid dictionary value for the string being ‘searched’. However, the Xcode error message seems to imply that rather than receiving ‘nil’ on a nonexistent voice identifier, one will always receive a non-optional dictionary return value of the form ‘[String : AnyObject]’. It’s just that on such a bad voice identifier search, you will receive an empty dictionary reply.
*** EDIT: My initial thought was that I would have to test the attributes dictionary to see if it was “isEmpty” prior to pulling out the NSVoiceName key value. But, on the other hand, searching an empty dictionary for NSVoiceName is probably ok, since we define the result as an optional String anyway… and the empty dictionary search will just return nil also. So, I’m back to just using “return attributes[NSVoiceName] as? String” without the empty dictionary test and without the optional chaining ‘?’ on attributes. ***