第一章主要学习了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
中的NSArray
和NSDictionary
。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当中的两种集合类型,数组和字典,使用方法与其它语言也是大同小异的,因此也比较容易上手。