Mastering Swift 3
上QQ阅读APP看书,第一时间看更新

Constants and variables

Constants and variables associate an identifier (such as myName or currentTemperature) with a value of a particular type (such as String or Int), where the identifier can be used to retrieve the value. The difference between a constant and a variable is that a variable can be updated or changed, while a constant cannot be changed once a value is assigned to it.

Constants are good for defining the values that you know will never change, such as the temperature that water freezes at or the speed of light. Constants are also good for defining a value that we use many times throughout our application, such as a standard font size or the maximum number of characters in a buffer. There will be numerous examples of constants throughout this book, and it is recommended that we use constants rather than variables whenever possible.

Variables tend to be more common in software development than constants. This is mainly because developers tend to prefer variables to constants. In Swift, we receive a warning if we declare a variable that is never changed. We can make useful applications without using constants (although it is a good practice to use them); however, it is almost impossible to create a useful application without variables.

Note

The use of constants is encouraged in Swift. If we do not expect or want the value to change, we should declare it as a constant. This adds a very important safety constraint to our code that ensures that the value never changes.

You can use almost any character in the identifier of a variable or constant (even Unicode characters); however, there are a few rules that you must follow:

  • An identifier must not contain any whitespace
  • It must not contain any mathematical symbols
  • It must not contain any arrows
  • An identifier must not contain private use or invalid Unicode characters
  • It must not contain line- or box-drawing characters
  • Also, it must not start with a number, but they can contain numbers
  • If you use a Swift keyword as an identifier, surround it with back ticks

Note

Keywords are words that are used by the Swift programming language. Some examples of keywords that you will see in this chapter are var and let. You should avoid using Swift keywords as identifiers to avoid confusion when reading your code.

Defining constants and variables

Constants and variables must be defined prior to using them. To define a constant, you use the  let keyword and to define a variable, you use the var keyword. The following code shows how to define both constants and variables:

// Constants 
let freezingTemperatureOfWaterCelsius = 0 
let speedOfLightKmSec = 300000 
 
// Variables 
var currentTemperature = 22 
var currentSpeed = 55 

We can declare multiple constants or variables in a single line by separating them with a comma. For example, we could shrink the preceding four lines of code down to two lines, as shown here:

// Constants 
let freezingTempertureOfWaterCelsius = 0,  
speedOfLightKmSec = 300000 
 
// Variables 
var currentTemperture = 22, currentSpeed = 55 

We can change the value of a variable to another value of a compatible type; however, as we noted earlier, we cannot change the value of a constant. Let's look at the following Playground. Can you tell what is wrong with the code from the error message that is shown?

Did you figure out what was wrong with the code? Any physicist can tell you that we cannot change the speed of light, and in our code, the speedOfLightKmSec variable is a constant, so we cannot change it here either. When we attempt to change the speedOfLightKmSec constant, an error is reported. We are able to change the value of the highTemperture variable without an error because it is a variable. We mentioned the difference between variables and constants a couple of times because it is a very important concept to grasp, especially when we define mutable and immutable collection types later in Chapter 3, Using Swift Collections and the Tuple Type.

Type safety

Swift is a type-safe language. In a type-safe language, we are required to be clear on the types of values we store in a variable. We will get an error if we attempt to assign a value to a variable that is of the wrong type. The following Playground shows what happens if we attempt to put a string value into a variable that expects integer values; note that we will go over the most popular types a little later in the chapter:

Swift performs a type check when it compiles code; therefore, it will flag any mis-matched types with an error. The error message in this Playground explains pretty clearly that we are trying to insert a string literal into an integer variable.

So the question is: how does Swift know that integerVar is of the Int type? Swift uses type inference to figure out the appropriate type. Let's take a look at what type inference is.

Type inference

Type inference allows us to omit the variable type when we define it. The compiler will infer the type, based on the initial value. For example, in Objective-C, we would define an integer like this:

int myInt = 1 

This tells the compiler that the myInt variable is of the int type, and the initial value is the number 1. In Swift, we would define the same integer like this:

var myInt = 1 

Swift infers that the variable type is an integer because the initial value is an integer. Let's take a look at a couple of more examples:

