17.Swift-可选链接(Optional Chaining)

来源:互联网 发布:淘宝批量编辑宝贝描述 编辑:程序博客网 时间:2024/05/22 17:46


可选链接(Optional Chaining)是一种可以请求和调用属性、方法及子脚本的过程,它的可选性体现于请求或调用的目标当前可能为空(nil)。如果可选的目标有值,那么调用就会成功;相反,如果可选的目标为空(nil),则这种调用将返回空(nil)。多次请求或调用可以被链接在一起形成一个链,如果任何一个节点为空(nil)将导致整个链失效。

 

注意: Swift的可选链接和Objective-C中的消息为空有些相像,但是Swift可以使用在任意类型中,并且失败与否可以被检测到。


一、可选链接替代强制拆包

通过在想调用的属性、方法、或附属脚本脚本的可选值(optional value)(非空)后面放一个问号,可以定义一个可选链接。这一点很像在可选值后面放一个感叹号!来强制拆得其封包内的值。它们的主要的区别在于当可选为空时可选链接即刻失败,然而一般的强制拆包将会引发运行时错误。

 

为了反映可选链接可以调用空(nil),不论你调用的属性、方法、附属脚本等返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来检测你的可选链接是否调用成功,有返回值即成功,返回nil则失败。

 

调用可选链接的返回结果与原本的返回结果具有相同的类型,但是原本的返回结果被包装成了一个可选值,当可选链接调用成功时,一个应该返回Int的属性将会返回Int?。

 

下面几段代码将解释可选链接和强制拆包的不同和如何检查可选链接是否成功。

 

首先定义两个类Person和Residence。

class Person{

    var residence:Residence?

}


class Residence{

    var numberOfRooms = 1

}

Residence具有一个Int类型的numberOfRooms,其值为1。Person具有一个可选residence属性,它的类型是Residence?。

 

如果你创建一个新的Person实例,它的residence属性由于是被定义为可选的,此属性将默认初始化为空:

let john = Person()

如果你想使用声明符!强制拆包获得这个人residence属性numberOfRooms属性值,将会引发运行时错误,因为这时没有可以供拆包的residence值。

let roomCount = john.residence!.numberOfRooms//error

当john.residence不是nil时,会运行通过,且会将roomCount 设置为一个int类型的合理值。然而,如上所述,当residence为空时,这个代码将会导致运行时错误。

 

可选链接提供了另一种获得numberOfRooms的方法。使用问号?来代替原来!的位置来使用可选链接

if let roomCount = john.residence?.numberOfRooms{

    println("John's residence has \(roomCount) room(s)")

}else{

    println("Unable to retrieve the number of rooms.")

}

//Unable to retrieve the number of rooms.


这告诉Swift来链接可选residence?属性,如果residence存在则取回numberOfRooms的值。

 

因为这种尝试获得numberOfRooms的操作有可能失败,可选链接会返回Int?类型值,或者称作“可选Int”。当residence是空的时候(上例),选择Int将会为空,因此会出先无法访问numberOfRooms的情况。

 

要注意的是,即使numberOfRooms是非可选Int时这一点也成立。只要是通过可选链接的请求就意味着最后numberOfRooms总是返回一个Int?而不是Int。

 

你可以自己定义一个Residence实例给john.residence,这样它就不再为空了:

john.residence = Residence()

john.residence 现在有了实际存在的实例而不是nil了。如果你想使用和前面一样的自判断链接来获得numberOfRoooms,它将返回一个包含默认值1的Int?: 

if let roomCount = john.residence?.numberOfRooms{

    println("John's residence has \(roomCount) room(s)")//John's residence has 1 room(s)

}else{

    println("Unable to retrieve the number of rooms.")

}


二、为可选链接定义模型类

你可以使用可选链接来多层调用属性,方法,和附属脚本。这让你可以利用它们之间的复杂模型来获取更底层的属性,并检查是否可以成功获取此类底层属性。

 

后面的代码定义了四个将在后面使用的模型类,其中包括多层可选链接。这些类包括上面的Person和Residence模型和新增的Room、Address类。

 

Person类定义与之前相同。

Residence类比之前复杂些。这次,它定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组:

class Residence{

    var rooms = [Room]()

    var numberOfRooms:Int{

        return rooms.count

    }

    subscript(i:Int) -> Room{

        get{

            return rooms[i]

        }

        set{

            rooms[i] = newValue

        }

    }

    func printNumberOfRooms(){

        println("The number of rooms is \(numberOfRooms)")

    }

    var address:Address?

    

}

因为Residence存储了一个Room实例的数组,它的numberOfRooms属性值不是一个固定的存储值,而是通过计算而来的。numberOfRooms属性值是由返回rooms数组的count属性值得到的。

为了能快速访问rooms数组,Residence定义了一个可读写的附属脚本,通过数组的元素角标就可以成功调用。

