4

If I have an implicitly unwrapped optional declared in my class which I then reference in a Dictionary of type [String : Any], it doesn't get unwrapped. Why is this? Why does Any, which isn't optional, not force it to unwrap?

var aString: String! = "hello"
var params : [String : Any] = [
    "myString" : aString
]
print(params)
// This prints ["myString": Swift.ImplicitlyUnwrappedOptional<Swift.String>.some("hello")]

Note that if I specify the dictionary to be of type [String : String], it will be unwrapped, but this is not useful for when I need multiple types in my Dictionary.

Hamish
  • 78,605
  • 19
  • 187
  • 280
Tometoyou
  • 7,792
  • 12
  • 62
  • 108
  • You can't have different types in a Dictionary. By definition - All keys have to be of the same type, and all values have to be of the same type. – Martin Muldoon Apr 02 '18 at 11:12
  • @MartinMuldoon This is not true, see: https://developer.apple.com/documentation/swift/dictionary – Tometoyou Apr 02 '18 at 11:14
  • It is true. All keys must be of the same type. All values must be of the same type. Type of key does not have to be same as Type of value. So you can have [String : Int] for example. – Martin Muldoon Apr 02 '18 at 11:20
  • Please try it out in Playground before commenting. I can literally specify keys and values of `String` and `Int` within the same dictionary as a heterogeneous collection of type `[AnyHashable : Any]` – Tometoyou Apr 02 '18 at 11:26
  • BTW mentioned dictionary is printing `["myString": hello]`. There is no error or warning like this. [See this](https://i.imgur.com/g9uIGFR.png) – TheTiger Apr 02 '18 at 13:21
  • @Hamish Why doesn't specifying the value as `Any` instead of `Any?` make it force unwrap? Btw I'm using Swift 4.1, how come it hasn't been removed? – Tometoyou Apr 02 '18 at 15:51
  • @TheTiger You probably haven't updated Xcode to the latest version. – Tometoyou Apr 02 '18 at 15:52
  • @Hamish Ok thanks, that makes sense! If you post your comments as answers I'll accept it. – Tometoyou Apr 02 '18 at 17:30
  • @Tometoyou Yes, using xcode 9.0. So you mean this is only in `Swift 4.1`? – TheTiger Apr 03 '18 at 04:16
  • @Tometoyou Sorry for the delay; I've gone ahead and written up an answer covering the points I made in my comments (and so I've now deleted those comments). – Hamish Apr 07 '18 at 14:50

4 Answers4

10

Under the rules set out by SE-0054, IUOs are only force unwrapped in contexts that demand their unwrapped type. In your case, the IUO doesn't need to be force unwrapped in order to be coerced to Any (as Any can represent any value), so it isn't.

This behaviour is discussed in more detail in these Q&As:

The fact that you end up with an ImplicitlyUnwrappedOptional value in your dictionary is legacy behaviour that has been removed in the latest Swift snapshots, in the future you will end up with an Optional value instead (as IUO is no longer a type).