var x = 3.14      // Double type 
var y = "Hello"   // String type 
var z = true      // Boolean type 

In the preceding example, the compiler will correctly infer that variable x is Double, variable y is String, and variable z is Boolean, based on the initial values.

Explicit types

Type inference is a very nice feature in Swift and is one that you will probably get used to very quickly; however, there are times when we would like to explicitly define a variable's type. For example, in the preceding example, the variable x is inferred to be Double, but what if we wanted the variable type to be Float? We can explicitly define a variable type like this:

var x : Float = 3.14 

Notice the Float declaration (the colon and the word Float) after the variable identifier. This tells the compiler to define this variable to be of the Float type and gives it an initial value of 3.14. When we define a variable in this manner, we need to make sure that the initial value is of the same type as we are defining the variable to be. If we try to give a variable an initial value that is of a different type than we are defining it as, we will receive an error.

We will need to explicitly define the variable type if we were not setting an initial value. For example, the following line of code is invalid because the compiler does not know what type to set the variable x to:

var x

If we use this code in our application, we will receive a Type annotation missing in pattern error. If we are not setting an initial value for a variable, we are required to define the type like this:

var x: Int 

Now that we have seen how to explicitly define a variable type, let's take a look at some of the most commonly used types.

Numeric types

Swift contains many of the standard numeric types that are suitable for storing various integer and floating-point values.

Integers

An integer is a whole number and can be either signed (positive, negative, or zero) or unsigned (positive or zero). Swift provides several integer types of different sizes. The following chart shows the value ranges for the different integer types on a 64-bit system:

Tip

Unless there is a specific reason to define the size of an integer, I would recommend using the standard Int or UInt type. This will save you from needing to convert between different types of integers.

In Swift, Int (as well as other numerical types) are actually named types, implemented in the Swift standard library using structures. This gives us a consistent mechanism for memory management of all the data types as well as properties that we can access. For the preceding chart, I retrieved the minimum and maximum values of each integer type using the min and max properties. Take a look at the following Playground to see how I retrieved the values:

Integers can also be represented as binary, octal, and hexadecimal numbers. We just need to add a prefix to the number to tell the compiler which base the number should be in. The following chart shows the prefix for each numerical base:

The following Playground shows how the number 95 is represented in each of the numerical bases:

Swift also allows us to insert arbitrary underscores in our numeric literals. This can improve the readability of our code. As an example, if we were defining the speed of light, which is constant, we can define it like this:

let speedOfLightKmSec = 300_000 

Swift will ignore these underscores; therefore, they do not affect the value of the numeric literals in any way.

Floating-point

A floating-point number is a number with a decimal component. There are two standard floating-point types in Swift: Float and Double. The Float type represents a 32-bit floating-point number, while the Double type represents a 64-bit floating-point number. Swift also supports an extended floating-point type Float80. The Float80 type is an 80-bit floating-point number.

It is recommended that we use the Double type over the Float type unless there is a specific reason to use the latter. The Double type has a precision of at least 15 decimal digits, while the Float type can be as little as six decimal digits. Let's look at an example of how this can effect our application without us knowing it. The following screenshot shows the results if we add two decimal numbers together using both a Float type and a Double type:

As we can see from the screenshot, the two decimal numbers that we are adding contain nine digits past the decimal point; however, the results in the Float type only contains seven digits, while the results in the Double type contains the full nine digits.

The loss of precision can cause issues if we are working with currency or other numbers that need accurate calculations. The floating-point accuracy problem is not an issue confined to Swift; all the languages that implement the IEEE 754 floating-point standard have similar issues. The best practice is to use Double types for floating-point numbers unless there is a specific reason not to.

What if we have two variables, one an Int and the other a Double? Do you think we can add them as the following code depicts?

var a : Int = 3 
var b : Double = 0.14 
var c = a + b 

If we put the preceding code into a Playground, we would receive the following error: operator '+' cannot be applied to operands of type Int and Double

This error lets us know that we are trying to add two different types of numbers, which is not allowed. To add an Int and a Double together, we need to convert the Int value into a Double value. The following code shows how to this:

var a : Int = 3 
var b : Double = 0.14 
var c = Double(a) + b 

