• Swift Tutorial: Optionals and Control Flow

    25 February 2015
    In this article, we zoom in on control flow in Swift. Before we can discuss control flow in more detail, we need to take a look at a concept that is new to most of you, optionals. Optionals are another safety feature of Swift. At first, it may look like a hassle to use optionals, but you'll quickly learn that optionals will make your code much safer.

    Optionals

    We've already seen that a variable must be initialized before it can be used. Take a look at the following example to better understand what this means.
     
    var str: String
    
    str.isEmpty
    
    If you're used to working with strings in Objective-C, then you may be surprised that Swift shows you an error. Let's see what that error tells us. In many languages, variables have an initial default value. In Objective-C, for example, the string in the following code snippet is equal to nil.
     
    NSString *newString;
    
    However, the concept of nil differs in Swift and Objective-C. We'll discuss nil in more detail a bit later.

    What is an optional?

    Swift uses optionals to encapsulate an important concept, that is, a variable or constant has a value or it hasn't. It's that simple in Swift. To declare a variable or constant as optional, we append a question mark to the type of the variable or constant.
     
     
    var str: String?
    
    The variable str is no longer of type String. It is now of type optional String. This is important to understand. The result or side effect is that we can no longer directly interact with the value of the str variable. The value is safely stored in the optional and we need to ask the optional for the value it encapsulates.

    Forced Unwrapping

    One way to access the value of an optional is through forced unwrapping. We can access the value of the variable str by appending an ! to the variable's name.
     
    
    var str: String?
    
    str = "Test"
    
    println(str!)
     
    It's important that you are sure that the optional contains a value when you force unwrap it. If the optional doesn't have a value and you force unwrap it, Swift will throw an error at you.

    Optional Binding

    There is a safer way to access the value of an optional. We'll take a closer look atif statements in a few minutes, but the following example shows how we can safely access the value stored in the variable str, which is of type optionalString.
     
    
    var str: String?
    
    if str != nil {
    
    println(str!)
    
    } else {
    
    println("str has no value")
    
    }
     
    We first check if the variable str is equal to nil before we print its contents. In this example, str doesn't have a value, which means it won't be forced unwrapped by accident. There's a more elegant approach called optional binding. In the following example, we assign the value stored in the optional to a temporary constant, which is used in the if statement. The value of the optional str is bound to the constantstrConst and used in the if statement. This approach also works for while statements.
     
    
    var str: String?
    
    str = "Test"
    
    if let strConst = str {
    
    println(strConst)
    
    } else {
    
    println("str has no value")
    
    }
    

    What is nil?

    If you're coming from Objective-C, then you most certainly know what nil is. In Objective-C, nil is a pointer to an object that doesn't exist. Swift defines nil a bit differently and it's important that you understand the difference. In Swift, nil means the absence of a value, any value. While nil is only applicable to objects in Objective-C, in Swift nil can be used for any type. It's therefore important to understand that an optional isn't the equivalent of nil in Objective-C. These concepts are very different.

    Control Flow

    Swift offers a number of common constructs to control the flow of the code you write. If you have any experience programming, then you'll have no problems getting up to speed with Swift's control flow constructs, conditional if and switch statements, and for and while loops. However, Swift wouldn't be Swift if its control flow didn't slightly differ from, for example, Objective-C's control flow constructs. While the details are important, I'm sure they won't hinder you from getting up to speed with Swift. Let's start with the most common conditional construct, the if statement.

    if

    Swift's if statements are very similar to those found in Objective-C. The main difference is that there's no need to wrap the condition in parentheses. Curly braces, however, are mandatory. The latter prevents developers from introducing common bugs that are related to writing if statements without curly braces. This is what anif statement looks like in Swift.
     
    
    let a = 10
    
    if a > 10 {
    
    println("The value of \"a\" is greater than 10.")
    
    } else {
    
    println("The value of \"a\" is less than or equal to 10.")
    
    }
     
    It should come to no surprise that Swift also defines an else clause. The code in the else clause is executed if the condition is equal to false. It's also possible to chain if statements as shown in the next example.
     
    
    let a = 10
    
    if a > 10 {
    
    println("The value of \"a\" is greater than 10.")
    
    } else if a > 5 {
    
    println("The value of \"a\" is greater than 5.")
    
    } else {
    
    println("The value of \"a\" is less than or equal to 5.")
    
    }
     
    There is one important note to make, that is, the condition of an if statement needs to return true or false. This isn't true for if statements in Objective-C. Take a look at the following if statement in Objective-C.
     
    
    NSArray *array = @[];
    
    if (array.count) {
    
    NSLog(@"The array contains one or more items.");
    
    } else {
    
    NSLog(@"The array is empty.");
    
    }
     
    If we were to port the above code snippet to Swift, we would run into an error. The error isn't very informative, but Swift does tell us that we need to ensure the result of the condition evaluates to true or false. The correct way to translate the above Objective-C snippet to Swift is by making sure the condition of the if statement evaluates to true or false, as in the following snippet.
     
    
    let array = [String]()
    
    if array.count > 0 {
    
    println("The array contains one or more items.")
    
    } else {
    
    println("The array is empty.")
    
    }
     

    switch

    Swift's switch statement is more powerful than its Objective-C equivalent. It's also safer as you'll learn in a moment. While there are some differences, switchstatements in Swift adhere to the same concept as those in other programming languages, a value is passed to the switch statement and it is compared against possible matching patterns. That's right, patterns. Like I said, a switch statement in Swift has a few tricks up its sleeve. We'll take a look at those tricks in a moment. Let's talk about safety first.

    Exhaustive

    A switch statement in Swift needs to be exhaustive, meaning that every possible value of the type that's handed to the switch statement needs to be handled by theswitch statement. As in Objective-C, this is easily solved by adding a defaultcase like in the following example.
     
    let a = 10
    
    switch a {
    
    case 0:
    
    println("a is equal to 0")
    
    case 1:
    
    println("a is equal to 1")
    
    default:
    
    println("a has another value")
    
    }
     

    Fallthrough

    An important difference with Objective-C's implementation of switch statements is the lack of implicit fallthrough. The following example doesn't work in Swift for a few reasons.
     
    
    let a = 10
    
    switch a {
    
    case 0:
    
    case 1:
    
    println("a is equal to 1")
    
    default:
    
    println("a has another value")
    
    }
     
    The first case in which a is compared against 0 doesn't implicitly fall through to the second case in which a is compared against 1. If you add the above example to your playground, you'll notice that Swift throws an error at you. The error says that every case needs to include at least one executable statement. Notice that the cases of the switch statement don't include break statements to break out of the switch statement. This isn't required in Swift since implicit fallthrough doesn't exist in Swift. This will eliminate a range of common bugs caused by unintentional fallthrough.

    Patterns

    The power of a switch statement in Swift lies in pattern matching. Take a look at the following example in which I've used ranges to compare the considered value against.
     
    let a = 10
    
    switch a {
    
    case 0..<5:
    
    println("The value of a lies between 0 and 4.")
    
    case 5...10:
    
    println("The value of a lies between 5 and 10.")
    
    default:
    
    println("The value of a is greater than 10.")
    
    }
     
    The ..< operator or half-open range operator defines a range from the first value to the second value, excluding the second value. The ... operator or closed range operator defines a range from the first value to the second value, including the second value. These operators are very useful in a wide range of situations. You can also compare the considered value of a switch statement to tuples. Take a look at the following example to see how this works.
     
    let latlng = (34.15, -78.03)
    
    switch latlng {
    
    case (0, 0):
    
    println("We're at the center of the planet.")
    
    case (0...90, _):
    
    println("We're in the Northern hemisphere.")
    
    case (-90...0, _):
    
    println("We're in the Southern hemisphere.")
    
    default:
    
    println("The coordinate is invalid.")
    
    }
     
    As you can see in the above example, it is possible that the value matches more than one case. When this happens, the first matching case is chosen. The above example also illustrates the use of the underscore. As we saw in the previous article, we can use an underscore, _, to tell Swift which values we're not interested in.

    Value Binding

    Value binding is also possible with switch statements as the following example demonstrates. The second value of the tuple is temporarily bound to the constantdescription for use in the first and second case.
     
    var response = (200, "OK")
    
    switch response {
    
    case (200..<400, let description):
    
    println("The request was successful with description \(description).")
    
    case (400..<500, let description):
    
    println("The request was unsuccessful with description \(description).")
    
    default:
    
    println("The request was unsuccessful with no description.")
    
    }
    

    for

    The for loop is the first loop construct we'll take a look at. It behaves very similar tofor loops in other languages. There are two flavors, the for loop and the for-in loop. Swift's for loop is almost identical to a for loop in Objective-C as the following example illustrates. The for loop executes a number of statements until a predefined condition is met.
     
    
    for var i = 0; i < 10; i++ {
    
    println("i is equal to \(i).")
    
    }
     
    As with if statements, there's no need to use parentheses to enclose the loop's initialization, condition, and increment definitions. The loop's statements, however, do need to be enclosed by curly braces.

    for-in

    The for-in loop is ideal for looping over the contents of a range or collection. In the following example, we loop over the elements of an array.
     
    
    let numbers = [1, 2, 3, 5, 8]
    
    for number in numbers {
    
    println("number is equal to \(number)")
    
    }
    
    
    We can also use for-in loops to loop over the key-value pairs of a dictionary. In the following example, we declare a dictionary and print its contents to the console. As we saw earlier in this series, the sequence of the key-value pairs is undefined since a dictionary is an unordered set of key-value pairs.
     
    var bids = ["Tom": 100, "Bart": 150, "Susan": 120]
    
    for (name, bid) in bids {
    
    println("\(name)'s bid is $\(bid).")
    
    }
    
    Each key-value pair of the dictionary is available in the for-in loop as a tuple of named constants. The for-in loop is also a great in combination with ranges. I'm sure you agree that the below snippet is easy to read and understand thanks to the use of a closed range.
     
    
    for i in 1...10 {
    
    println("i is equal to \(i)")
    
    }
     

    while

    The while loop also comes in two variations, while and do-while. The main difference is that the set of statements of a do-while loop is always executed at least once, because the condition of the do-while is evaluated at the end of each iteration. The following example illustrates this difference.
     
    var c = 5
    
    var d = 5
    
    while c < d {
    
    println("c is smaller than d")
    
    }
    
    do {
    
    println("c is smaller than d")
    
    } while c < d
    
    The println statement of the while loop is never executed while that of the do-while loop is executed once. In many cases, for loops can be rewritten as while loops and it's often up to the developer to determine which type of loop to use in a particular situation. The following for and while loops result in the same output.
     
    
    for var i = 0; i < 10; i++ {
    
    println(i)
    
    }
    
    var i = 0
    
    while i < 10 {
    
    println(i)
    
    i++
    
    }
    

Comments

Comments closed on this post.