One important thing to note here however (that I'm sure will trip up people) is that the printing of IUOs got changed in 4.1.

In Swift 4.0.3, your example prints like this:

var aString: String! = "hello"
var params : [String : Any] = [
  "myString" : aString
]
print(params)
// This prints ["myString": hello]

giving you the illusion that the IUO was force unwrapped when coerced to Any. This however is just how IUOs were printed in Swift 4.0.3 – if they had a value, then they would print as that value, otherwise they would print as nil:

var aString: String! = nil
var params : [String : Any] = [
  "myString" : aString
]
print(params)
// This prints ["myString": nil]

The reason why this changed in Swift 4.1 is that ImplicitlyUnwrappedOptional's conformance to Custom(Debug)StringConvertible was removed in this commit in order to make progress towards removing the type itself. So now ImplicitlyUnwrappedOptional values get printed using Swift's default printing mechanism (using reflection).

So, in a dictionary, you get the IUO's default debugDescription, which looks like this:

let aString: String! = "hello"
let params : [String : Any] = [
  "myString" : aString
]
print(params)
// This prints ["myString": Swift.ImplicitlyUnwrappedOptional<Swift.String>.some("hello")]

If you had printed it on its own, you would get its default description, which looks like this:

let aString: String! = "hello"
print(aString) // some("hello")

This is because in Swift 4.1, the ImplicitlyUnwrappedOptional type is implemented in the same way as Optional, an enumeration with two cases:

public enum ImplicitlyUnwrappedOptional<Wrapped> : ExpressibleByNilLiteral {
  // The compiler has special knowledge of the existence of
  // `ImplicitlyUnwrappedOptional<Wrapped>`, but always interacts with it using
  // the library intrinsics below.

  /// The absence of a value. Typically written using the nil literal, `nil`.
  case none

  /// The presence of a value, stored as `Wrapped`.
  case some(Wrapped)

  // ...
}

For an IUO with a payload value, Swift's default reflection will therefore print it as the case some containing the wrapped value.

But this is only temporary; the IUO type is currently (in Swift 4.1) deprecated, however it will be removed in Swift 4.2. The compiler was internally using the IUO type in quite a few places, which took quite a bit of work to remove. Therefore in 4.2 you'll have actual Optional values in your dictionary, which will print like Optional("hello").

Hamish
  • 78,605
  • 19
  • 187
  • 280
0

Change this line:

var params : [String : Any] = ["myString" : aString]

to:

var params : [String : String] = ["myString" : aString]
Martin Muldoon
  • 3,388
  • 4
  • 24
  • 55
  • I've mentioned this in my post already, but that's not helpful for the use case of a `Dictionary` that accepts multiple types – Tometoyou Apr 02 '18 at 10:33
  • Because Any can be anything... Int, Double, Class, so it doesn't have a clue what you want. You clearly want it to be a String type so cast it that way. [String : String] – Martin Muldoon Apr 02 '18 at 10:39
  • Yes but Int, Double, Class are all unwrapped types. Which I'd have thought means that the IUO would be unwrapped... – Tometoyou Apr 02 '18 at 10:41
  • OK.. Great question. When retrieving a value from a dictionary, it is always an optional. You must unwrap it. The reason for this is that the Dictionary might be empty. The compiler won't know until you attempt to access the value. – Martin Muldoon Apr 02 '18 at 10:44
  • Did this make sense? – Martin Muldoon Apr 02 '18 at 11:02
0

When you define a dictionary of type

let dictionary = [String:Any]()

you can put anything in this dictionary

like

 dictionary["name"] = "xyz"

and

 dictionary["code"] = 123

while in

let dictionary = [String:String]()

you can only put an only string value

that's why you have to unwrap value when you are accessing the value because it can be anyting

Devil Decoder
  • 966
  • 1
  • 10
  • 26
0
  • Any can represent an instance of any type at all, including function types and optional types.
  • AnyObject can represent an instance of any class type.

And your aString is an instance object which should not be use to declare in property Area, if you put that line in any function/instance function()then it will work perfectly, because it will be your instance type then.

In One word, you can not declare instance type in property area in Swift.

override func viewDidLoad() {
   let params : [String : Any] = ["myString": aString]
}

Even in Property Area, You can not do like following:

var aString: String! = "String"
var abc = aString

You have to abc = aString do in any Method() to get work.

Hope it will help you.

Abhishek Mitra
  • 3,335
  • 4
  • 25
  • 46
  • If dictionary declared outside the function then how does it print? – TheTiger Apr 02 '18 at 13:18
  • @TheTiger Well, A Dictionary consists of Key Value Compliant, In swift every thing perform under any function, if you want to declare a dictionary in Property area means out side of an function and within a class, then you have to provide string for key and value Like this: `var testDic : [String : Array] = ["someStringKey" : ["1", "2"]]` and `var testDic1: Dictionary = ["someStringKey" : ["1", "2"]]` – Abhishek Mitra Apr 02 '18 at 13:25
  • I'm saying dictionary probably declared in any function body thats why OP is able to print `params` otherwise it will not even compile. – TheTiger Apr 02 '18 at 13:27
  • I did the same in my way and found expected result, I don't know how he got that type of result. @TheTiger Where as, you can not declare like the question in property area. – Abhishek Mitra Apr 02 '18 at 13:29