Implicitly Unwrapped Optionals? Damn!

This is the first article in the series of “Swift? Damn!” articles. If you hadn’t read the introduction then please do it now.

So… we are writing a game and  in the same time friends of mine studying Swift and asking me about all unclear things. Optionals were the first topic on which I got hard questions. From Objective-c perspective optional value which you define in normal way with question mark is very similar to nil. However, implicitly unwrapped optionals is something that looks very wrong.

It wasn’t a thing I can explain 100% clearly. I read Apple’s docs and several articles on SO. It have not made IUO more clear than it was. I decided to ask my colleagues in Dynamo. Our team of iOS developers is incredible strong and includes Jack Nutting, Reda Lemeden, Gabriel Roupillard and many more, really smart world-class mobile developers.

I tried to contact several people from Swift team to get an official answer, but with no luck :(

Anyway… Let’s start with definition.

Sometimes it is clear from a program’s structure that an optional will always have a value, after that value is first set. In these cases, it is useful to remove the need to check and unwrap the optional’s value every time it is accessed, because it can be safely assumed to have a value all of the time.

These kinds of optionals are defined as implicitly unwrapped optionals. You write an implicitly unwrapped optional by placing an exclamation mark (String!) rather than a question mark (String?) after the type that you want to make optional.

The Swift Programming Language (Swift 3.0.1)

So… IUO is the same as regular optional but behaves as non-optional on access and has no initial value. If you access it before value assigned then you get a crash. You do not need to use “?” or “!” to access the value. It is promised (but not guaranteed) that value will be assigned before use.

Where IUO is used. Potentially in all cases if initial value cannot be obtained on definition of variable or constant, but assumed to be defined on the time when code will use it, for example:

Why IUO are unsafe.

First of all, IUO is the developer’s promise that value will be assigned. This means that anything may happen after that. You may forget to assign value in some branch of the code, or async code will play bad joke, or XIB/Storyboard will not contain referenced outlets.

Second thing comes from the code example which I found in NatashaTheRobot’s article: What You Need to Know About Implicitly Unwrapped Optionals

Here is a bit modified code:

func valueUp(value: Int!) {
  let oldValue = value 
  // Optional(Int)
  let newValue = value + 1 
  // Int
  let anotherValue = 5 
  // Int as expected

  let values1 = [oldValue, newValue, anotherValue] 
  // [Int?] - [Some(10), Some(11), Some(5)]

 let unwrappedValues = [oldValue!, newValue, anotherValue] // [Int] - [10, 11, 5] 
}

valueUp(value: 10) 

I see interesting thin in the last line of the code: implicitly unwrapped optional is unwrapped to get the value. Well… if it supposed to have value when it accessed isn’t it overhead to unwrap it again?

Third, I think most interesting thing, happens then IUO meets type inference. In this case IUO can behave as optional and as non-optional value in the same time and compiler will not notify you about any problem.

Let’s see on example:

let keyValue: Int! = 1
let optionalValue: Int? = 2
let strictValue: Int = 3

var array1 = [keyValue, optionalValue]
// array1 - [Int?]: [Optional(1), Optional(2)]
var array2 = [strictValue]
// array2 - [Int]: [3]
array2.append(keyValue)
// array2 - [Int]: [3, 1]

var array3 = [strictValue, keyValue] 
// ...

The code defines three constants with IUO (keyValue), optional and non-optional types. Additionally, three arrays defined via literals.

It is interesting to look on keyValue behaviour against these arrays:

  1. If keyValue passed with another optional value, then resulting array type will have [Optional(Int)] type
  2. If it’s value appened to array2 (which inferring [Int] type) then keyValue will be silently unwrapped and appended as Int type. This is also predictable behaviour because of IUO definition

Third behaviour come to scene if IUO passed to initialization together with non-optional value.

What is expected result should be here?

Due to type inference I see two possibilities:

  1. Type of array3 array will be infered as [Int]. Why? Swift will check all values and select most appropriate: Int or Int? this is obvious. I also expect that it will process values the same way, as in any another situation when you combine non-optional and optional value. For example:
      var a: Int! = 1
      var b: Int = 2
      print(a+b)
      // Output: "3" and not "Optional(3)
    
  2. Compiler will notify me that I’m trying to initialize array with wrong data types. This is probably incorrect expectation, because keyValue is IUO, but it looks safer.

However, result array is type of [Int?].

var array3 = [strictValue, keyValue] 
// array3 - [Int?] : [Optional(3), Optional(1)] 

I searched for explanation of this behaviour, but haven’t find anything that explains why this happens.

Finally, take a look on the code:

var array3 =        [strictValue, keyValue]
var array4:[Int] =  [strictValue, keyValue]
var array5:[Int?] = [strictValue, keyValue]

All these definitions will work in one block of code without warnings from compiler. Variables used to initialise arrays are the same, but result types of arrays are different.

This is soooooo unswifty!

Discussion with Dynamates have ended up with conclusions that:

  1. IUO should be used in very few special cases and it is good practice to avoid IUO in your own code.
  2. IUO as a parameters for methods is a sign of bad architecture
  3. Do not rely of type inference if you have any chance that IUO type will be passed to collection initialization.

 

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *