Shouldn't an optional be inclusive to a non-optional type in Swift? - ios

Updated: with full code
I have this code:
struct Set<T : Hashable>: Sequence {
var items: Dictionary<Int, T> = [:]
func append(o: T?) {
if let newValue = o {
items[newValue.hashValue] = newValue
}
}
func generate() -> TypeGenerator<T> {
return TypeGenerator ( Slice<T>( items.values ) )
}
}
and I get the error:
Could not find an overload for 'subscript' that accepts the supplied arguments.
for the line:
items[newValue.hashValue] = newValue
As far as I understand it's because newValue's type is T rather than T?, meaning that it's not optional. This is because the Dictionary's subscript for accessing the key/value pair is defined as
subscript (key: KeyType) -> ValueType?
meaning that it can only take an optional as the value. And in my case newValue is not an optional after validating it's not nil.
But isn't an optional inclusive of non-optionals? Isn't a type's optional is the type + nil?
Why would something that can take everything + nil reject a type that can't be nil?
Small clarification: the reason I check that o is not nil is to be able to call its hashValue which is not accessible directly from the optional or the unwrapped optional (o!.hashValue throws a compile error).
I also can't replace the assignment line with
items[newValue.hashValue] = o
because it has validated that o is not an optional worthy of assignment even though it does not allow access to it's hashValue property.

The dictionary is not defined to store an optional value. It is just the assignment operator that accepts an optional value because giving it nil will remove the whole key from the dictionary.
The problem you are having is that you are trying to mutate your property items in a non-mutating method. You need to define your method as mutating:
mutating func append(o: T?) {
if let newValue = o {
items[newValue.hashValue] = newValue
}
}
There is certainly no problem assigning an optional variable to a non-optional value:
var optionalString : String? = "Hello, World"
In the same way, it is perfectly valid to assign a dictionary's key to a non-optional value:
var items : [Int:String] = [:]
items[10] = "Hello, World"
You can then assign the key to nil to remove the key entirely from the dictionary:
items[10] = nil
Also, I think you have a fundamental misunderstanding of what a hashValue is and how to use it. You should not pass the value of hashValue to a dictionary as a key. The dictionary calls hashValue on the value you provide, so you are having the dictionary take the hashValue of the hashValue.
There is no guarantee that a hashValue will be different from all other the hashValues of different values. In other words, the hash value of "A" can be the same hash value as "B". The dictionary is sophisticated enough to handle that situation and still give you the right value for a particular key, but your code is not handling that.

You can indeed store a non-optional value in a Dictionary and in general you can pass an object of type T whenever a T? is expected.
The real problem is that you haven't told the compiler that T is Hashable.
You have to put a type constraint on T, using T: Hashable. You can do this at a class level (I suppose this method lives inside a generic class), like so
class Foo<T: Hashable> {
var items: Dictionary<Int, T> = [:]
func append(o: T?) {
if let newValue = o {
items[newValue.hashValue] = newValue
}
}
}

Related

Cannot assign value of type 'String??' to type 'String?' error

let say I defined a class
class Dummy {
var title: String?
}
and I have a dictionary as
let foo: [Int: String?] = [:]
then when I make an assignment as below
var dummy = Dummy()
dummy.title = foo[1]
it says
Cannot assign value of type 'String??' to type 'String?'
Insert ' as! String'
return type of foo is String? and Dictionary returns optional of its value type when used subscript but what is String?? type in swift?
I think it should be legal to make such assignment.
Why it complains and how should I make this assignment
Since having a value corresponding to the key 1 in the dictionary foo is optional and the value in the dictionary is of type String? it returns type String??. Unwrapping the value once to check if the value exists would fix this issue
if let value = foo[1] {
dummy.title = value
}
By declaring your dictionary as [Int:String?] you are saying that the key is an Int and values are optional Strings. Now the key may not be present in the dictionary, so foo[1] optionally returns an optional and you end up with an with an optional optional - String??
Now while there are sometimes uses for optional optionals, I don't think that is what you want in this case.
You can simply make a dictionary of [Int:String] and then not insert an element if the value is nil.
let foo: [Int: String] = [:]
dummy.title = foo[1]
If you do need to handle the case where "there is a value for this key and it is nil" then the nil-coalescing operator may help you:
dummy.title = foo[1] ?? nil
or even
dummy.title = foo[1] ?? ""