Notice how we use the Double() function to convert the Int value to a Double value. All of the numeric types in Swift have a conversion convenience initializer, similar to the Double() function shown in the preceding code sample. For example, the following code shows how you can convert an Int variable to Float and UInt16 variables:

var intVar = 32 
var floatVar = Float(intVar) 
var uint16Var = UInt16(intVar) 

The Boolean type

Boolean values are often referred to as logical values because they can be either true or false. Swift has a built-in Boolean type that accepts one of the two built-in Boolean constants: true and false.

Boolean constants and variables can be defined like this:

let swiftIsCool = true 
let swiftIsHard = false 
 
var itIsWarm = false 
var itIsRaining = false 

Boolean values are especially useful when working with conditional statements, such as if and while. For example, what do you think this code would do?

let isSwiftCool = true 
let isItRaining = false 
if (isSwiftCool) { 
    print("YEA, I cannot wait to learn it") 
} 
if (isItRaining) { 
    print("Get a rain coat") 
} 

If you answered that this code would print out YEA, I cannot wait to learn it, then you would be correct. Since isSwiftCool is set to true, the YEA, I cannot wait to learn it message is printed out, but isItRaining is false; therefore, the Get a rain coat message is not printed.

The string type

A string is an ordered collection of characters, such as Hello or Swift. In Swift, the string type represents a string. We have seen several examples of strings already in this book, so the following code should look familiar. This code shows how to define two strings:

var stringOne = "Hello" 
var stringTwo = " World" 

Since a string is an ordered collection of characters, we can iterate through each character of a string. The following code shows how to do this:

var stringOne = "Hello" 
for char in stringOne.characters { 
    print(char) 
} 

The preceding code will display the results shown in the following screenshot:

There are two ways to add one string to another string. We can concatenate them or include them inline. To concatenate two strings, we use the + or += operator. The following code shows how to concatenate two strings. The first example appends stringB to the end of stringA and the results are put into a new stringC variable. The second example appends stringB directly to the end of stringA without creating a new string:

var stringC = stringA + stringB 
stringA += stringB 

To include a string inline, with another string, we use a special sequence of characters \( ). The following code shows how to include a string inline with another string:

var stringA = "Jon" 
var stringB = "Hello \(stringA)" 

In the previous example, stringB will contain the message Hello Jon, because Swift will replace the \(stringA) sequence of characters with the value of stringA.

In Swift, we define the mutability of variables and collections by using the var and let keywords. If we define a string as a variable using var, the string is mutable, meaning that we can change and edit the value of the string. If we define a string as a constant using let, the string is immutable, meaning that we cannot change or edit the value once it is set. The following code shows the difference between a mutable and an immutable string:

var x = "Hello" 
let y = "HI" 
var z = " World" 
 
//This is valid, x is mutable 
x += z 
 
//This is invalid, y is not mutable. 
y += z 

Strings in Swift have two methods that can convert the case of the string. These methods are lowercased() and uppercased(). The following example demonstrates these methods:

var stringOne = "hElLo" 
print("Lowercase String:  " + stringOne.lowercased()) 
print("Uppercase String:  " + stringOne.uppercased()) 

If we run this code, the results will be as follows:

Lowercase String:  hello 
Uppercase String:  HELLO 

Swift provides four ways to compare a string; these are string equality, prefix equality, suffix equality, and isEmpty. The following example demonstrates these ways:

var stringOne = "Hello Swift" 
var stringTwo = "" 
stringOne.isEmpty  //false 
stringTwo.isEmpty  //true 
stringOne == "hello swift"  //false 
stringOne == "Hello Swift"  //true 
stringOne.hasPrefix("Hello")  //true 
stringOne.hasSuffix("Hello")  //false 

We can replace all the occurrences of a target string with another string. This is done with the stringByReplacingOccurrencesOfString()method. The following code demonstrates this:

var stringOne = "one,to,three,four" 
print(stringOne.replacingOccurrences(of: "to", with: "two")) 

The preceding example will print one,two,three,four to the screen because we are replacing all the occurrences of to with two.

We can also retrieve substrings and individual characters from our strings. The following example shows various ways to do this:

var stringOne = "one,to,three,four" 
print(stringOne.replacingOccurrences(of: "to", with: "two")) 
 
var path = "/one/two/three/four" 
//Create start and end indexes 
let startIndex = path.index(path.startIndex, offsetBy: 4) 
let endIndex = path.index(path.startIndex, offsetBy: 14) 
 
let myRange = startIndex..<endIndex 
 
path.substring(with: myRange)   //returns the String /two/three 
 
 
path.substring(to:startIndex)  //returns the String /one 
path.substring(from:endIndex)  //returns the String /four 
 
path.characters.last 
path.characters.first 

In the preceding example, we use the substring(with:) function to retrieve the substring between a start and end index. The indexes are created with the index(_: offsetBy:) function. The first property in the index(_: offsetBy:) function gives the index of where we wish to start, and the offsetBy property tells us how much to increase the index by.

The substring(to:) function creates a substring from the beginning of the string to the index. The substring(from:) function creates a substring from the index to the end of the string. We then used the last property to get the last character of the string and the first property to get the first character.

We can retrieve the number of characters in a string by using the count property. The following example shows how you can use this function:

var path = "/one/two/three/four" 
var length = path.characters.count 

This completes our whirlwind tour of strings. I know we went through these properties and functions very quickly, but we will be using strings extensively throughout this book, so we will have a lot of time to get used to them.

Optional variables

All the variables we have looked at so far, are considered to be non-optional variables. This means that the variables are required to have a non-nil value; however, there are times when we want or need our variables to contain nil values. This can occur if we return a nil from a function whose operation has failed or if a value is not found.

In Swift, an optional variable is a variable that we are able to assign nil (no value) to. Optional variables and constants are defined using ? (question mark). Let's look at the following Playground; it shows us how to define an Optional type and shows what happens if we assign a nil value to a Non-Optional variable:

Notice the error we receive when we try to assign a nil value to the non-optional variable.

Optional variables were added to the Swift language as a safety feature. They provide a compile-time check of our variables to verify that they contain a valid value. Unless our code specifically defines a variable as optional, we can assume that the variable contains a valid value, and we do not have to check for nil values. Since we are able to define a variable prior to initiating it, this could give us a nil value in a non-optional variable; however, the compiler checks for this. The following Playground shows the error that we receive if we attempt to use a non-optional variable prior to initiating it:

To verify that an optional variable or constant contains a valid (non-nil) value, our first thought may be to use the != (not equals to) operator to verify that the variable is not equal to nil, but there are also other ways. These other ways are optional binding and optional chaining. Before we cover optional binding and optional chaining, let's see how to use the != (not equals to) operator and what force unwrapping is.

To use force unwrapping, we must first make sure that the optional has a non-nil value and then we can use the explanation point to access that value. The following example shows how we can do this:

var name: String? 
name = "Jon" 
 
if name != nil { 
    var newString = "Hello " + name! 
} 

In this example, we create an optional variable named name and we assign it a value of Jon. We then use the != operator to verify that the optional is not equal to nil. If it is not equal to nil, we use the explanation point to access its value. While this is a perfectly viable option, it is recommended that we use the optional binding method discussed next instead of force unwrapping.

Optional binding is used to check whether an optional variable or constant has a non-nil value, and, if so, assign that value to a temporary variable. For optional binding, we use the if let or if var keywords together. If we use if let, the temporary value is a constant and cannot be changed, while the if var keyword puts the temporary value into a variable that allows us to change the value. The following code illustrates how optional binding is used:

var myOptional: String? 
if let temp = myOptional { 
    print(temp) 
    print("Can not use temp outside of the if bracket") 
} else { 
    print("myOptional was nil") 
} 

In the preceding example, we use the if let keywords to check whether the myOptional variable is nil. If it is not nil, we assign the value to the temp variable and execute the code between the brackets. If the myOptional variable is nil, we execute the code in the else bracket, which prints out the message, myOptional was nil. One thing to note is that the temp variable is scoped only for the conditional block and cannot be used outside of the conditional block.

It is perfectly acceptable with optional binding to assign the value to a variable of the same name. The following code illustrates this:

if let myOptional = myOptional { 
    print(myOptional) 
    print("Can not use temp outside of the if bracket") 
} else { 
    print("myOptional was nil") 
} 

To illustrate the scope of the temporary variable, let's take a look at the following code:

var myOptional: String? 
 
myOptional = "Jon" 
 
if var myOptional = myOptional { 
    myOptional = "test" 
    print("Inside:  \(myOptional)") 
} 
 
print("Outside: \(myOptional)")

In this example, the first line that is printed to the console is Inside: test because we are within the scope of the if var statement where we assign the value of test to the myOptional variable. The second line that is printed to the console would be Outside: Optional(Jon) because we are outside of the scope of the if var statement where the myOptional variable is set to Jon.

We can also test multiple optional variables in one line. We do this by separating each optional check with a comma. The following example shows how to do this:

if let myOptional = myOptional, myOptional2 = myOptional2, myOptional3 = myOptional3 { 
  // only reach this if all three optionals 
  // have non-nil values 
} 

Optional chaining allows us to call properties, methods, and subscripts on an optional that might be nil. If any of the chained values return nil, the return value will be nil. The following code gives an example of optional chaining using a fictitious car object. In this example, if either car or tires are nil, the variable s will be nil; otherwise, s will be equal to the tireSize property:

var s = car?.tires?.tireSize 

The following Playground illustrates the three ways to verify whether an optional contains a valid value prior to using it:

In the preceding Playground, we begin by defining the optional string variable, stringOne. We then explicitly check for nil by using the != operator. If stringOne is not equal to nil, we print the value of stringOne to the console. If stringOne is nil, we print the Explicit Check: stringOne is nil message to the console. As we can see in the results console, Explicit Check: stringOne is nil is printed to the console because we have not assigned a value to stringOne yet.

We then use optional binding to verify that stringOne is not nil. If stringOne is not nil, the value of stringOne is put into the tmp temporary variable, and we print the value of tmp to the console. If stringOne is nil, we print the Optional Binding: stringOne is nil message to the console. As we can see in the results console, Optional Binding: stringOne is nil is printed to the console because we have not assigned a value to stringOne yet.

We use optional chaining to assign the value of the characters.count property of the stringOne variable to the charCount1 variable if stringOne is not nil. As we can see, the charCount1 variable is nil because we have not assigned a value to stringOne yet.

We then assign a value of http://www.packtpub.com/all to the stringOne variable and rerun all the three tests again. This time, stringOne has a non-nil value; therefore, the value of charCount2 is printed to the console.

Note

It would be tempting to say that I may need to set this variable to nil, so let me define it as optional, but that would be a mistake. The mindset for optionals should be to only use them if there is a specific reason for the variable to have nil value.

We will be discussing optionals further in Chapter 10, Using Optional Types, later in this book.

Enumerations

Enumerations (otherwise known as enums) are a special data type that enable us to group related types together and use them in a type-safe manner. Enumerations in Swift are not tied to integer values as they are in other languages, such as C or Java. In Swift, we are able to define an enumeration with a type (string, character, integer, or floating-point) and then define its actual value (known as the raw value). Enumerations also support features that are traditionally only supported by classes, such as computed properties and instance methods. We will discuss these advanced features in depth in Chapter 5, Classes and Structures. In this section, we will look at the traditional features of enumerations.

We will define an enumeration that contains the list of Planets like this:

enum Planets { 
    case Mercury 
    case Venus 
    case Earth 
    case Mars 
    case Jupiter 
    case Saturn 
    case Uranus 
    case Neptune 
} 

The values defined in an enumeration are considered to be the member values (or simply the members) of the enumeration. In most cases, you will see the member values defined like the preceding example because it is easy to read; however, there is a shorter version. This shorter version lets us define multiple members in a single line, separated by commas, as the following example shows:

enum Planets { 
    case Mercury, Venus, Earth, Mars, Jupiter 
    case Saturn, Uranus, Neptune 
} 

We can then use the Planets enumeration like this:

var planetWeLiveOn = Planets.Earth 
var furthestPlanet = Planets.Neptune 

