Intro
As ruby developers, we often learn to handle nils as follows:
def some_method(some_var)
some_var.try!(:some_method)
end
or
def some_method(some_var)
some_var.some_method if some_var
end
If we don't do this, we will get the dreaded "NoMethodError: undefined method some_method
for nil:NilClass
" in cases where the variables/objects we pass to method may be nil. This can happen if a user does not fill out a field in a form, or when we read a field from a database whose value is unknown. This approach is brittle, though, since it's not always appropriate to handle nils gracefully everywhere. If we made a function that expects a string, it would be more appropriate in some cases for the caller of the function to ensure that no nils get passed.
Crash course in Swift optionals
Recently, we have been working on developing our first iOS application. We have decided to write this application in Swift, Apple's latest programming languge. One advantage of Swift is that the compiler can help us find nil errors in some cases. It does this via "Optionals".
func sayHello(name: String) {
println("hello \(name)!!!")
}
let names = [
1: "Joe",
2: "Bob",
]
sayHello(names[1])
The above code will not compile because names is a dictionary, and in Swift, a dictionary will return nil if the key that is passed does not have a corresponding value. The advantage here is that sayHello
does not have to worry about nil values because it can be certain that only strings are ever passed to it. If we wanted to get the real value out of this dictionary, we need to unwrap the optional value.
// safe unwrapping, prevents runtime errors, more boilerplate
if let name = names[1] {
sayHello(name)
}
In the above example, we "unwrap" the optional and assign it to a local constant. From within the if
statement, the compiler knows that the name constant is a string and safe to pass to any function that expects a string, or to have string functions called on it.
// explicit unwrapping, can cause runtime errors if not careful
sayHello(names[1]!)
The above example shows how to use explicit unwrapping. With this we tell the compiler that we don't care that names[1]
may contain nil; just unwrap it and send it to sayHello
. This will throw a runtime error if names[1]
is nil. This may be more appropriate in situations like above where the list of values is small and known ahead by the developer. The code is simple enough for the developer to know that indexes 1 and 2 are safe.
var name: String?
if someCondition {
name = "Bob dole"
}
println(name)
The above example shows how to declare an optional variable. We do this by appending a '?' after the type. If we did not do this, we would get a compilation error saying that the variable was used before being initialized. This is because if the if
statement does not execute, name
would be nil and only optionals can contain nil values. Anywhere that a type can be declared (variable/constant assignment, parameters to functions) can define a type to be optional.
// assume user.username and user.password are optional strings
var username: String! = user.username
var password: String! = user.password
// assume authenticate takes two strings
if User.authenticateLogin(username, password: password) {
println("welcome \(username)")
}
The above example shows how to declare an "implicitly unwrapped" optional. Implicitly unwrapped optionals allow optional values to be passed to functions of the same non-optional type without compilation errors and without the boilerplate of safely unwrapping them. Implicitly unwrapped optionals also can be "safely" unwrapped if the developer feels that it is necessary. In general, use implicitly unwrapped optionals in cases where the value should never be nil but the compiler cannot infer it.
Practical uses of implicitly unwrapped optionals
You might wonder why you would sacrifice the type safety introduced by optionals. In most cases, it's where a variable can never be nil but the compiler does not know that nil should not be possible. One example of this can be a controller that is initialized via a storyboard. To follow along, clone source code: git clone git@github.com:patientslikeme/SwiftOptionalExamples.git
. Note that ViewController.swift
is defined as follows.
import UIKit
class ViewController: UIViewController {
@IBOutlet var usernameField: UITextField!
@IBOutlet var passwordField: UITextField!
@IBOutlet var authenticateButton: UIBarButtonItem!
@IBAction func authenticate() {
// prevent double taps
authenticateButton.enabled = false
let username = usernameField.text
let password = passwordField.text
if User.authenticateUsername(username, password: password) {
// present success message
} else {
// present fail message
}
authenticateButton.enabled = true
}
}
Inspecting the storyboard with Interface Builder, you will notice that usernameField
, passwordField
and authenticateButton
are defined. The compiler does not know this, though, since they are defined after initialization. Removing the ! from these variables will cause a compilation error since the compiler does not allow non optional values from containing nil. It is impractical to "safely" unwrap these variables since a nil value would mean the programmer made a mistake and should probably correct this. In summary, use implicitly unwrapped optionals in cases where nil is not a valid value.
Practical examples of explicitly unwrapped optionals
Above we saw an example of when we can tell the compiler it can relax. Let's now see an example of where we would want a programmer to explicitly unwrap an optional and have the . Modify User.swift
and ViewController.swift
as follows (or type in git checkout step-2
from root of example project):
(User.swift)
// rest of code
let users = [
User(firstName: "Tobias", lastName: "Fünke", username: "frightenedinmate2", password: "gymnophobic"),
User(firstName: "GOB", lastName: "Bluth", username: "magicGOB", password: "2000dollarsuit"),
]
class User {
// rest of code
class func authenticateUsername(username: String, password: String) -> User? {
for user in users {
if user.password == password && user.username == username {
return user
}
}
return nil
}
// rest of code
}
(ViewController.swift)
// rest of code
class ViewController {
// rest of code
func authenticate() {
// rest of code
if let user = User.authenticateUsername(username, password: password) {
let alert = UIAlertController(title: "Good job",
message: "thanks for visiting \(user.firstName) \(user.lastName)",
preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
presentViewController(alert, animated: true, completion: nil)
} else {
// present fail message
}
// rest of code
}
// rest of code
}
In this example, we modify User.authenticateUsername
to return a user if it has a correct username and password or nil otherwise. If the method returns a user, we greet the user; otherwise, we tell them to try again.
Conclusion
Optionals give us additional type safety, preventing us from calling methods or passing nil objects to places where they are not expected. Sometimes, the developer knows better than the compiler and can force unwrapping with implicitly unwrapped optionals. In general, though, use explicitly unwrapped optionals in cases where factors outside of the programmer's control (for example, user input) can cause nils. Use implicitly unwrapped optionals where nil is an invalid state but the compiler does not know that the value will be initialized before use, such as UI components from a storyboard. Everywhere else, use non-optional types so the compiler will warn us when we try passing an unwrapped optional (that may be nil) to a method.