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:
- Can NEVER be nil (or if it IS, its a bug) = non-optional variable….you know, the regular kind
- Starts out nil but NEVER ends up nil after init (or if it is, its a bug) = implicitly unwrapped optional
- 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 🙂