Swift Dictionary confusion - swift

Say I have
var dict = parseJSON(getJSON(url)) // This results in an NSDictionary
Why is
let a = dict["list"]![1]! as NSDictionary
let b = a["temp"]!["min"]! as Float
allowed, and this:
let b = dict["list"]![1]!["temp"]!["min"]! as Float
results in an error:
Type 'String' does not conform to protocol 'NSCopying'
Please explain why this happens, note that I'm new to Swift and have no experience.

dict["list"]![1]! returns an object that is not known yet (AnyObject) and without the proper cast the compiler cannot know that the returned object is a dictionary
In your first example you properly cast the returned value to a dictionary and only then you can extract the value you expect.

To amend the answer from #giorashc: use explicit casting like
let b = (dict["list"]![1]! as NSDictionary)["temp"]!["min"]! as Float
But splitting it is better readable in those cases.

Related

Swift 4: Could not cast value of type '__NSCFNumber' to 'NSString'

I have tried
self.adc_role_id = String(res["adc_role_id"])
self.adc_role_id = "\(res["adc_role_id']"
self.adc_role_id = (\(res["adc_role_id"] as? String)!
but still get
Could not cast value of type '__NSCFNumber' to 'NSString'
I added the dump of res[4] below
As new as I am to Swift, I don't know anything else to try
In Swift 4, the String initializer requires the describing: argument label.
I don't know if this will solve your problem, but your first line of code should be written:
self.adc_role_id = String(describing: res["adc_role_id"])
In your screenshot we can see that res["adc_role_id"] is an NSNumber.
To transform an NSNumber to a String you should use its stringValue property.
And since a dictionary gives an Optional, you should use optional binding to safely unwrap it.
Example:
if let val = res["adc_role_id"] {
self.adc_role_id = val.stringValue
}
You could also, if you want, use string interpolation instead of the property:
if let val = res["adc_role_id"] {
self.adc_role_id = "\(val)"
}
but I think using the property is more relevant.
If for some reason the compiler complains about the type of the content, cast it:
if let val = res["adc_role_id"] as? NSNumber {
self.adc_role_id = val.stringValue
}
Note that you should not use String(describing:) because this initializer will try to represent the string in many ways, and some of them will give inaccurate and unexpected results (for example, if String(describing:) resolves to use the debugDescription property, as explained in the documentation, you may get a totally different string than the one you want).
It's also worth noting that using String(describing:) with an optional value such as your dictionary will resolve to a wrong string: String(describing: res["adc_role_id"]) will give Optional(yourNumber)! This is why Mike's answer is wrong. Be careful about this. My advice is to avoid using String(describing:) altogether unless for debugging purposes.
The error message is clear and the dump is clear, too.
The value is not a String, it's an Int(64) wrapped in NSNumber
Optional bind the value directly to Int (NSNumber is implicit bridged to Int) and use the String initializer.
if let roleID = res["adc_role_id"] as? Int {
self.adc_role_id = String(roleID)
}
Please conform to the naming convention that variable names are camelCased rather than snake_cased

Value type of "Any" has no member 'objectforKey' Swift 3 conversion

I am currently facing this problem (Value type of "Any" has no member 'objectforKey') due to swift 3 upgrade. Anybody knows why?
Here is my line of code that have the error
let bookName:String = (accounts[indexPath.row] as AnyObject).objectForKey("bookName") as! String
*accounts is an array.
Okay basically it is the .objectForKey needs to be change as the following:
let bookName:String = (accounts[indexPath.row] as AnyObject).object(forKey:"bookName") as! String
As always, do not use NS(Mutable)Array / NS(Mutable)Dictionary in Swift. Both types lack type information and return Any which is the most unspecified type in Swift.
Declare accounts as Swift Array
var accounts = [[String:Any]]()
Then you can write
let bookName = accounts[indexPath.row]["bookName"] as! String
Another Dont: Do not annotate types that the compiler can infer.

Convert or cast object to string

how can i convert any object type to a string?
let single_result = results[i]
var result = ""
result = single_result.valueForKey("Level")
now i get the error: could not assign a value of type any object to a value of type string.
and if i cast it:
result = single_result.valueForKey("Level") as! String
i get the error:
Could not cast value of type '__NSCFNumber' (0x103215cf0) to 'NSString' (0x1036a68e0).
How can i solve this issue?
You can't cast any random value to a string. A force cast (as!) will fail if the object can't be cast to a string.
If you know it will always contain an NSNumber then you need to add code that converts the NSNumber to a string. This code should work:
if let result_number = single_result.valueForKey("Level") as? NSNumber
{
let result_string = "\(result_number)"
}
If the object returned for the "Level" key can be different object types then you'll need to write more flexible code to deal with those other possible types.
Swift arrays and dictionaries are normally typed, which makes this kind of thing cleaner.
I'd say that #AirSpeedVelocity's answer (European or African?) is the best. Use the built-in toString function. It sounds like it works on ANY Swift type.
EDIT:
In Swift 3, the answer appears to have changed. Now, you want to use the String initializer
init(describing:)
Or, to use the code from the question:
result = single_result.valueForKey("Level")
let resultString = String(describing: result)
Note that usually you don't want valueForKey. That is a KVO method that will only work on NSObjects. Assuming single_result is a Dictionary, you probably want this syntax instead:
result = single_result["Level"]
This is the documentation for the String initializer provided here.
let s = String(describing: <AnyObject>)
Nothing else is needed. This works for a diverse range of objects.
The toString function accepts any type and will always produce a string.
If it’s a Swift type that implements the Printable protocol, or has overridden NSObject’s description property, you’ll get whatever the .description property returns. In the case of NSNumber, you’ll get a string representation of the number.
If it hasn’t, you’ll get a fairly unhelpful string of the class name plus the memory address. But most standard classes, including NSNumber, will produce something sensible.
import Foundation
class X: NSObject {
override var description: String {
return "Blah"
}
}
let x: AnyObject = X()
toString(x) // return "Blah"
"\(x)" // does the same thing but IMO is less clear
struct S: Printable {
var description: String {
return "asdf"
}
}
// doesn't matter if it's an Any or AnyObject
let s: Any = S()
toString(s) // reuturns "asdf"
let n = NSNumber(double: 123.45)
toString(n) // returns "123.45"
n.stringValue // also works, but is specific to NSNumber
(p.s. always use toString rather than testing for Printable. For one thing, String doesn’t conform to Printable...)
toString() doesn't seem to exist in Swift 3 anymore.
Looks like there's a failable initializer that will return the passed in value's description.
init?(_ description: String)
Docs here https://developer.apple.com/reference/swift/string/1540435-init

What function does "as" in the Swift syntax have?

Recently I stumbled upon a syntax I cannot find a reference to: What does as mean in the Swift syntax?
Like in:
var touch = touches.anyObject() as UITouch!
Unfortunately, it's hard to search for a word like as, so I didn't find it in the Swift Programming Language handbook by Apple. Maybe someone can guide me to the right passage?
And why does the element after as always have an ! to denote to unwrap an Optional?
Thanks!
The as keyword is used for casting an object as another type of object. For this to work, the class must be convertible to that type.
For example, this works:
let myInt: Int = 0.5 as Int // Double is convertible to Int
This, however, doesn't:
let myStr String = 0.5 as String // Double is not convertible to String
You can also perform optional casting (commonly used in if-let statements) with the ? operator:
if let myStr: String = myDict.valueForKey("theString") as? String {
// Successful cast
} else {
// Unsuccessful cast
}
In your case, touches is (I'm assuming from the anyObject() call) an NSSet. Because NSSet.anyObject() returns an AnyObject?, you have to cast the result as a UITouch to be able to use it.
In that example, if anyObject() returns nil, the app will crash, because you are forcing a cast to UITouch! (explicitly unwrapping). A safer way would be something like this:
if let touch: UITouch = touches.anyObject() as? UITouch {
// Continue
}
A constant or variable of a certain class type may actually refer to
an instance of a subclass behind the scenes. Where you believe this is
the case, you can try to downcast to the subclass type with the type
cast operator (as).
from Swift Programming Language, Type Casting
And why does the element after as always have an ! to denote to unwrap an Optional?
It is not. It is trying to downcast to "Implicitly Unwrapped Optionals", see Swift Programming Language, Types
as is an operator that cast a value to a different type.
For example:
Suppose you have an NSSet instance with some elements that have a type Car.
Then if you want to get any object:Car from this set, you should call anyObject().
var someCar = set.anyObject() //here someCar is Optional with type AnyObject (:AnyObject?), because anyObject() -> AnyObject?
Let's imagine the situation when you need to get an object from the set with type Car.
var realCar: Car = someCar as Car //here realCar is Optional with type Car (:Car?)
Than if you exactly know that someCar is not an Optional ( someCar != nil) you can do follow:
var realCarAndNotAnOptional = someCar as Car! //here realCarAndNotAnOptional just have a type == Car
More info here: Swift: Type Casting

Convert JSON AnyObject to Int64

this is somewhat related to this question: How to properly store timestamp (ms since 1970)
Is there a way to typeCast a AnyObject to Int64? I am receiving a huge number via JSON this number arrives at my class as "AnyObject" - how can I cast it to Int64 - xcode just says its not possible.
JSON numbers are NSNumber, so you'll want to go through there.
import Foundation
var json:AnyObject = NSNumber(longLong: 1234567890123456789)
var num = json as? NSNumber
var result = num?.longLongValue
Note that result is Int64?, since you don't know that this conversion will be successful.
You can cast from a AnyObject to an Int with the as type cast operator, but to downcast into different numeric types you need to use the target type's initializer.
var o:AnyObject = 1
var n:Int = o as Int
var u:Int64 = Int64(n)
Try SwiftJSON which is a better way to deal with JSON data in Swift
let json = SwiftJSON.JSON(data: dataFromServer)
if let number = json["number"].longLong {
//do what you want
} else {
//print the error message if you like
println(json["number"])
}
As #Rob Napier's answer says, you are dealing with NSNumber. If you're sure you have a valid one, you can do this to get an Int64
(json["key"] as! NSNumber).longLongValue