Residence中也提供了一个printNumberOfRooms的方法,即简单的打印房间个数。

最后,Residence定义了一个可选属性叫address(address?)。Address类的属性将在后面定义。 用于rooms数组的Room类是一个很简单的类,它只有一个name属性和一个设定room名的初始化器。

class Room {

    let name:String

    init(name:String){

        self.name = name

    }

}

这个模型中的最终类叫做Address。它有三个可选属性,类型是String?。前面两个可选属性buildingName和 buildingNumber作为地址的一部分,是定义某个建筑物的两种方式。第三个属性street,用于命名地址的街道名:

class Address{

    var buildingName:String?

    var buildingNumber:String?

    var street:String?

    func buildingIdentifier() -> String?{

        if buildingName != nil{

            return buildingName

        }else if buildingNumber != nil{

            return buildingNumber

        }else{

            return nil

        }

    }

}

Address类还提供了一个buildingIdentifier的方法,它的返回值类型为String?。这个方法检查buildingName和buildingNumber的属性,如果buildingName有值则将其返回,或者如果buildingNumber有值则将其返回,如果两者都没有,返回空。


三、通过可选链接访问属性

正如上面“ 可选链接可替代强制拆包”中所述,你可以利用可选链接的可选值获取属性,并且检查属性是否获取成功。

 

使用上述定义的类来创建一个人实例,并再次尝试后去它的numberOfRooms属性:

let john = Person()

if let roomCount = john.residence?.numberOfRooms{

    println("John's residence has \(roomCount) room(s).")

}else{

    println("Unable to retrieve the number of rooms.")//Unable to retrieve the number of rooms.

}

因为john.residence是nil,所以可选链接像之前一样失败了。


你也可以尝试通过可选链接为属性赋值:

let someAddress = Address()

someAddress.buildingNumber = "29"

someAddress.street = "Acacia Road"

john.residence?.address = someAddress

if let roomCount = john.residence?.numberOfRooms{

    println("John's residence has \(roomCount) room(s).")

}else{

    println("Unable to retrieve the number of rooms.")//Unable to retrieve the number of rooms.

}

在这个例子中,尝试通过可选链接为john.residence的address属性赋值将会失败,因为john.residence现在是nil。

如果写成下面这样,在为john.residence赋值之前先创建一个Residence实例给它就不会失败了:

let someAddress = Address()

someAddress.buildingNumber = "29"

someAddress.street = "Acacia Road"

john.residence = Residence()

john.residence?.address = someAddress

if let roomCount = john.residence?.numberOfRooms{

    println("John's residence has \(roomCount) room(s).")

}else{

    println("Unable to retrieve the number of rooms.")//Unable to retrieve the number of rooms.

}



四、通过可选链接调用方法

你可以使用可选链接来调用可选值的方法并检查方法调用是否成功,即使这个方法没有返回值。

 

Residence的printNumberOfRooms方法会打印numberOfRooms的当前值。




这个方法没有返回值。但是,没有返回值类型的函数和方法有一个隐式的返回值类型 ()(参见Function Without Return Values)。

 

如果你利用可选链接调用此方法,这个方法的返回值类型将是Void?,而不是Void,因为当通过可选链接调用方法时返回值总是可选类型(optional type)。即使这个方法本是没有定义返回值,你也可以使用if语句来检查是否能成功调用printNumberOfRooms方法:如果方法通过可选链接调用成功,printNumberOfRooms的隐式返回值将会是Void,如果没有成功,将返回nil:

if john.residence?.printNumberOfRooms() != nil{

    println("It was possible to print the number of rooms.")

}else{

    println("It was not possible to print the number of rooms.")//It was not possible to print the number of rooms.

}

一样调用失败,任何通过可选链接来给属性赋值的尝试都会返回Void?类型的值,通过它你可以判断你的可选链接是否调用成功。


五、通过可选链接访问附属脚本

你可以使用可选链接来尝试获取附属脚本的值或者为附属脚本辅助并检查附属脚本的调用是否成功。

注意: 当你使用可选链接来访问附属脚本的时候,你应该将问号放在附属脚本括号的前面而不是后面。可选链接的问号一般直接跟在可选表达语句的后面。

 

下面这个例子用在Residence类中定义的附属脚本来获取john.residence数组中第一个房间的名字。因为john.residence现在是nil,子脚本的调用失败了。

if let firstRoomName = john.residence?[0].name{

    println("The first room name is \(firstRoomName)")

}else{

    println("Unable to retrieve the first room name.")//Unable to retrieve the first room name.

}

这个可选链接的问号直接放在了john.residence的后面和[]的前面,因为john.residence是可选链接尝试访问的可选值。

类似的,你也可以通过可选链接来设置新的值:

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

这个赋值附属脚本一样失败了,还是因为residence是nil。


如果你创建一个Residence实例给john.residence,且在他的rooms数组中有一个或多个Room实例,那么你可以使用可选链接通过Residence附属脚本来获取在rooms数组中的实例了:

let johnsHouse = Residence()

johnsHouse.rooms.append(Room(name: "Living Room"))

johnsHouse.rooms.append(Room(name: "Kitchen"))

john.residence = johnsHouse


if let firstRoomName = john.residence?[0].name{

    println("The first room name is \(firstRoomName)")//The first room name is Living Room

}else{

    println("Unable to retrieve the first room name.")

}


访问可选类型的附属脚本

如果附属脚本返回一个可选类型,比如Swift中的字典类型的键脚本。通过在脚本括号后面加上问号来链接可选返回值:

var testScores = ["Dave":[86,82,84],"Bev":[79,94,81]]

testScores["Dave"]?[0] = 91

testScores["Bev"]?[0]++

testScores["Brian"]?[0] = 72

testScores //["Bev": [80, 94, 81], "Dave": [91, 82, 84]]

上面的例子定义了一个testScores的字典,包含了两对字符串对应整型数组的键值对。例子中使用可选链接来设置”Dave”对应的整型数组的第一个值为91,使“Bev”对应的整型数组的第一个值增加1,设置”Brian”对应的整型数组的第一个值为72。前面两步成功执行,因为testScores包含了”Dave”和”Bev”两个键,最后一步执行失败,因为字典不包含”Brian”键。



六、链接多层链接

你可以将多层可选链接连接在一起,可以掘取模型内更下层的属性方法和附属脚本。然而多层可选链接不会再添加比已经返回的可选值更多的层。 也就是说:

 

如果你试图获得的类型不是可选类型,由于使用了可选链接它将变成可选类型。 

如果你试图获得的类型已经是可选类型,由于可选链接它也不会提高可选性。

 

因此,如果你试图通过可选链接获得Int值,不论使用了多少层链接返回的总是Int?。

 相似的,如果你试图通过可选链接获得Int?值,不论使用了多少层链接返回的总是Int?。

 

下面的例子试图获取john的residence属性里的address的street属性。这里使用了两层可选链接来联系residence和address属性,他们两者都是可选类型:

if let johnsStreet = john.residence?.address?.street{

    println("John's street name is \(johnsStreet).")

}else{

    println("Unable to retrieve the address.")//Unable to retrieve the address.

}

john.residence的值现在包含一个Residence实例,然而john.residence.address现在是nil,因此john.residence?.address?.street调用失败。

 

从上面的例子发现,你试图获得street属性值。这个属性的类型是String?。因此尽管在可选类型属性前使用了两层可选链接,john.residence?.address?.street的返回值类型也是String?。

 

如果你为Address设定一个实例来作为john.residence.address的值,并为address的street属性设定一个实际值,你可以通过多层可选链接来得到这个属性值。

let johnsAddress = Address()

johnsAddress.buildingName = "The Larches"

johnsAddress.street = "laurel Street"

john.residence!.address = johnsAddress


if let johnsStreet = john.residence?.address?.street{

    println("John's street name is \(johnsStreet).")//"John's street name is laurel Street."

}else{

    println("Unable to retrieve the address.")

}

值得注意的是,“!”符的在定义address实例时的使用(john.residence.address)。john.residence属性是一个可选类型,因此你需要在它获取address属性之前使用!拆包以获得它的实际值。



七、链接可选返回值的方法

前面的例子解释了如何通过可选链接来获得可选类型属性值。你也可以通过调用返回可选类型值的方法并按需链接方法的返回值。

 

下面的例子通过可选链接调用了Address类中的buildingIdentifier 方法。这个方法的返回值类型是String?。如上所述,这个方法在可选链接调用后最终的返回值类型依然是String?:

if let buildingIdentifier = john.residence?.address?.buildingIdentifier(){

    println("John's building identifier is \(buildingIdentifier)")//John's building identifier is The Larches

}


如果你还想进一步对方法返回值执行可选链接,将可选链接问号符放在方法括号的后面:

if let beginsWithThe = john.residence?.address?.buildingIdentifier()?.hasPrefix("The"){

    if beginsWithThe{

        println("John's building identifier begins with \"The\".")//John's building identifier begins with "The".

    }else{

        println("John's building identifier does not beign with \"The\"")

    }

}

注意: 在上面的例子中,你将可选链接问号符放在括号后面是因为你想要链接的可选值是buildingIdentifier方法的返回值,不是buildingIdentifier方法本身。


八、总结

本章介绍了可选链接的概念(在可选值后面加个?来定义一个可选链接),其实就是可以将可选类型串成一条链,一级一级地调用,只要有一个链接地方失败返回nil,整个可选链接就会失败。可选链接可以访问方法、属性、附属脚本。需要注意多层可选链接的规则、?的位置以及?和!的区别。


参考:

1、The Swift Programming Language

2、http://www.cocoachina.com/ios/20140612/8788.html

0 0
原创粉丝点击