Why, oh Why, did Apple take away ARC just to give us Optionals!?

I’m having trouble understanding optionals so here is a shot at explaining them. ūüôā

var thisIsAnInt: Int

Simple, this is an Int variable declaration.

var couldBeAnInt: Int?

This on the other hand is an optional variable declaration.

This optional declaration doesn’t mean: “this is an int, which is optional”.

It reads more like: This is an OPTIONAL variable. ¬†It’s a type of variable in and of itself. ¬†Its NOT NECESSARILY an Int. ¬†It just may or may NOT contain an Int”.

Woah!

Ok so what is it for and when do you use it.  Well that part seems simple enough:

If that variable can or could or may be nil at some point, it’s an optional.

If the variable will always have a value, it will never be nil, its NOT an optional.

Don’t be misled by this simplicity. ¬†Some variables can start out as nil and receive a value at some point. ¬†What then?

Well it turns out there is a sort of table, if you will:

  1. Can NEVER be nil (or if it IS, its a bug) = non-optional variable….you know, the regular kind
  2. Starts out nil but NEVER ends up nil after init (or if it is, its a bug) = implicitly unwrapped optional
  3. Nil value has meaning and is expected (if it is, its NOT a bug) = optional

Yeah, thanks for confusing me :S

Here is another way around it:

var perhapsInt: Int? //this is perhaps an Int
perhapsInt = 1 //here we assign it an int
if perhapsInt != nil { //now we check to see if its nil before using it
     let intString = String(perhapsInt!) //IF it isn't, then we can access it by using !
     println(intString)
}

you can also check for that in a different way:

var perhapsInt:Int?
perhapsInt = 1 
if let actualInt = perhapsInt {
     println(actualInt)
}

Here is the kicker:

var perhapsInt: Int?
let definiteInt = perhapsInt ?? 2
println(definiteInt) // prints 2
perhapsInt = 3
let anotherInt = perhapsInt ?? 4
println (anotherInt) // prints 3

And it¬†gets trickier what with ?? and variable != nil. ¬†So let’s throw everything out the window and start anew. ¬†Let’s define a function we might actually be interested in:

Assume we have an array of locations:

let errorCodes = ["100","200","300","400", "500"]
func findErrorCode (code : String, errorCodes: [String])-> String {
for tempCode in errorCodes {
     if ( tempCode == code) {
     return code
     }
}
return ""
}

This is a function we wrote to find a particular code¬†inside an array of possible error codes. ¬†We call it by passing it in a code¬†and the array of possible codes. ¬†If we pass it a code¬†that is NOT in the array, let’s say “700”, then the “internal if” will not evaluate to true and we must return nil. ¬†We must return nil because otherwise, because the function is meant to return a String (the name of the matched code) && that “if block” didn’t hold true, you get a compiler error because we are not being exhaustive. ¬†This means, if we pass the function a value that IS in the array, great, we will get a ‘return code’. ¬†However, if we pass it a value that is NOT in the array, the function would not be returning anything. ¬†So we NEED to return something for it to work.

We could return an emply value but consider the following. ¬†Let’s say we wish to display that code¬†returned in the console, via println (or plot it on a map of servers for example). ¬†The point being that we need to do something with the returned code:

if findCode("700", errorCodes) {
	plotOnMap()
}

So how do we account for such a possibility? ¬†We need to define the return value of the function findCode as an optional. ¬†To do this we add this to the variable “?”. ¬†Here is what the function would look like:

func findCode (code : String, errorCodes: [String])-> String? {
for tempCode in errorCodes {
     if ( tempCode == code) {
          return code
     }
}
return nil
}

Now we can actually return a String value, maybe, or we could be returning an Optional value.

Great, now let’s asume the plot function looks like this:

func plotOnMap (code: Int) -> () {
     println("This line of code magically plots the code on a server map :)")
}

Ok so now we can say

let receiver = findCode("200",errorCodes)
if receiver {
	plotOnMap(receiver);
}

The issue is that the value returned by findCode, is Optional.  So the value of receiver will be optional.

This is what is called “implicitly defined optional“. ¬†We have implicitly defined receiver to be an optional because the return of the function is an optional itself.

We also have another issue, plotOnMap() takes an Int, not a String.  So we have actually defined receiver as:

let receiver: String? = findCode("400",errorCodes) // inferred type String?

Notice we have defined it as String?, not String.  This means receiver can hold a String or a nil, because its being defined as an Optional type, not a String type.

So what can we do?  We can ask it for a value only if it HAS one:

let receiver = findCode("400",errorCodes) 
if receiver {
	plotOnMap(receiver!) // We use of the ! operator to unwrap the value to String
}

This would indeed fix the problem. ¬†You have solved the possibility of an optional in a function you wrote yourself. ¬†But there is always the other case, values returned by functions you DIDNT write! ¬†Ugh! ¬†Remember I mentioned that our plotOnMap() takes an Int, and not a String? ¬†And we are in fact unwrapping an implicitly defined variable (called receiver) into a String (cause that’s what the function returns? ¬†Well we need to convert it into an int, which is simple in Swift, just say string.toInt(). ¬†This means we have to do this:

if let receiver = findCode("400",errorCodes) {
	plotOnMap(receiver.toInt())
}

But if you jump to definition on toInt(), you will notice that it also returns an optional.  So we would need to do something more convoluted:

if let receiver = findCode("400",errorCodes) {
	if let receiverErrorCode = receiver.toInt() {
		plotOnMap(receiverErrorCode)
	}
}

which is quite confusing. ¬†So there is a Swift syntax which let’s you condense this into:

if let receiverErrorCode = findCode("400",errorCodes)?.toInt() {
	plotOnMap(receiverErrorCode)
}

It would be prudent just to add an else to the if to be safe ūüôā

Leave a Reply