The type for the planetWeLiveOn and furthestPlanet variables is inferred when we initialize the variable with one of the member values of the Planets enumeration. Once the variable type is inferred, we can then assign a new value without the Planets prefix, as shown here:

planetWeLiveOn = .Mars 

We can compare an enumeration value using the traditional equals (==) operator or use a switch statement. The following example shows how to use the equals operator and the switch statement with an enum:

// Using the traditional == operator 
if planetWeLiveOn == .Earth { 
    print("Earth it is") 
} 
// Using the switch statement 
switch planetWeLiveOn { 
case .Mercury: 
    print("We live on Mercury, it is very hot!") 
case .Venus: 
    print("We live on Venus, it is very hot!") 
case .Earth: 
    print("We live on Earth, just right") 
case .Mars: 
    print("We live on Mars, a little cold") 
default: 
    print("Where do we live?") 
} 

Enumerations can come prepopulated with raw values, which are required to be of the same type. The following example shows how to define an enumeration with string values:

enum Devices: String { 
    case MusicPlayer = "iPod" 
    case Phone = "iPhone" 
    case Tablet = "iPad" 
} 
print("We are using an " + Devices.Tablet.rawValue) 

The preceding example creates an enumeration with three types of devices. We then use the rawValue property to retrieve the raw value for the Tablet member of the Devices enumeration. This example will print a message, saying We are using an iPad.

Let's create another Planets enumeration, but this time, assign numbers to the members, as follows:

enum Planets: Int  { 
    case Mercury = 1 
    case Venus 
    case Earth 
    case Mars 
    case Jupiter 
    case Saturn 
    case Uranus 
    case Neptune 
} 
print("Earth is planet number \(Planets.Earth.rawValue)") 

The big difference between these last two enumerations examples is that in the second example, we only assign a value to the first member (Mercury). If integers are used for the raw values of an enumeration then we do not have to assign a value to each member. If no value is present, the raw values will be auto-incremented.

In Swift, enumeration can also have associated values. Associated values allow us to store additional information along with member values. This additional information can vary each time we use the member. It can also be of any type, and the types can be different for each member. Let's take a look at how we might use associate types by defining a Product enumeration, which contains two types of products:

  enum Product { 
    case Book(cost: Double, year: Int, pages: Int) 
    case Puzzle(cost: Double, pieces: Int) 
  } 
  var masterSwift = Product.Book(cost: 49.99, year: 2016,   
            pages: 310) 
  var worldPuzzle = Product.Puzzle(cost: 9.99, pieces: 200) 
 
Switch masterSwift { 
case .Book(let price, let year, let pages): 
    print("Mastering Swift was published in \(year) for the price of \(price) and has \(pages) pages") 
case .Puzzle(let price, let pieces): 
    print("Master Swift is a puzze with \(pieces) pieces and sells for \(price)") 
} 
 
switch worldPuzzle { 
case .Book(let price, let year, let pages): 
    print("World Puzzle was published in \(year) for the price of \(price) and has \(pages) pages") 
case .Puzzle(let price, let pieces): 
    print("World Puzzle is a puzze with \(pieces) pieces and sells for \(price)") 
} 

In the preceding example, we begin by defining a Product enumeration with two members—Book and Puzzle. The Book member has an associated value of Double, Int, Int, types and the Puzzle member has an associated value of Double, Int types. Notice that we are using named associated types where we assign a name for each associated type. We then create two products masterSwift and worldPuzzle. We assign the masterSwift variable a value of Product.Book with the associated values of 49.99, 2016, and 310. We then assign the worldPuzzle variable a value of Product.Puzzle with the associated values of 9.99, 200.

We can then check the Products enumeration using a switch statement, as we did in an earlier example. We extract the associated values within the switch statement. In this example, we extracted the associated values as constants with the let keyword, but you can also extract the associated values as variables with the var keyword.

If you put the previous code into a Playground, the following results will be displayed:

"Master Swift was published in 2016 for the price of 49.99 and has 310 pages" 
"World Puzzle is a puzzle with 200 and sells for 9.99" 

We have only scratched the surface of what enumerations can do in Swift. In Chapter 5, Classes and Structures we will look at some additional features on enumerations with Swift and see why they are so powerful.