How do I check for null when unwrapping optionals from a Swift dictionary? [duplicate]

I have the following code in a playground file:
extension Dictionary {
func test() {
for key in self.keys {
self[key]
}
}
}
var dict: [String: AnyObject?] = [
"test": nil
]
dict.test()
I will henceforth refer to the line within the for-each loop as the output since it is what's relevant. In this particular instance the output is nil.
When I change the for-each loop to look like this:
for key in self.keys {
print(self[key])
}
The output is "Optional(nil)\n".
What I really want to do is check the value for nil, but the code:
for key in self.keys {
self[key] == nil
}
outputs false.
One other thing I tried was the following:
for key in self.keys {
self[key] as! AnyObject? == nil
}
which produces the error:
Could not cast value of type 'Swift.Optional<Swift.AnyObject>' to 'Swift.AnyObject'
Any help with this is much appreciated!
You've gotten yourself into kind a mess, because a dictionary whose values can be nil presents you with the prospect of a double-wrapped Optional, and therefore two kinds of nil. There is the nil that you get if the key is missing, and then the nil that you get if the key is not missing and you unwrap the fetched result. And unfortunately, you're testing in a playground, which is a poor venue for exploring the distinction.
To see what I mean, consider just the following:
var d : [String:Int?] = ["Matt":1]
let val = d["Matt"]
What is the type of val? It's Int?? - an Int wrapped in an Optional wrapped in another Optional. That's because the value inside the dictionary was, by definition, an Int wrapped in an Optional, and then fetching the value by its key wraps that in another Optional.
So now let's go a bit further and do it this way:
var d : [String:Int?] = ["Matt":nil]
let val = d["Matt"]
What is val? Well, the playground may say it is nil, but the truth is more complicated; it's nil wrapped in another Optional. That is easiest to see if you print val, in which case you get, not nil, but "Optional(nil)".
But if we try for something where the key isn't there at all, we get a different answer:
let val2 = d["Alex"]
That really is nil, signifying that the key is missing. And if we print val2, we get "nil". The playground fails to make the distinction (it says nil for both val and val2), but converting to a String (which is what print does) shows the difference.
So part of the problem is this whole double-wrapped Optional thing, and the other part is that the Playground represents a double-wrapped Optional in a very misleading way.
MORAL 1: A dictionary whose value type is an Optional can get really tricky.
MORAL 2: Playgrounds are the work of the devil. Avoid them. Use a real app, and use logging to the console, or the variables pane of the debugger, to see what's really happening.
Checking if a dictionary value is nil makes sense only if the
dictionary values are an Optional type, which means that you have to
restrict your extension method to that case.
This is possible by defining a protocol that all optional types conform
to (compare How can I write a function that will unwrap a generic property in swift assuming it is an optional type?, which is just a slight modification of Creating an extension to filter nils from an Array in Swift):
protocol OptionalType {
typealias Wrapped
var asOptional : Wrapped? { get }
}
extension Optional : OptionalType {
var asOptional : Wrapped? {
return self
}
}
Now you can define an extension method with is restricted to
dictionaries of optional value types:
extension Dictionary where Value : OptionalType {
func test() {
for key in self.keys { ... }
}
}
As Matt already explained, self[key] can only be nil if that key
is missing in the dictionary, and that can not happen here. So you
can always retrieve the value for that key
let value = self[key]!
inside that loop. Better, enumerate over keys and values:
for (key, value) in self { ... }
Now value is the dictionary value (for key), and its type
conforms to OptionalType. Using the asOptional protocol property
you get an optional which can be tested against nil:
if value.asOptional == nil { ... }
or used with optional binding.
Putting all that together:
extension Dictionary where Value : OptionalType {
func test() {
for (key, value) in self {
if let unwrappedValue = value.asOptional {
print("Unwrapped value for '\(key)' is '\(unwrappedValue)'")
} else {
print("Value for '\(key)' is nil")
}
}
}
}
Example:
var dict: [String: AnyObject?] = [
"foo" : "bar",
"test": nil
]
dict.test()
Output:
Value for 'test' is nil
Unwrapped value for 'foo' is 'bar'

