how should I use "?" in Swift - swift

for example
if let name = jsonDict["name"] as AnyObject? as? String {
println("name is \(name)")
} else {
println("property was nil")
}
I have the follow question:
jsonDict["name"] as AnyObject? as? String is the same as jsonDict["name"] as? AnyObject as? String yes or no?
jsonDict["name"] as AnyObject? as? String is the same as jsonDict["name"] as AnyObject? as String? yes or no?
I don't know the difference between as? String and as String?

jsonDict["name"] as AnyObject? as? String is the same as jsonDict["name"] as? AnyObject as? String yes or no? - No, the latter makes no sense, you are trying to making a double cast from AnyObject to String. Furthermore, jsonDict["name"] would be enough for the compiler to recognize what type the return is, you shouldn't need any casting to a string.
jsonDict["name"] as AnyObject? as? String is the same as jsonDict["name"] as AnyObject? as String? yes or no?. Same as the first case, making a double cast makes little sense. Furthermore, the difference between as? and as is that as? will only execute in the case that the object can be successfully converted to the desired type, if this is not the case, the object will not be casted, therefore avoiding crashes.

there is huge difference between as and as?.
as
the as downcasts the object and force-unwrap the result in every time, and it does not really care about the real type of the object or any fail-safe procedure. the responsibility is yours, therefore that can cause the following code crashes in runtime:
let jsonDict: Dictionary<String, AnyObject> = ["name": 32]
if let downcasted: String = jsonDict["name"] as String? { ... } else { ... }
the reason: it forcibly downcasts the Int32 value to an optional String (=String?), and that cause a simple crash – if force-unwrapping fails, it will cause crash in every occasion in runtime.
you can use this solution only when you are 100% certain the downcast won't fail.
therefore, if your dictionary looks like this:
let jsonDict: Dictionary<String, String?> = ["name": nil]
if let downcasted: String = jsonDict["name"] as String? { ... } else { ... }
the else-branch will be executed, because the String? is nil, when you replace the nil with any string (e.g. "My Name") in your dictionary, the if-branch will be executed in runtime as expected – if the value is any other type than optional String, a crash will happen as I highlighted above in every case when the value is not nil.
as?
the as? optionally downcast the object, and if the downcasting procedure fails then it returns nil. that solution helps you to check the success of the donwcasting in runtime without suffering any crash:
let jsonDict: Dictionary<String, AnyObject> = ["name": 32]
if let downcasted: String = jsonDict["name"] as? String { ... } else { ... }
in that situation the else-branch will executed and your application can carry on, because the actual Int32 value fails to be downcasted to String – if your value is a String (e.g. "My Name") then the if-branch will be executed in runtime.
you have to use this solution when you are not certain about which the actual object's type is in runtime and there is a chance the downcasting procedure could fail.
NOTE: I would recommend to read the official docs about type-casting and optional-chaining for better understanding the entire procedure.

Related

Failing cast from Any to Int

