Data Types and Type Safety - FUNDAMENTALS - Understanding Swift Programming: Swift 2 (2015)

Understanding Swift Programming: Swift 2 (2015)

PART 1: FUNDAMENTALS

1. Data Types and Type Safety

Everything referenced by a statement in a computer language that has a representation in memory has a data type. A data type, or just type, tells the runtime system how to interpret what would otherwise just be a sequence of unrecognizable bits.

For example, if we know that a variable has a name of n and a type specified as Int, the system can look it up and find that this is an integer that is stored in memory at a certain address and that has a certain number of bits formatted in a certain way.

One of the goals of Swift is to be safe, meaning that it helps programmers avoid errors. And a big source of errors is getting the wrong type and thus misunderstanding the sequence of bits involved. Swift avoids this by being very rigid about types, more so than Objective-C, and much more so than most scripting languages. Swift is thus said to be highly type safe.

Swift avoids some of the problems of languages like JavaScript by not allowing a variable's type to be changed once it has been set, and, particularly, not allowing "implicit conversion"—the automatic conversion of one type to another.

In Swift, if you have a string "5", and you want to convert it to an integer so you can do some arithmetic on it, you have to manually choose to do so. The compiler will not do it for you. This avoids ambiguities like:

a = "5" + 7

Should the variable a above be a string of "57" or an integer of 12?

In Swift, the line above results in a compiler error, and the programmer has to specifically decide what to do.

To some extent this rigidity of typing and other aspects of code safety in Swift reflects a certain philosophy: Crashing is good! It is better to catch errors than not, and it's better to catch them earlier in the development process rather than later. It is thus better to catch errors at compile time than runtime. It is also better to catch them when the system is set up to crash whenever something suspicious occurs rather than let errors to slowly cause deterioration of the data in the app so that it exhibits strange but subtle behavior that you cannot track down.

Swift is also safer in another respect in that it does not keep track of objects by defining pointers that reference a particular location in memory, as Objective-C does. Languages that use such pointers are prone to error because a small error that changes the value of a pointer can result in accidentally overwriting a substantial amount of memory, and doing so in a way that can be difficult to detect.

Variables

Fundamental to all mainstream programming languages is the idea of a variable. A variable associates a name with an address in the computer's memory and a sequence of bits that is stored there.

Before a variable can be used in Swift, it must be declared. This tells the compiler that a variable is being defined and that this is its name and data type.

The following statement declares n to be the name of a variable, using the keyword var, and assigns it the value 78:

var n = 78

This says to the compiler "take the number 78 and store it in the computer's memory in the location associated with the variable name n".

Variables, as suggested in the earlier discussion, have a data type.

One of the very nice things that the Swift compiler does is type inference. That is, when it sees a statement like the one above, setting 78 to the variable n, it infers that the variable n must be an integer and defines its type as Int, the Swift default integer type, and one of the fundamental types in the language.

Note that this is not like implicit conversion, in part because there is no uncertainty about it. Type inference removes most of the drudgery associated with dealing with rigid types and allows a high degree of type safety with relatively little effort.

The programmer can also, when desired, explicitly declare the type of a variable with a type annotation:

var n: Int

