What an utter nightmare this portion of Swift seems to be...
let priceNumber = dictionary[kSomeKey] as? NSNumber
cell.titleLabel.text = String(format: "The price is £%li",priceNumber?.longValue)
Compile error: value of optional NSNumber not unwrapped
Now, forgive me for potentially being a complete cretin, but I just want my old Obj-C style if nil, display 0... what on earth is going on? if this dictionary key is nil, putting the suggested (priceNumber?.longValue)! will crash right?
Closest approach
let priceNumber = dictionary[kSomeKey] as Int? ?? 0
cell.titleLabel.text = "The price is £\(priceNumber)"
The nil coalescing operator ?? unwraps the optional if it's not nil
I'll try this way:
%li is expecting a long integer.
In Swift nil is not the equivalent of zero like it might happen in some cases in Obj-C. Nil is nothing
Since %li is expecting a long integer, and nil is not literal convertible, and Swift is statically typed, you have to unwrap the number and make sure it is not nil (Zero is not nil).
This might not be the 100% exact technical explanation. But it should give the rough idea.
You are making it more difficult than it has to be. Values in dictionaries are optional because you might not get a value for a particular key. To be safe, you bind the value from the dictionary in an if let to make sure you actually have an object, then use string interpolation to set the cell's text property.
// This is all so you can test it in a playground
var dictionary = [String : AnyObject]()
let kSomeKey = "price"
dictionary[kSomeKey] = NSNumber(long: 12)
// Bind the value out of the dictionary
if let priceNumber = dictionary[kSomeKey] as? NSNumber {
// priceNumber is guaranteed to be an instance of NSNumber at this point
cell.titleLabel.text = "The price is £\(priceNumber)")
}
Related
Semantically speaking, [String:AnyObject?] and [String:AnyObject] are the same thing in terms of the way they act, meaning they will return the same thing if I access a key that wasn't set and setting a key to nil will remove that key from the dictionary. Why aren't they considered the same type?
EDIT: I understand the difference from the compiler point of view from the answers. I decided to put the following code in the playground:
var optional = [String:AnyObject?]()
var regular = [String:AnyObject]()
//Some control keys
optional["controlkey"] = "valueoptional"
regular["controlkey"] = "valueRegular"
//Set the keys
optional["keyOptional"] = "valueoptional"
regular["keyRegular"] = "valueRegular"
//Unset the keys
optional["keyOptional"] = nil
regular["keyRegular"] = nil
for (key,val) in optional {
print("key: \(key)\tval: \(val)")
}
for (key,val) in regular {
print("key: \(key)\tval: \(val)")
}
To my surprise the optional did not print the key that was set to nil.
Output was as follow:
key: controlkey val: Optional(valueoptional) //The keys for the optional dictionary
key: controlkey val: valueRegular //The keys for the `regular` dictionary
Why doesn't the key that I set to nil show up?
From the Apple docs:
The Swift language defines the postfix ? as syntactic sugar for the
named type Optional, which is defined in the Swift standard
library.
The type AnyObject? is an enumeration with two cases, None and Some(Wrapped), which are used to represent values that may or may not be present. But AnyObject is responding to one that will be presented.
Setting nil for a key in dictionary means removing the element itself.
Try to set NSNull()
optional["keyOptional"] = NSNull()
Because a nullable type and a non nullable type aren't the same thing from the compiler point of view. It just happens that the Dictionnary interface eventually "blend them" in similar entity, but this is linked with the Dictionnary implementation, not really with the type system.
EDIT: Your update changes the context of the question, but setting to nil is a way to unset from my understanding.
They are different because you can actually store nil into [String:AnyObject?]. You cannot do it using optional[key] = value (subscript operator) because that one has a special behavior for nil (removes value) but you can do it:
var optional: [String:AnyObject?] = ["test": nil]
optional.updateValue(nil, forKey: "test2")
print(optional) // ["test2": nil, "test": nil]
Of course, getting a value from such a dictionary:
print(optional["test"]) // Optional(nil)
results in a double optional Optional<Optional<AnyObject>> (or AnyObject??) and interaction with such types is cumbersome so you should avoid storing nil into dictionaries.
Because setting a key to nil remove that key from the dictionary! From the documentation
Reading a key that is not present in self yields nil. Writing nil as the value for a given key erases that key from self
You can't store nil as a dictionary value. If you need something denote nothingness, use NSNull:
optional["keyOptional"] = NSNull()
regular["keyRegular"] = NSNull()
They aren't the same type because they aren't the same type ;-) Compilers are such literal creatures.
#Sulthan's answer is correct and more complete - I didn't see it until after I hit post on my own answer. But if you look up a key in a [String: AnyObject?] dictionary and a value is present, you'll get an optional wrapped in an optional - the result is an AnyObject??. You'd have to unwrap it twice to use it:
var optional = [String: AnyObject?]()
// I changed it to NSString because String isn't an AnyObject
v
optional["controlkey"] = NSString(string: "valueoptional")
print(optional["controlkey"]) // prints "Optional(Optional(valueoptional))"
if let val = optional["controlkey"] {
print(val) // prints "Optional(valueoptional)"
if let unwrappedVal = val {
print(unwrappedVal) // prints "valueoptional"
}
}
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)
}
I need to retrieve a setting (with Swift):
var highScoreNumber: NSInteger = 0
var returnValue: [NSInteger]? = NSUserDefaults.standardUserDefaults().objectForKey("food") as? [NSInteger]
if (returnValue != nil) {
highScoreNumber = returnValue as NSInteger
}
I tried this and other variations of the code and I always get
'NSInteger?' not convertible to 'NSInteger'
This is my first foray into Swift, so
Am I missing something simple in the conversion?
Is there a better way to retrieve an NSInteger from settings?
When converting a nullable value to a non-nullable one, there are many good options to choose from.
But in your case, you have two issues. One is the array you’re fetching from NSUserDefaults is an array of integers, so you need to decide which one you want from that array.
If it’s the first one you want, you can use the first property. You can use optional chaining to get it. And since it’s a high score, you probably want to default to zero if it’s not present.
Here’s that in one line:
let returnValue = NSUserDefaults.standardUserDefaults().objectForKey("food") as? [Int]
let highscore = returnValue?.first ?? 0
Things to note about the above: there’s no need to give the type to the left of the = if the type is unambiguously determined by what lies to the right of the =. And it’s better to prefer Int over NSInteger.
The returnValue?.first says “if returnValue is nil, then nil, else the value of first (which, itself, returns nil if the array is empty).” The ?? says “if the left of ?? is nil, then the value on the right of nil, else the unwrapped value from the left”.
That said – do you really mean to store an array? Or do you really just want to store a single integer in the defaults and then get that out directly?
let highscore = NSUserDefaults.standardUserDefaults().integerForKey("food")
// and elsewhere in your code, when saving
NSUserDefaults.standardUserDefaults().setInteger(highscore, forKey: "food")
(integerForKey already returns a 0 default when not present rather than an optional, so no need for any unwrapping)
I have an "if let" statement that is being executed, despite the "let" part being nil.
if let leftInc : Double? = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!]! {
println(leftInc)
let valueString : String = formatter.stringFromNumber(NSNumber(double: leftInc!))!
self.leftIncisorTextField?.text = valueString
self.btnLeftIncisor.associatedLabel?.text = valueString
}
// self.analysis.inputs is a Dictionary<String, Double?>
The inputs dictionary holds information entered by the user - either a number, or nil if they haven't entered anything in the matching field yet.
Under the previous version of Swift, the code was written as this:
if let leftInc : Double? = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!]?? {
and worked correctly.
I saw a similar question here, but in that instance the problem seemed to be the result of using Any?, which is not the case here.
Swift 2.2
In your if let you define another optional, that's why nil is a legitimate case. if let is intended mainly to extract (maybe) non optional value from an optional.
You might try:
if let leftInc : Double = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!].flatMap ({$0}) {
// leftInc is not an optional in this scope
...
}
Anyway I'd consider to not do it as a one liner but take advantage of guard case. Just in order to enhance readability. And avoid bang operator (!).
The if-let is for unwrapping optionals. You are allowing nil values by setting the type to an optional Double.
The if statement should be:
if let leftInc = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!] as? Double{
...
}
This will attempt to get an object out of inputs, if that fails it returns nil and skips it. If it does return something it will attempt to convert it to a Double. If that fails it skips the if statement as well.
if inputs is a dictionary like [Something:Double] then you don't need the last as? Double as indexing the dictionary will return a Double?
I recommend reading the swift book on optional chaining.
You could break it down further -
if let optionalDouble = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!], leftInc = optionalDouble {
....
}
as your dictionary has optional values - this way of writing it might make it clearer what's going on
if let k = dict["someKey"]{}, dict["someKey"] will be an object of type Any
this can bypass a nill
So do a typecast to get it correct like if let k = dict["someKey"] as! String {}
I'm having a bit of a hard time wrapping my head around optionals and why they are beneficial.
First off, are these two code block essentially equivalent?
Swift:
if let unwrappedName = p.name {
var greeting = “Hello “ + unwrappedName
} else {
var greeting = “Hello stranger”
}
Objective-C:
NSString *greeting;
if (p.name) {
greeting = [NSString stringWithFormat:#"Hello %#", p.name];
} else {
greeting = #"Hello stranger"
}
Second, this is coming from the Swift iBook, I don't get any errors on these but what exactly is the difference between them??
1.
let optionalSquare: Square? = Square(sideLength: 10, name: "Optional Square")
let sideLength = optionalSquare?.numberOfSides
2.
let optionalSquare: Square! = Square(sideLength: 10, name: "Optional Square")
let sideLength = optionalSquare?.numberOfSides
3.
let optionalSquare: Square? = Square(sideLength: 10, name: "Optional Square")
let sideLength = optionalSquare!.numberOfSides
Yes, optional types are pretty similar for reference types to Objective-C references. The key difference is that non optional type can never be "nil". If the type of returned a method you are calling is Object?, you are forced to unwrap it before using it. It is clear that the response can be "empty" and the compiler will force you to handle that.
If the type is not optional then you never need to check if it is nil before using. In addition, Optional work with value types like Int. You don't need to remember if the empty value is 0 or -1 or some other magic value.
You should use optional pretty much anytime you are thinking about using a magic sentinel value that means the absence of something, ie 0, -1, nil, empty list, empty string, etc. It not hard to remember what should be checked but it is easy to forget. With optional, you do not need to "double check" its not nil, even though it "should not be nil but maybe it is". Instead, of return -1 as an index of an object that was not found return Int?. Instead of throwing an exception, when a file is not found you can pass a an optional object.
Compare with optionals:
if let mapleSyrup = shoppingList.removeAtIndex(3) {
print "Bought /(mapleSyrup)"
}
To without optionals and not perfectly clear documentation:
var mapleSyrup = shoppingList.removeAtIndex(3)
if mapleSyrup != nil && mapleSyrup.isEmpty {
print "Bought /(mapleSyrup)"
}
The second question is answered well in the swift ibook here.
let optionalSquare: Square! = Square(sideLength: 10, name: "Optional Square")
Lets you use optionalSquare as if it was a normal variable and doesn't need to be unwrapped first.
let sideLength = optionalSquare!.numberOfSides
If you know optionalSquare has a value, you can force to be unwrapped and used with !.
This article starts with some great information on the problems with nil and the benefits of optional types.