Thứ Hai, 23 tháng 6, 2014

A Beginner’s Guide to Optionals in Swift

0 Flares 0 Flares ×

Swift was announced three weeks ago. Since then, I have been reading the Swift’s official guide and playing around with it in Xcode 6 beta. I started to love the simplicity and syntax of Swift. Along with my team, I am still studying the new language and see how it compares with Objective-C, a 30-year-old programming language. At the same time, we’re working really hard to see how we can teach beginner and help the community to pick up Swift effortlessly.

Two weeks ago, we covered the basics of Swift. In coming weeks, we’ll write a series of tutorials to cover a number of new features in Swift. This week, let’s first take a look at optionals.

swift-optionals-featured

Optionals Overview

I’ve mentioned optionals in the previous post but didn’t go into the details. So, what is optional? When declaring variables in Swift, they are designated as non-optional by default. In other words, you have to assign a non-nil value to the variable. If you try to set a nil value to a non-optional, the compiler will say, “hey you can’t set a nil value!”.

1
2
var message : String = "Swift is awesome!" // OK
message = nil // compile-time error

Of course the error message is not so user-friendly but it’s like “Could not find an overload for ‘__conversion’ that accepts the supplied arguments”. The same applies when declaring properties in a class. The properties are designated as non-optional by default.

1
2
3
4
class Messenger {
    var message1 : String = "Swift is awesome!" // OK
    var message2 : String // compile-time error
}

You’ll get a compile-time error for message2 as it’s not assigned with an initial value. For those coming from Objective-C, you may be a bit surprised. In Objective-C, you won’t get any compile-time error when assigning nil to a variable or declaring a property without initial value:

1
2
NSString *message = @ "Objective-C will never die!";
message = nil;
1
2
3
4
class Messenger {
    NSString *message1 = @ "Objective will never die!";
    NSString *message2;
}

However, it doesn’t mean you can’t declare a property without assigning an initial value in Swift. Swift introduces optional type to indicate the absence of a value. It is defined by adding a question mark ? operator after the type declaration. Here is an example:

1
2
3
4
class Messenger {
    var message1 : String = "Swift is awesome!" // OK
    var message2 : String ? // OK
}

You can still assign a value when the variable is defined as optional. However if the variable is not assigned with any value like the below code, its value automatically defaults to nil.

Why Optionals?

Swift is designed for safety. As Apple mentioned, optionals are an example of the fact that Swift is a type safe language. As you can see from the above examples, Swift’s optionals provide compile-time check that would prevent some common programming errors happened at run-time. Let’s go through the below example and you’ll have a better understanding of the power of optionals.

Consider the following method in Objective-C:

1
2
3
4
5
6
7
8
9
- ( NSString * )findStockCode : ( NSString * )company {
    if ( [company isEqualToString : @ "Apple" ] ) {
        return @ "AAPL";
    } else if ( [company isEqualToString : @ "Google" ] ) {
        return @ "GOOG";
    }
   
    return nil;
}

You can use the findStockCode: method to get the stock code for a particular listed company. For demo purpose, the method only returns you the stock code of Apple and Google. For other inputs, it returns nil.

Assuming the method is defined within the same class and we use it like this:

1
2
3
4
NSString *stockCode = [self findStockCode : @ "Facebook" ]; // nil is returned
NSString *text = @ "Stock Code - ";
NSString *message = [text stringByAppendingString :stockCode ]; // runtime error
NSLog ( @ "%@", message );

The code can compile properly but as the method returns nil for Facebook, a runtime exception is thrown when running the app.

With Swift’s optionals, instead of discovering error at the runtime, it reveals the error at compile time. If we rewrite the above example in Swift, it’ll look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func findStockCode (company : String ) -> String ? {
    if (company == "Apple" ) {
      return "AAPL"
    } else if (company == "Google" ) {
      return "GOOG"
    }

    return nil
}

var stockCode :String ? = findStockCode ( "Facebook" )
let text = "Stock Code - "
let message = text + stockCode   // compile-time error
println (message )

The stockCode is defined as optional. That means it can either contain a string or nil. You can’t execute the above code as the compiler detects the potential error (“value of optional type String? is not unwrapped) and tells you to correct it.

As you can see from the example, Swift’s optionals reinforce the nil-check and offer compile-time cues to developers. Obviously, the use of optionals contributes to better code quality.

Unwrapping Optionals

So how can we make the code work? Apparently, we need to test if the stockCode contains a nil value or not. We modify it as follows:

1
2
3
4
5
6
var stockCode :String ? = findStockCode ( "Facebook" )
let text = "Stock Code - "
if stockCode {
    let message = text + stockCode !
    println (message )
}

Just like the Objective-C counterpart, we used if to see if the optional contains a value. Once we know the optional must contain a value, we unwrap it by placing an exclamation mark (!) to the end of the optional’s name. In Swift this is known as forced unwrapping. You use the ! operator to unwrap an optional and reveal the underlying value.

Referring to the above example, we only unwrap the “stockCode” optional after the nil-check. We know the optional must contain a non-nil value before unwrapping it using the ! operator. It is always recommended to ensure that an optional contains a value before unwrapping it.