Why print() is printing my String as an optional?

I have a dictionary and I want to use some of its values as a key for another dictionary:
let key: String = String(dictionary["anotherKey"])
here, dictionary["anotherKey"] is 42 but when I print key in the debugger I see the following:
(lldb) expression print(key)
Optional(42)
How is that possible? To my understanding, the String() constructor does not return an optional (and the compiler does not complain when I write let key: String instead of let key: String?). Can someone explain what's going on here?
As a sidenote, I am currently solving this using the description() method.
This is by design - it is how Swift's Dictionary is implemented:
Swift’s Dictionary type implements its key-value subscripting as a subscript that takes and returns an optional type. [...] The Dictionary type uses an optional subscript type to model the fact that not every key will have a value, and to give a way to delete a value for a key by assigning a nil value for that key. (link to documentation)
You can unwrap the result in an if let construct to get rid of optional, like this:
if let val = dictionary["anotherKey"] {
... // Here, val is not optional
}
If you are certain that the value is there, for example, because you put it into the dictionary a few steps before, you could force unwrapping with the ! operator as well:
let key: String = String(dictionary["anotherKey"]!)
You are misunderstanding the result. The String initializer does not return an optional. It returns the string representation of an optional. It is an non-optional String with value "Optional(42)".
A Swift dictionary always return an Optional.
dictionary["anotherKey"] gives Optional(42), so String(dictionary["anotherKey"]) gives "Optional(42)" exactly as expected (because the Optional type conforms to StringLiteralConvertible, so you get a String representation of the Optional).
You have to unwrap, with if let for example.
if let key = dictionary["anotherKey"] {
// use `key` here
}
This is when the compiler already knows the type of the dictionary value.
If not, for example if the type is AnyObject, you can use as? String:
if let key = dictionary["anotherKey"] as? String {
// use `key` here
}
or as an Int if the AnyObject is actually an Int:
if let key = dictionary["anotherKey"] as? Int {
// use `key` here
}
or use Int() to convert the string number into an integer:
if let stringKey = dictionary["anotherKey"], intKey = Int(stringKey) {
// use `intKey` here
}
You can also avoid force unwrapping by using default for the case that there is no such key in dictionary
var dictionary = ["anotherkey" : 42]
let key: String =
String(dictionary["anotherkey", default: 0])
print(key)

Cannot assign a value of type 'nil' to a value of type 'NSMutableArray'

I am writing swift code & having a simple problem.
I declared a MSMutableArray, under certain condition, I set it to nil:
func doJob() -> NSMutableArray {
var arr = NSMutableArray()
arr = addContentToArray()
if CRITICAL_CONDITION {
//ERROR: Cannot assign a value of type 'nil' to a value of type 'NSMutableArray'
arr = nil
}
return arr
}
But when I set arr to nil, I got compiler error:
Cannot assign a value of type 'nil' to a value of type 'NSMutableArray'
Why? How can I set it to nil then?
You can assign nil only to optional variables. However, when you are letting the type to be inferred, the compiler doesn't know that you are planning to assign nil in the future:
var arr: NSMutableArray? = NSMutableArray()
However, the whole thing about assigning nil to a variable that has previously held an array seems a bit dirty to me. Maybe it would be easier to use a new variable?
You haven't posted your real code so we can't do a real review but:
if CRITICAL_CONDITION {
arr = nil
}
return arr
can be more easily written as
if CRITICAL_CONDITION {
return nil
}
return arr
That will solve the problem, too, because you won't need to reassign the variable. A different approach would be to use a second variable:
var result: NSArray? = array
if CRITICAL_CONDITION {
result = nil
}
return result
or even better
let result = CRITICAL_CONDITION ? nil: array
return result;
The whole point of specifying when a variable cannot be nil (non-optional) is the fact that optional variables are dangerous and you have to check them for nil all the time. So, use optionals only for a short time, ideally one condition and then convert them to non-optional. In this case, use the non-optional as long as possible and only when you really need to assign nil, convert to optional (declare a second, optional variable).