This says that the variable n has a type of Int. It consists of a colon right after the variable name (there can't be a space between the variable name and the colon) and the name of a type. A space after the colon is optional, but is the usual practice.

We can declare a variable without setting a value to it, but if we do not include a value, we have to include a type annotation with it, so the compiler knows the type. If we don't set a value, the compiler doesn't have any way to infer the type.

The following will result in a compiler error:

var n // Compiler error

Multiple variables can be declared and assigned as part of the same statement (String and Float are more fundamental types):

var n: Int, q: String, r: Float

Constants

Constants in Swift are handled very much like variables. Apple defines a constant as a variable the value of which can be set only once.

A constant is indicated by the keyword let:

let p = 78

In this situation the same thing happens as if p were a variable. The constant p is declared, and its value 78 for it is stored in memory, along with the type that has been inferred. The only difference is that the value cannot be changed.

The type of a constant can also be set explicitly:

let p: Int = 78

And if a value is not provided the type must be set explicitly:

let p: Int // OK

let p // Compiler error

An attempt to set a value to a constant a second time will result in a compiler error:

let p = 78

p = 89 // Compiler error

The practice recommended by Apple is to use constants, rather than variables, for any values that do not change. This practice increases safety, in that it prevents the error of changing a value that should not change. Most programmers are surprised when they start following this practice and then realize that the code they end up writing has far more constants in it than variables.

"Converting" a Type In Swift

Often you will want to do an operation on variables of different types. For example, consider the following:

var a = 5

var b = 3.2

var c = a + b // Compiler error

If you try to add together an integer and a floating point number that are both stored in variables, you will get a compiler error. (If you try this in Objective-C, it will work fine.)

However, just to avoid confusion here about what works and what does not, note that if the numbers are literals, the compiler will allow you to add them together:

var d = 5 + 3.2 // Works OK

This is allowed because the compiler is smart enough, when it assigns a type to the literal 5, to assign it a type of Double, only because it is in the context of being added to a floating point number. However, in the earlier case, the types have already been assigned.

What can we do if we have variables with mixed types that we want to add together? Once a variable is given a type (either explicitly or by type inference), that type cannot be changed. However, there are two things that can be done short of this.

First, you can create a new instance of a desired type by providing a value in another type to an initializer of the desired type.

Thus, if you have a value that is an integer but you need it as a floating point number, you can do the following:

var x = Double(5)

This declares a variable x of type Double and assigns it a value of 5.0.

You can also do:

var j = 5

var k = Double(j)

which does the same thing.

Returning to the example where we got the compiler error:

var a = 5

var b = 3.2

var c = Double(a) + b // Works OK

The Double(a) created a new value of type Double and value 5.0, and it could then be added to the b value of 3.2.

A second thing that can be done short of changing a type is known as type casting. This is normally allowed only for instances of classes that are part of an inheritance hierarchy. What is usually done is to downcast the definition of a type of a variable from one higher up in the inheritance hierarchy to one lower in the hierarchy. This is not actually changing the type but only allowing it to be used temporarily as the type that is lower in the hierarchy. Although the general rule is that if a variable is declared to be a particular type, you can only assign values of that type to it, the rules of polymorphism mean that you can also assign values that are instances of subclasses. This issue is covered more thoroughly in Chapter 30 on "Type Checking and Type Casting", and is also discussed in Chapter 11 on “Object-Oriented Programming” in the section on subtyping.

Types in Swift

Aside from the desire to be rigid about types for the purpose of safety, the way that types are defined also says a lot about the architecture of the Swift language. The language consists of a relatively small number of fundamental types, plus a few types that have the capability of allowing a programmer to create a new custom type in the language. In the remainder of this chapter, I will provide a brief description of all of the types that have been defined in Swift. This will help you get a sense of the language.

Fundamental Types in Swift

The fundamental types are as follows:

INTEGERS

The basic data type for an integer presented earlier was Int. The actual size of the integer as represented in memory, in terms of bits, depends upon the machine. A 32-bit machine will use 32 bits to represent an integer declared as an Int. A 64-bit machine will use 64 bits to represent an integer declared as an Int.

In addition, it is possible to declare specialized integer types that are unsigned or that have a specific size, or both.

Thus UInt declares a variable or constant to be unsigned, meaning that it is neither positive nor negative. (An unsigned integer can hold a larger positive value than a signed integer of the same size.) As in the case of Int, a 32-bit machine will represent a UInt with 32 bits, while a 64-bit machine will represent a UInt with 64 bits.

The types Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, and UInt64 all represent valid integer types with different combinations of signed vs unsigned and size.

The usual practice is to just use Int for all integer types. The other types may be useful if it is desired to store a lot of data in integer form in a particularly efficient way.

FLOATING POINT NUMBERS

There are two types for floating point numbers, Double or Float, but Double is the default and, realistically, the only one you need to be concerned with unless you are dealing with some kind of legacy code or legacy APIs that need Float types, or need to conserve space. A standard 64-bit processor, which most devices now use, will represent a Double level of precision.

The statement:

var p = 5.7

results in the variable p having a type of Double. If you want the type to be Float, you have to declare this explicitly:

var p: Float = 5.7

BOOLEANS

In Swift, there is only one kind of Boolean type, Bool, and it must have one of only two values, true and false.

Other Types in Swift

In addition to the fundamental types, there are other types that are often composed of the fundamental types.

STRINGS

A string is a sequence of one or more characters, which may be Unicode characters that represent single letters, logographic characters, or emoji:

var a = "5"

var b = "Good afternoon"

var c = "的" // Chinese

var d = "" // Emoji

CHARACTERS

In Swift, a literal single character is represented by being enclosed in double quotes and, in addition, being explicitly defined as having a type of Character:

var ch: Character = "a"

ARRAYS

An array is an ordered sequence of values. The values may be of any type but all of the values in an array must be of the same type. The first line of the example shown below is an array containing floating point numbers that have a type of Double. The second line shows the same thing but with the type explicitly defined.

var temperature = [67.8, 34.2, 76.3]

var temperature: [Double] = [67.8, 34.2, 76.3]

SETS

A set is an unordered group of values for which any value is only represented once. The values may be of any type but all of the values in a set must be of the same type. The first line of the example shown below is a set containing strings that have a type of String. The second line shows the same thing but with the type explicitly defined. (Sets are new in Swift 1.2.)

var airportCodes = Set(["SFO", "OAK", "LAX"])

var airportCodes: Set<String> = Set(["SFO", "OAK", "LAX"])

DICTIONARIES

A dictionary is an unordered group of key-value pairs. The keys and values may be of any type but all of the keys must be of the same type and all of the values must be of the same type. The first line of the example shown below is a dictionary containing a key-value pair that has that have a type of String for the key and Int for the value. The second line shows the same thing but with the type explicitly defined.

var personsAndTheirAge = ["Tom": 12, "Peter": 22, "Beth": 74]

var personsAndTheirAge: [String: Int] = ["Tom": 12, "Peter": 22, "Beth": 74]

TUPLES

A tuple is a simple way to pass or store a group of related values. These typically have different types and can't be put in an array because the values in an array must all be of the same type. The first line of the example shown below is a tuple that has an Int for the first position in the tuple and a String for the second position. The second line shows the same thing but with the type explicitly defined.

var errorMessageTuple = (432, "Error: Something Bad Happened")

var errorMessageTuple: (Int, String) = (432, "Error: Something Bad Happened")

Type Declarations for Functions, Methods, and Closures

Functions, methods, and closures might normally not be seen as things that have types, but they actually do. Their types, known as compound types because they consist of multiple fundamental types, consist of the types of their input parameters and return parameter.

FUNCTIONS AND METHODS

Functions and methods have types that are defined by the types of the parameters passed to them and their return value. Thus:

func addTwoNumbers (a: Int, b: Int) -> Int {

var c = a + b

return c

}

This function has a type of (Int, Int) -> Int. This might be expressed in English as "A function with two input parameters, each with an integer type, and a return type of integer". (A method in Swift is just a function that is associated with a class or similar type.)

CLOSURE EXPRESSIONS

A closure expression, like a function, has a type that is defined by the types of the parameters passed to them and their return value. Closures have a very flexible syntax and may or may not have input parameters and/or return values.

A simple form of a closure is shown in the first line of the example, while a complex closure is shown beginning in the second line. The comment next to each indicates the type of the closure.

{ print("hey") } // Simple syntax

// Type is ()-> Null

{ (a: Int, b: Int) -> Int in

var c = a + b

return c

} // Full syntax

// Type is (Int, Int) -> Int

Optional Types

Everything in Swift that has a type and can contain a value can alternatively be defined as having an optional version of that type. Thus, the following code declares n to be a variable with a type that might be described in English as an "optional integer":

var n: Int? = 5

An optional version of a type is represented by a type name followed by a question mark. Ordinary, non optional types require that a value be stored in them; that is, they cannot be nil. An attempt to store a nil in them will result in a runtime error. A variable that is declared as an optional, however, is allowed to be nil.

The causing of a runtime error when a nil is stored in a non optional variable is a first line of defense in ensuring that variables have values. There is also a second line of defense. A variable declared as an optional that contains a value must be unwrapped to get to that value. An attempt to use a value from a variable declared as an optional that has not been unwrapped will normally cause a runtime error. An attempt to unwrap a value from a variable declared as an optional when that value is nil will also normally cause a runtime error. Swift has a number of capabilities in the language for dealing with optionals depending upon the particular situation. See Chapter 9 on "Optional Values".

Creating Custom Types

The following types, including classes, structures, and enumerations, are used for creating new, custom types. The data types discussed above that were referred to as "fundamental" types are not actually fundamental types hard coded in the language. They were created as custom types of structures.

CLASSES

A new custom type named Banana can be defined with the following code:

class Banana {

}

STRUCTURES

A new custom type named Kumquat can be defined with the following code:

struct Kumquat {

}

ENUMERATIONS

A new custom type named DayOfTheWeek can be defined with the following code. This defines the only allowed values for the new type. The type DayOfTheWeek has seven allowed values.

enum DayOfTheWeek {

case Monday

case Tuesday

case Wednesday

case Thursday

case Friday

case Saturday

case Sunday

}

Aliases for Types

A type can be referred to by another name if that name is defined with the typealias keyword. For example, the statement:

typealias Byte = UInt8

will allow you to refer to an eight bit unsigned integer by using the alias Byte.

Hands-On Exercises

Go to the following web address with a Macintosh or Windows PC to do the Hands-On Exercises.

For Chapter 1 exercises, go to

understandingswiftprogramming.com/1

Do NOT go to the site with a tablet or phone—typing in code with a mobile keyboard is a horrible experience, and the web site will not work with mobile devices, in order to not waste your time.