But what if we forget the verification like below?

1
2
3
var stockCode :String ? = findStockCode ( "Facebook" )
let text = "Stock Code - "
let message = text + stockCode !   // runtime error

There will be no compile-time error. The compiler assumes that the optional contains a value as forced unwrapping is used. When you run the app, a runtime error is thrown with the following message:

fatal error: Can’t unwrap Optional.None

Optional Binding

Other than forced unwrapping, optional binding is a simpler and recommended way to unwrap an optional. You use optional binding to check if the optional contains a value or not. If it does contain a value, unwrap it and put it into a temporary constant or variable.

There is no better way to explain optional binding than using an example. We convert the sample code in the previous example into optional binding:

1
2
3
4
5
6
var stockCode :String ? = findStockCode ( "Facebook" )
let text = "Stock Code - "
if let tempStockCode = stockCode {
    let message = text + tempStockCode
    println (message )
}

The “if let” (or “if var”) are the two keywords of optional binding. In plain English, the code says “If stockCode contains a value, unwrap it, set its value to tempStockCode and execute the conditional block. Otherwise, just skip it the block”. As the tempStockCode is a new constant, you no longer need to use the ! suffix to access its value.

You can further simplify the code by evaluating the function in the if statement:

1
2
3
4
5
let text = "Stock Code - "
if var stockCode = findStockCode ( "Apple" ) {
    let message = text + stockCode
    println (message )
}

Here the stockCode is not an optional, there is no need to use the ! suffix to access its value in the conditional block. If nil value is returned from the function, the block will not be executed.

Optional Chaining

Before explaining optional chaining, let’s tweak the original example a bit. We create a new class named Stock with the code and price properties, which are optionals. The findStockCode function is modified to return Stock class instead of String.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Stock {
    var code : String ?
    var price : Double ?
}

func findStockCode (company : String ) -> Stock ? {
    if (company == "Apple" ) {
        let aapl : Stock = Stock ( )
        aapl. code = "AAPL"
        aapl. price = 90.32

        return aapl
           
    } else if (company == "Google" ) {
        let goog : Stock = Stock ( )
        goog. code = "GOOG"
        goog. price = 556.36
           
        return goog
    }
       
    return nil
}

We rewrite the original example as below. We first find the stock code/symbol by calling the findStockCode function. And then we calculate the total cost needed when buying 100 shares of the stock.

1
2
3
4
5
6
if let stock = findStockCode ( "Apple" ) {
    if let sharePrice = stock. price {
        let totalCost = sharePrice * 100
        println (totalCost )
    }
}

As the return value of findStockCode() is an optional, we use optional binding to check if it contains an actual value. Apparently, the price property of the Stock class is an optional. Again we use the “if let” statement to test if stock.price contains a non-nil value.

The above code works without any error. Instead of writing nested “if set”, you can simplify the code by using Optional Chaining. The feature allows us to chain multiple optionals together with the “?.” operator. Here is the simplified version of the code:

1
2
3
4
if let sharePrice = findStockCode ( "Apple" ) ?. price {
    let totalCost = sharePrice * 100
    println (totalCost )
}

Optional chaining provides an alternative way to access the value of price. The code now looks a lot cleaner and simpler. Here I just cover the basics of optional chaining. You can find further information about optional chaining in Apple’s Swift guide.

Swift and Objective-C Interoperability

Swift’s optionals are very powerful, though it may take you a bit of time to get used to the syntax. Optionals help you to be clear about the values your code can work with and avoid missing the nil-check.

Swift is designed to interact with Objective-C APIs. Whenever you need to interact with UIKit or other framework APIs, you will definitely come across optionals. Here are some optionals you’ve come across when implementing a table view:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    func numberOfSectionsInTableView (tableView : UITableView? ) -> Int {
        // Return the number of sections.
        return 1
    }

    func tableView (tableView : UITableView?, numberOfRowsInSection section : Int ) -> Int {
        // Return the number of rows in the section.
        return recipes.count
    }
   

    func tableView (tableView : UITableView !, cellForRowAtIndexPath indexPath : NSIndexPath ! ) -> UITableViewCell ! {
        let cell = tableView.dequeueReusableCellWithIdentifier ( "Cell", forIndexPath : indexPath ) as UITableViewCell
       
        cell.textLabel.text = recipes [indexPath.row ]
       
        return cell
    }

Summary

It’s crucial for you to understand how optionals work and this is why we devote a full article to optionals. Optionals in Swift allow developer to discover potential issues at compile-time, thus prevent unexpected errors at runtime. Once you get used to the syntax, you would appreciate the beauty of optionals.

As always, we love to hear your feedback. If you have any questions about optionals or want to share your thought, feel free to leave us comment.

We’re going to launch a new book on Swift and iOS 8 programming. It’s a beginner guide for anyone who want to learn Swift, Xcode 6 and master the new APIs of iOS 8 SDK. The book will launch this fall. To receive an early access of the book, please sign up here.
Source : appcoda[dot]com

Không có nhận xét nào:

Đăng nhận xét