《Swift by Tutorials》学习笔记,第二章

第一章主要学习了Swift的一些基本内容,变量、常量、数据类型以及控制流。而这一章讲的是相对“高级”一点的基础知识,当然也还是基础知识啦。

可选类型(Optionals)

根据之前的知识,当我们需要声明一个变量的时候,我们可以这么做:

var str = "Hello, playground"

这样,我们在声明的时候同时指定了初值,但是,如果在有些情况下,你声明变量的时候是不知道初值的,那么应该怎么办呢?从直观上来讲,我们会这么做:

// 声明一个字符串类型的变量,但没有赋初值
var str: String

实际上,如果我们这么做,编译器会报出 Variable 'str' used before being initialized 的错误, 在Swift当中,不允许声明一个变量而不赋值,这能防止我们在没有初始化的情况下使用

变量。

那么,如果这样呢?

var str: String = nil

结果,这次报出的错误是 Type ‘string’ does not conform to protocol ‘NilLiteralConvertible’str是一个字符串类型的变量,而nil不是一个字符串,所以我们不能进行赋值。

事实上,在Swift当中,如果一个变量的值有可能为空,则我们需要指定它的类型为可选类型。

可选类型的声明

当我们需要声明一个可选类型的变量,我们可以这么做:

var str: String?

只需在变量类型的后面加一个问号就可以了。在没有赋初值的情况下,str会自动初始化为nil

如果我们把声明改成如下:

var str: String? = "Hello Swift by Tutorials"

可以在右边的面板中看到输出结果为 {Some "Hello Swift by Tutorials!"},这说明str是一个可选类型,而非真正的字符串。

在这种情况下,如果我们要使用str,则必须先对其进行解包,可以这么做:

if let unwrappedStr = str {
    println("Unwrapped! \(unwrappedStr.uppercaseString)")
} else {
    println("Was nil")
}

当str的值不为nil时,则会将str解包,将直接赋值到unwrappedStr,然后执行第一个语句,否则会执行else里面的语句。

强制解包(Forced unwrapping)

当我们确定一个可选变量的值不为nil时,我们可以使用强制解包来获取变量内的值,而不必使用if语句来进行判断。要使用强制解包,只需要在变量的后面加一个感叹号:

var str: String? = "Hello Swift by Tutorials!"
println("Force unwrapped! \(str!.uppercaseString")

如果对一个值为nil的变量进行强制解包,会造成一个运行时错误。

虽然强制解包很方便,但是它破坏了可选类型的安全性,只有当我们十分确定一个变量不是为nil时才能对它进行强制解包。

隐式解包(Implict unwrapping)

隐式解包功能可以让我们使用可选类型的时候不需要手动进行解包。要使用一个隐式解包的变量,我们需要在声明变量的时候不是使用问号,而是使用感叹号

// 声明一个隐式解包的变量
var str: String! = "Hello Swift by Tutorials"

之后,我们可以像使用普通变量一样来使用str

str = str.lowercaseString
println(str)

虽然,我们使用隐式解包变量的语法跟普通变量是一样的,但是实际是上编译器自己帮我们进行了解包,如果直接使用了一个值为nil的变量,还是会造成一个运行时的错误,所以在使用隐式解包变量的时候一定要注意。

可选链(Optional chaining)

使用可选链可以方便地获取一个可选变量的值,而不需要使用if/let的条件判断来对其进行解包。

var maybeString: String? = "Hello Swift by Tutorials"
let uppercase = maybeString?.uppercaseString

第二行的问号,是可选链的语法。在运行的时候,编译器会先检查maybeString,如果它包含了正确的实例,则会继续执行uppercaseString语句,否则,则整个表达式返回nil。因此,uppercase也是一个可选类型的变量。

集合类型

任何一门语言里面都必然会包括集合类型。比如Objective-C中的NSArrayNSDictionary。Swift语言里面内置了两种数据类型,即数组和字典。

数组

数组是一组相同类型变量的集合,可以使用方括号来声明:

var array = [1, 2, 3, 4, 5]

在这里,编译器会进行类型推断,因为这个数组为Int类型的数组,也即,这个数组里面只能存储整型类型的数据。

可以使用下标来获取特定位置的元素:

println(array[2])

也可以方便地向数组中添加元素:

array.append(6)

或者直接添加一组范围内的元素:

array.extend(7...10)

我们也可以显式地指定数组所能存储的类型:

var array: [Int] = [1, 2, 3, 4, 5]

在Swift当中有一个AnyObject类型,可以用来指代任何类型,如果我们声明了一个AnyObject类型的数组,则这个数组就可以存储任意类型的元素。虽然使用AnyObject看起来挺方便的,不过它破坏了Swift的类型安全机制,所以最好少用。

字典

数组存储的是一组类型的数据,而字典则存储的是一组键值对。

var dictionary = [1: "Dog", 2: "Cat"]

这行代码声明了一个包含两个键值对的字典,键和值之间使用分号隔开,而每个键值对之间使用逗号来分隔。

与数组类似,我们也可以显示声明字典的类型:

var dictionary: [Int: String] = [1: "Dog", 2: "Cat"]

可以使用方括号配合键来获得特定的值:

println(dictionary[1])

也可以对值进行更新:

dictionary[3] = "Mouse"
println(dictionary)

我们可以将某个值设为nil来删除它:

dictionary[3] = nil

上面的代码执行后,则字典里又只包含两个键值对。

要注意到,当我们使用键来获取某个值的时候,返回的是一个可选值,因为我们要取的值有可能不包含在字典当中。

// 输出Optional("Dog")
println(dictionary[1])

引用和拷贝

在Swift当中,当我们对数组或者字典进行赋值,或者在函数进行传参的时候,编译器会当整个数组或字典进行拷贝,而并非只传递了引用,这与其它很多语言有很大的不同。

var dictionaryA = [1: 1, 2: 4, 3: 9, 4: 16]
var dictionaryB = dictionaryA

dictionaryB[4] = nil
println(dictionaryA)
println(dictionaryB)

运行之后可以看到,我们修改了字典B的元素,而字典A不会受到影响,因此可以证明在Swift当中字典确实是按值拷贝的。

对数组进行同样的操作

var arrayA = [1, 1, 2, 3, 5, 8, 13]
var arrayB = arrayA

arrayB.removeAtIndex(0)
println(arrayA)
println(arrayB)

同样的,对数组B进行的操作不会影响到数组A。

常量集合

如果我们想使声明的集合类型为不可变的,只需要使用let关键字。

let constantArray = [1, 2, 3, 4, 5]

这样定义出来的数组将是不可变的,即不能修改元素的内容,也不能对数组进行增加和删除。

小结

这章主要讲了Swift当中的可选类型,可选类型是Swift当中比较独特的类型,也是用来保证Swift类型安全的重要类型,因此用到的地方会很多,有必要好好掌握。同时还介绍了Swift当中的两种集合类型,数组和字典,使用方法与其它语言也是大同小异的,因此也比较容易上手。