I have the following dictionary of type [String: Any] (this is what the log looks like):
["name": Cesare de Cal, "last_name": de Cal, "email": hi#cesare.io, "id": 1012058902268810, "first_name": Cesare]
I want to get the profile ID "id":
if let fbID = fbValues["id"] as? Int {
print("here should be the fb Id", fbID)
} else {
print("cast failed") // cast failed
}
but this cast fails. Why? I'm assuming "id" (1012058902268810) is a number, right?
rob already provided you with a possible solution. Just to answer your question about why the cast fails, it fails because of how type casting works in Swift:
From the documentation:
Type casting is a way to check the type of an instance, or to treat
that instance as a different superclass or subclass from somewhere
else in its own class hierarchy. (...)
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 a type cast operator (as? or as!). (...)
Casting does not actually modify the instance or change its values. The underlying instance remains the same; it is simply treated and accessed as an instance of the type to which it has been cast.
This will work:
let something: Any = 1
let number = something as! Int
This won't work:
let something: Any = "1"
let number = something as! Int
This won't work either because Int has no initializer for type Any:
let something: Any = "1"
let number = Int(something)
But this will work - first you cast to String and then you coerce to Int (and Int has an initializer that accepts String)
let something: Any = "1"
let string = something as! String
let number = Int(string)!
Edit to answer Cesare: You're right. I edited my answer to just provide more info about type casting since you already had your problem solved ;)
And these were just some examples for getting the cast/coercion point across. In a real scenario you shouldn't be forcing unwrapping any of this as John Montgomery pointed out.
If you don't know whether the id value is coming in as a String or an Int, you could try handling both:
switch fbValues["id"] {
case nil:
print("no id given")
case let string as String:
if let id = Int(string) { print("id = \(id)") }
else { print("non-numeric id \(string)") }
case let id as Int:
print("id = \(id)")
default:
print("what is this I don't even")
}

Why is this still optional?

I make a query that returns a NSNumber. I then attempt to cast the NSNumber to String. For some reason, it always prints/ compares as an optional...but when I check the variables type it says string...Why is it optional? I need it to be a string!
let whoseTurn = selectedGame?["currentSender"]
let whoseTurnAsString: String = String(describing: whoseTurn)
if let whoseTurn = selectedGame?["currentSender"] as? NSNumber {
let whoseTurnAsString = "\(whoseTurn)"
print(whoseTurnAsString)
}
This is the right way to do optional chaining and make sure you are not forcing an optional
whoseTurn is an optional wrapping your NSNumber. You aren't "casting" it here to a string, you are making a string that "describes" it, and in this case that description includes the fact that whoseTurn is an optional... If you don't want that you'll need to unwrap it,
let whoseTurnAsString: String = String(describing: whoseTurn!)
(note the ! at the end)
This line of code let whoseTurn = selectedGame?["currentSender"] will return an optional.
This line of code let whoseTurnAsString: String = String(describing: whoseTurn) will return a String describing that optional value, which will be a string like this: Optional(5) or Optional(6). It describes the value saying that it is an optional.
So you need to unwrap that optional in order to get the wrapped value, you can force unwrap selectedGame like this:
let whoseTurn = selectedGame!["currentSender"] and then use the normal String initializer like this: String(whoseTurn).
Or, preferably, safely unwrap it like this:
if let whoseTurn = selectedGame?["currentSender"] {
let whoseTurnAsString = String(whoseTurn)
}
String can be optional also '?' or '!' indicates optional, check documentation on optionals.

swift using guard and typechecking in one line

I like using guard and came across the situation where I want to use where for a typecheck as well:
guard let status = dictionary.objectForKey("status") as! String! where status is String else { ...}
xCode complains correctly that it's always true.
My goal is to have an unwrapped String after the guard in one line.
How can I do this?
Probably you want this?
guard let status = dictionary["status"] as? String else {
// status does not exist or is not a String
}
// status is a non-optional String
When you use as! String! you tell Swift that you know that the object inside your dictionary must be a String. If at runtime the object is not a String, you let Swift throw a cast exception. That is why the where part of your check is not going to fail: either the status is going to be a String, or you would hit an exception prior to the where clause.
You can do an optional cast instead by using as? operator instead of as!. Coupled with guard let, this approach produces
guard let status = dictionary.objectForKey("status") as? String else { ... }
... // If you reached this point, status is of type String, not String?

Converting from Int to String Swift 2.2

Dears
I have this case where chatId is a property of type Int
let StringMessage = String(self.listingChat?.messages.last?.chatId)
When I debug I find that StringMessage is returning Optional(15) Which means it is unwrapped. But at the same time XCode does not allow me to put any bangs (!) to unwrap it. So I am stuck with Unwrapped Variable. I know its noob question but it I really cant get it. Your help is appreciated.
Thank you
It depends on what you want the default value to be.
Assuming you want the default value to be an empty string (""), You could create a function or a method to handle it.
func stringFromChatId(chatId: Int?) -> String {
if let chatId = chatId {
return String(chatId)
} else {
return ""
}
}
let stringMessage = stringFromChatId(self.listingChat?.messages.last?.chatId)
Or you could handle it with a closure.
let stringMessage = { $0 != nil ? String($0!) : "" }(self.listingChat?.messages.last?.chatId)
If you don't mind crashing if self.listingChat?.messages.last?.chatId is nil, then you should be able to directly unwrap it.
let StringMessage = String((self.listingChat?.messages.last?.chatId)!)
or with a closure
let stringMessage = { String($0!) }(self.listingChat?.messages.last?.chatId)
Update
Assuming chatId is an Int and not an Optional<Int> (AKA Int?) I missed the most obvious unwrap answer. Sorry, I was tired last night.
let StringMessage = String(self.listingChat!.messages.last!.chatId)
Force unwrap all the optionals along the way.
Optionals have a very nice method called map (unrelated to map for Arrays) which returns nil if the variable is nil, otherwise it calls a function on the (non-nil) value. Combined with a guard-let, you get very concise code. (I've changed the case of stringMessage because variables should begin with a lower-case letter.)
guard let stringMessage = self.listingChat?.messages.last?.chatId.map { String($0) } else {
// Do failure
}
// Success. stringMessage is of type String, not String?
I think:
let StringMessage = String(self.listingChat?.messages.last?.chatId)!

Chaining Optionals in Swift

Up until now, I've been unwrapping Optionals in Swift 2.1 like so:
#IBOutlet var commentTextView: UITextView!
if let comment = user["comment"] as? String {
commentTextView.text = comment
}
I never really thought about it, but I think the reason I was doing this was because I was worried that this statement would throw an error if user["comment"] returned something other than a String:
commentTextView.text = user["comment"] as? String
If user["comment"] isn't a String, will the variable on the left of the assignment operator be assigned and throw an error or will the assignment be skipped?
I guess user is in fact a dictionary [String: Any] and what you really do with if let comment = user["comment"] as? String { ... } is not just unwrapping the optional but a conditional type casting (and then unwrapping an optional result of it):
Use the conditional form of the type cast operator (as?) when you are not sure if the downcast will succeed. This form of the operator will always return an optional value, and the value will be nil if the downcast was not possible. This enables you to check for a successful downcast.
Now, to answer your question, if user["comment"] isn't a String then the result will be that commentTextView.text will be assigned nil value, which is bad because its type is String! (implicitly unwrapped optional) about which we hold a promise that it will never be nil. So, yes, there will be an error, an exception actually, but not at the place you would like it to be but at the moment your application will try to access its value assuming that it's not going to be nil.
What you should really do depends on a particular case.
E.g. if you can make user to be a dictionary like [String: String], then you would be able to truly get to unwrapping the optionals and use something like if let comment = user["comment"] { ... }. Or, if you are totally sure that the value for "comment" key will always be there, then you could just do let comment = user["comment"]!.
But if that's not possible then you have to stick with down-casting and the only other thing you can do is to use forced form of it, that is commentTextView.text = user["comment"] as! String. This one at least will produce an exception right at the spot in case if the value at "comment" happens to be not a String but something else.
nil will be assigned to the variable.
If the type of the variable is a non-optional, you'll get a runtime error.
However if user["comment"] is a String you'll get a compiler error about missing ! or ?.
First we need to know of what type the dictionary "user" is.
I assume it is of an unknown type like [String: AnyObject], otherwise why would you try to unwrap it as an String. Let us write a short test to see what happens:
let dict: [String: AnyObject] = ["SomeKey" : 1]
if let y = dict["SomeKey"] as? String {
print(y)
}
You can see clearly that the value of "SomeKey" is an Integer. Trying to unwrap it as an String triggers no error, the "if" statement is just skipped. If an assignment actually happened is hard to prove (maybe by looking at the assembler code) because the variable "y" simply does not exist after the if statement. I assume it will not be created at all.
If the type of the dictionary is known as [String: String] you can omit the try to unwrap it as a String because it's always clear that the type is String.
let dict2: [String: String] = ["SomeKey" : "SomeValue"]
if let y = dict2["WrongKey"] {
// In this case print(y) will not be called because the subscript operator of the dictionary returns nil
print(y)
}
// In this case print(y) will be called because the key is correct
if let y = dict2["SomeKey"] {
print(y)
}