Swift if let evaluates successfully on Optional(nil)

I have a custom object called Field. I basically use it to define a single field in a form.
class Field {
var name: String
var value: Any?
// initializers here...
}
When the user submits the form, I validate each of the Field objects to make sure they contain valid values. Some fields aren't required so I sometimes deliberately set nil to the value property like this:
field.value = nil
This seems to pose a problem when I use an if-let to determine whether a field is nil or not.
if let value = field.value {
// The field has a value, ignore it...
} else {
// Add field.name to the missing fields array. Later, show the
// missing fields in a dialog.
}
I set breakpoints in the above if-else and when field.value has been deliberately set to nil, it goes through the if-let block, not the else. However, for the fields whose field.value I left uninitialized and unassigned, the program goes to the else block.
I tried printing out field.value and value inside the if-let block:
if let value = field.value {
NSLog("field.value: \(field.value), value: \(value)")
}
And this is what I get:
field.value: Optional(nil), value: nil
So I thought that maybe with optionals, it's one thing to be uninitialized and another to have the value of nil. But even adding another if inside the if-let won't make the compiler happy:
if let value = field.value {
if value == nil { // Cannot invoke '==' with an argument list of type '(Any, NilLiteralConvertible)'
}
}
How do I get around this? I just want to check if the field.value is nil.
I believe this is because Any? allows any value and Optional.None is being interpreted as just another value, since Optional is an enum!
AnyObject? should be unable to do this since it only can contain Optional.Some([any class object]), which does not allow for the case Optional.Some(Optional) with the value Optional.None.
This is deeply confusing to even talk about. The point is: try AnyObject? instead of Any? and see if that works.
More to the point, one of Matt's comment mentions that the reason he wants to use Any is for a selection that could be either a field for text input or a field intended to select a Core Data object.
The Swifty thing to do in this case is to use an enum with associated values, basically the same thing as a tagged/discriminated union. Here's how to declare, assign and use such an enum:
enum DataSelection {
case CoreDataObject(NSManagedObject)
case StringField(String)
}
var selection : DataSelection?
selection = .CoreDataObject(someManagedObject)
if let sel = selection { // if there's a selection at all
switch sel {
case .CoreDataObject(let coreDataObj):
// do what you will with coreDataObj
case .StringField(let string):
// do what you will with string
}
}
Using an enum like this, there's no need to worry about which things could be hiding inside that Any?. There are two cases and they are documented. And of course, the selection variable can be an optional without any worries.
There's a tip to replace my Any? type with an enum but I couldn't get this error out of my head. Changing my approach doesn't change the fact that something is wrong with my current one and I had to figure out how I arrived at an Optional(nil) output.
I was able to reproduce the error by writing the following view controller in a new single-view project. Notice the init signature.
import UIKit
class Field {
var name: String = "Name"
var value: Any?
init(_ name: String, _ value: Any) {
self.name = name
self.value = value
}
}
class AppState {
var currentValue: Field?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let f = Field("Amount", AppState().currentValue)
NSLog("\(f.value)")
}
}
In short, I was passing a nil value (AppState().currentValue) to an initializer that accepts Any, and assigns it to a property whose type is Any?. The funny thing here is if I directly passed nil instead, the compiler will complain:
let f = Field("Amount", nil) // Type 'Any' does not conform to protocol 'NilLiteralConvertible'
It seems that somewhere along the way, Swift wraps the nil value of AppState().currentValue in an optional, hence Optional(nil).

Resources