Swift 中的可选类型(Optional)

来源:互联网 发布:dwg转pdf软件 编辑:程序博客网 时间:2024/05/21 06:43

在 Swift 中,我们使用可选类型来表示值有可能缺失的情况。一个可选类型的值表示他有值并且值等于 x 或者他根本就没有值。

注意:可选类型在C语言和OC语言中并不存在。OC中与可选类型意思最接近的是一个本该返回某个类型的对象的方法可以返回nilnil在此时表示没有有效值。然而,OC中的这种机制只对对象类型有效,对结构体,基本C类型和枚举等都无效。对于这些类型,当他们没有有效值的时候,OC会返回一些特殊常量(比如 NSNotFound)。这种方式假定方法的调用者知道这个特殊值并且记得去检测这个值。Swift中的可选类型不同,他可以表示任意类型的有效值缺失的情况,不需要指定一些特殊值。

下面的例子说明可选类型是如何处理有效值缺失的情况的。Swift 中的 Int 类型有一个将 String 类型的值转化为 Int 值的构造器。但是并不是每个 String 类型的值都可以转化成 Int 类型,“123” 可以被转化为 Int 类型的123,“hello world”却不能被转化为一个有效数字。

let possibleNumber = "123"// 由于改构造器有失败的可能,所以他的返回值的类型是可选类型的 Int。let convertedNumber = Int(possibleNumber)

一个可选类型的 Int 写作 Int?,问号表示这是一个可选类型。这个可选类型可以包含一个 Int 类型的值,或者没有值。只有这两种情况,不可以是 String 类型, Bool 类型或者其他任何类型。

nil

通过将一个可选类型的值设为 nil 来表示这个可选类型中不包含任何值。

// serverResponseCode 包含一个 Int 类型的值 404var serverResponseCode: Int? = 404// serverResponseCode 不包含任何值serverResponseCode = nil
注意:nil 只能被赋值给一个可选类型来表示值缺失的情况。不能把 nil 赋值给非可选类型的变量。如果一个变量有可能不包含值,请将其声明为可选类型。

如果你在声明一个可选类型时没有给他初始值,他会被自动设置为nil。

// surveyAnswer 的值为 nilvar surveyAnswer: String?
注意:Swift中的 nilOC中的 nil 是不一样的。在OC中,nil 是指向一个不存在的对象的指针。在Swift中,nil 不是一个指针,他用来表示一个可选类型的变量不包含任何值的情况。任何类型的可选值都可以被设置为 nil,不是只有对象类型。

if 语句和强制解析

你可以使用 if 语句来判断一个可选类型是否有值。当一个可选类型有值的时候,他被认为是不等于 nil 的,反之,则等于 nil。

var convertedNumber = Int("123")if convertedNumber != nil {    print("convertedNumber contains some integer value")}convertedNumber = Int("hello world")if convertedNumber == nil {    print("convertedNumber does not contain any integer number")}输出结果:convertedNumber contains some integer value        convertedNumber does not contain any integer number

如果你确定一个可选类型包含有效值,你可以在这个可选类型后面加上 ! 号来获取这个有效值。获取有效值被称为强制解析。

let convertedNumber = Int("123")if convertedNumber != nil {    print("convertedNumber contains some integer value of \(convertedNumber!)")}输出结果:convertedNumber contains some integer value of 123
注意:强制解析一个值为nil的可选类型会引发运行时错误,导致程序崩溃。所以在使用强制解析之前,请确保该可选类型包含一个有效值。

可选绑定

使用可选类型之前,通常需要判断这个可选类型是否包含有效值,如果包含,要把这个有效值强制解析出来进行使用。可选绑定将这个过程融合到了一步里面。你可以在 if语句和 while语句中使用可选绑定。可选绑定的一般格式如下:

// someOptional 是一个可选类型。可选绑定判断这个可选类型是否包含有效值,如果包含,则将这个有效值强制解析出来赋给常量name,并且执行if语句块中的代码。if let name = someOptional {    // statements}
let possibleNumber = "123"if let actualNumber = Int(possibleNumber) {    // 可选绑定已经将可选类型的有效值强制解析了出来,并将其赋值给actualNumber, 所以actualNumber 不是一个可选类型,使用他的时候不需要在后面加 !。    print("'\(possibleNumber)' has a integer value of \(actualNumber)")} else {    print("'\(possibleNumber)' can not be converted to a integer value")}// 执行结果:'123' has a integer value of 123

也可以在可选绑定中将强制解析出来的值赋给一个变量。如上例,如果你需要在 if语句的第一分支中改变 actualNumber 的值,那么你可以在可选绑定中将 actualNumber 声明为一个变量,使用 var 关键字。

可以将多个可选绑定写在同一个 if判断中,并使用 where 关键字来连接其他的逻辑判断语句。如下例:

if let firstNumber = Int("4"), secondNumber = Int("42") where firstNumber < secondNumber {    print("\(firstNumber) < \(secondNumber)")}// 执行结果: 4 < 42

where 关键字仅仅用来添加其他非可选绑定的逻辑判断,不是必须要和前面出现的可选绑定有联系。

if let firstNumber = Int("4") where 20 < 40 && 30 < 50 {    print("firstNumber is \(firstNumber), where can append any Bool expression")}// 执行结果: firstNumber is 4, where can append any Bool expression

隐式解析可选类型

可选类型表示一个常量或者变量可以不包含有效值,我们一般使用可选绑定来判断和使用他的有效值。

注意:可选类型可以是一个常量。当你将一个可选类型声明为一个常量时,如果这个可选类型没有有效值,你可以赋给他一个有效值。一旦他具有了有效值,这个有效值不可改变,也不能将其重新设置为nil

有时根据程序的结构,我们可以明显地看出当一个可选类型被设置了有效值之后,他将总是具有有效值。这种情况下,每次使用这个值都需要强制解析或者使用可选绑定就显得有点繁琐了,此时可以使用隐式解析可选类型。

声明隐式解析可选类型时在其类型名后面加上感叹号 ! 而不是问号 ?,声明之后,可以直接使用这个常量或者变量的有效值。如上所述,隐式解析可选类型适用于当一个可选类型的量一旦被设置了有效值,就会在其后的任一时间都具有有效值的情况(这个有效值可以改变,只要具有有效值即可)。

隐式解析可选类型实际上还是一个可选类型,只是使用上看起来像是非可选类型,因为使用他的有效值的时候不需要强制解析。下例演示了使用可选类型和隐式解析可选类型的区别:

let possibleNumber: String? = "An optional string"// 获取有效值时需要在后面加上感叹号来强制解析let forcedString: String = possibleNumner!let assumedString: String! = "An implicitly unwrapped optional string"// 获取有效值不需要强制解析let implicitString: String = assumedString

你可以认为隐式解析可选类型在每一次使用他的时候,都会被自动解析出来。

注意:如果一个隐式解析可选类型的值为nil,在你直接使用他的时候,会触发运行时错误。所以使用隐式解析可选类型一定要确保这个量一旦被赋予有效值之后,就会一直具有有效值。

你也可以像使用一个一般可选类型一样来使用一个隐式解析可选类型,比如判断他是否包含有效值:

if assumedString != nil {    print(assumedString)}// 执行结果:An implicitly unwrapped optional string

或者对其使用可选绑定:

if let definiteString = assumedString {    print(definiteString)}// 执行结果:An implicitly unwrapped optional string

可选链

可选链是一种用于查询或调用一个可能不包含有效值的可选类型的属性、方法和下标的过程。如果这个可选类型包含有效值,这个查询或调用过程正常执行;如果这个可选类型不包含有效值,这个查询或调用过程返回nil,表示过程失败。多个查询可以链接在一起,当其中任何一个查询失败时,整个查询链即告失败,整体返回nil。

注意:Swift中的可选链类似于在OC中向nil发送消息,不同的是,可选链适用于任何类型而且可以被判断是否执行成功。

可选链是强制解析之外的另一种选择

当需要查询或调用一个包含有效值的可选类型的属性或者方法时,通过在其后面加上 ? 号来形成可选链。这和在其后加上 ! 来强制解析其有效值的过程非常相似。主要区别在于,如果这个可选不包含有效值,可选链仅仅会返回nil,表示查询或者调用失败,而强制解析会触发运行时错误,导致程序崩溃。

因为可选链查询或调用成功会返回对应的返回值,失败则返回nil,所以可选链的返回值是一个可选类型,不论所要查询或者调用的属性和方法的返回值是不是可选类型。你可以使用这个返回的可选类型来判断可选链是否执行成功。

具体而言,可选链调用的返回值的类型和其调用的属性的类型或者方法的返回值的类型是相同的,只是这个值被封装在了一个可选类型中。如果用可选链去查询一个 Int 类型的属性,查询成功时,这个可选链的返回值类型为 Int? 。

下面的几段代码演示了可选链和强制解析的区别以及如何判断可选链是否成功。

class Person {    var residence: Residence?}class Residence {    var numberOfRooms = 1}

Residence 类型的对象包含一个 Int 类型的属性 numberOfRooms,默认值为1。Person 类型的对象包含一个可选类型的属性 residence,类型是 Residence?。

如果你创建一个 Person 类型的实例,他的 residence 属性默认被初始化为 nil。如下代码所示:

// 创建一个 Person 实例 john,此时 john.residence = nillet john = Person()

此时如果你想要通过强制解析来使用 john 的 residence 的有效值,会触发运行时错误:

let roomCount = john.residence!.numberOfRooms// 触发运行时错误,程序崩溃

当 john.residence 包含有效值的时候,上面的代码会执行成功,roomCount 会被正确赋值。但是,当你使用强制解析来获取一个可选类型的有效值时,如果这个可选类型不包含有效值,则一定会触发运行时错误。如上所示。

可选链是获取 numberOfRooms 的值的另一种方法。在可选类型后面加上 ? 即可使用可选链,如下所示:

if let roomCount = john.residence?.numberOfRooms {    print("John's residence has \(roomCount) room(s).")} else {    print("Unable to retrieve the number of rooms.")}// 执行结果:Unable to retrieve the number of rooms.

上面代码中的可选链告诉 Swift 去将对 residence 是否包含可选值的判断和 numberOfRooms 属性的值的获取链接在一起,并且只有当 residence 包含有效值的时候才去获取 numberOfRooms 属性。

因为获取 numberOfRooms 属性有可能失败,所以整个可选链的返回值是 Int? 。如果 residence 不包含有效值,可选链返回nil,表示无法获取 numberOfRooms 的值。需要注意的是,尽管 numberOfRooms 属性是 Int 类型,获取他的值的可选链的返回值还是一个 Int? 类型。可选链的返回值永远是可选类型。

你可以给 john.residence 属性赋一个值,使他不再为 nil:

john.residence = Residence()

john.residence 现在包含一个有效值。如果此时使用上段代码中的可选链获取 numberOfRooms 属性的值的话,可选链现在会返回一个包含有效值1的 Int 可选类型。

if let roomCount = john.residence?.numberOfRooms {    print("John's residence has \(roomCount) room(s).")} else {    print("Unable to retrieve the number of rooms.")}// 执行结果:John's residence has 1 room(s).

可选链可以多层链接,查询或调用深层次的属性和方法

可以使用可选链查询更深层次的属性、方法或者下标。下面定义了几个类型,用于演示可选链的多种用法。

Person 类和之前的定义相同:

class Person {    var residence: Residence?}

Residence 类添加了更多的属性和方法,其中用到的 Room 类型和 Address 类型会在后面定义:

class Residence {    var rooms = [Room]()    // 计算型属性,仅仅是返回 rooms 数组的长度。    var numberOfRooms: Int {        return rooms.count    }    // 下标方法,简化对 rooms 数组的访问。使用时需提供对应index。    subscript(i: Int) -> Room {        get {            return rooms[i]        }        set {            rooms[i] = newValue        }    }    func printNumberOfRooms() {        print("The number of rooms if \(numberOfRooms)")    }    var address: Address?}class Room {    let name: String    init(name: String) {        self.name = name    }}class Address {    var buildingName: String?    var buildingNumber: String?    var street: String?    // 返回地址    func buildingIdentifier() -> String? {        if buildingName != nil {            // 如果建筑名字不为nil,则返回建筑名字            return buildingName        } else if buildingNumber != nil && street != nil {            // 如果建筑门牌号和街道名字都不为nil,则将他们拼在一起作为地址            return "\(buildingNumber) \(street)"        } else {            // 无法正确的描述地址,返回nil            return nil        }    }}

通过可选链访问属性

创建一个 Person 实例,并访问他的 numberOfRooms 属性,和之前代码一样:

let john = Person()if let roomCount = john.residence?.numberOfRooms {    print("John's residence has \(roomCount) room(s).")} else {    print("Unable to retrieve the number of rooms.")}// 执行结果:Unable to retrieve the number of rooms.

因为 john.residence 为nil,这个可选链返回nil。

你也可以通过可选链给属性赋值:

let someAddress = Address()someAddress.buildingNumber = "29"someAddress.street = "Acacia Road"john.residence?.address = someAddress

上段代码中的给 john.residence 的 address 属性赋值的操作将会失败,因为 john.residence 为nil。

通过可选链调用方法

你可以使用可选链调用可选类型属性的方法并且验证改方法调用是否成功。不论这个方法是否具有返回值,或者返回值是什么类型,你都可以使用可选链调用他并且验证他是否调用成功。

Residence 类中的 printNumberOfRooms() 方法输出了 numberOfRooms 的值,下面是这个方法的定义:

func printNumberOfRooms() {    print("The number of rooms is \(numberOfRooms)")}

这个方法没有指定返回值。在Swift 中,没有指定返回值的方法或函数都隐性的返回一个 Void 类型,表示他们返回一个(),或者叫空元组。

如果你通过一个可选链来调用这个方法,这个可选链的返回值将是 Void?,而不是 Void,因为可选链的返回类型总是可选类型。这允许我们使用 if 语句来判断是否可以调用 printNumberOfRooms 方法,尽管这个方法没有返回值。如下所示:

if john.residence?.printNumberOfRooms() != nil {    print("It was possible to print the number of rooms.")} else {    print("It was not possible to print the number of rooms.")}// 执行结果:It was not possible to print the number of rooms.

同样的,通过可选链给可选类型属性的属性赋值时,也可以判断赋值是否成功,可选链在此种情况下的返回值也是 Void? 类型。

if (john.residence?.address = someAddress) != nil {    print("It was possible to set the address")} else {    print("It was not possible to set the address")}// 执行结果:It was not possible to set the address

通过可选链访问下标

可以通过可选链访问一个可选类型的下标并验证是否成功。

注意:当你通过可选链访问一个可选类型下标时,将 ? 放在中括号的前面而不是后面。可选链中的 ? 号总是紧跟在可选类型之后。

下面的代码想要使用可选链,通过下标访问 john.residence 属性中的 rooms 数组的第一个元素。由于 john.residence 目前没有有效值,所以这次下表访问失败了:

if let firstRoomName = john.residence?[0].name {    print("The first room name is \(firstRoomName).")} else {    print("Unable to retrieve the first room name.")}// 执行结果:Unable to retrieve the first room name.

类似的,也可以使用可选链通过下标赋值:

john.residence?[0] = Room(name: "Bathroom")

上面代码中的赋值操作也是失败的,因为 residence 现在没有有效值。

如果你给 john.residence 赋一个有效值,你就可以通过可选链成功地使用下标访问 rooms 数组了。

let johnsHouse = Residence()johnsHouse.rooms.append(Room(name: "Living Room"))johnsHouse.rooms.append(Room(name: "Kitchen"))john.residence = johnsHouseif let firstRoomName = john.residence?[0].name {    print("The first room name is \(firstRoomName).")} else {    print("Unable to retrieve the first room name.")}// 执行结果:The first room name is Living Room.

访问返回可选类型的下标

如果一个下标返回可选类型(比如 Swift 中的 Dictionary 类型),在下标的中括号的后面加上 ? 来使用可选链访问其返回值的属性或方法。

var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]// 前两句代码执行成功,因此下标的返回值具有有效值testScores["Dave"]?[0] = 91testScores["Bev"]?[0]++// 执行失败,因为下标的返回值不具有有效值testScores["Brain"]?[0] = 72// 执行完毕后,"Dave" 数组为 [91, 82, 84], "Bev" 数组为 [80, 94, 91]

使用多层可选链

你可以将多个可选链链接在一起来访问更深层次的属性或方法,但是不管你链接了多少层,整个可选链的返回值的类型还是一个可选类型,并且是可能包含和最终要访问的属性的类型或者方法的返回值的类型相同的有效值的可选类型。多层链接中只要有一层返回nil,则整个可选链失败,返回nil。

if let johnsStreet = john.residence?.address?.street {    print("John's street name is \(johnsStreet).")} else {    print("Unable to retrieve the address.")}// 执行结果:Unable to retrieve the address.

john.residence 包含有效值,但是 john.residence.address 是一个nil,所以上述执行还是失败了。

上述代码中,多层可选链最终要访问的属性的类型是 String?,所以整个可选链的返回类型也是 String?。

如果我们给 address 属性赋一个有效值,就可以成功访问了。

let johnsAddress = Address()johnsAddress.buildingName = "The Larches"johnsAddress.street = "Laurel Street"// 赋值成功,此时 john.residence 包含有效值john.residence?.address = johnsAddressif let johnsStreet = john.residence?.address?.street {    print("John's street name is \(johnsStreet).")} else {    print("Unable to retrieve the address.")}// 执行结果:John's street name is Laurel Street.

链接返回值为可选类型的方法

可以使用可选链调用返回值为可选类型的方法,并且继续链接这个返回值如果需要的话。

下例演示了使用可选链调用 Address 类中的 buildingIdentifier() 方法。这个方法的返回值为 String?,和之前讲的一样,整个可选链的返回值将是 String?。

if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {    print("John's building identifier is \(buildingIdentifier).")}// 执行结果:John's building identifier is The Larches.

如果你需要将这个返回值也加入到可选链中来,可以在方法调用的后面加上 ?:

if let beginsWithThe = john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {    if beginsWithThe {        print("John's building identifier begins with \"The\".")    } else {        print("John's building identifier does not begin with \"The\".")    }}// 执行结果:John's building identifier begins with "The".
0 0