可選鏈 - Optional Chaining

2018-08-12 21:19 更新

Optional Chaining

可選鏈(Optional Chaining)是一種可以請(qǐng)求和調(diào)用屬性、方法及下標(biāo)腳本的過(guò)程,它的可選性體現(xiàn)于請(qǐng)求或調(diào)用的目標(biāo)當(dāng)前可能為空(nil)。如果可選的目標(biāo)有值,那么調(diào)用就會(huì)成功;相反,如果選擇的目標(biāo)為空(nil),則這種調(diào)用將返回空(nil)。多次請(qǐng)求或調(diào)用可以被鏈接在一起形成一個(gè)鏈,如果任何一個(gè)節(jié)點(diǎn)為空(nil)將導(dǎo)致整個(gè)鏈?zhǔn)А?/p>

注意:
Swift 的可選鏈和 Objective-C 中的消息為空有些相像,但是 Swift 可以使用在任意類(lèi)型中,并且失敗與否可以被檢測(cè)到。

可選鏈可替代強(qiáng)制解析

通過(guò)在想調(diào)用的屬性、方法、或下標(biāo)腳本的可選值(optional value)(非空)后面放一個(gè)問(wèn)號(hào),可以定義一個(gè)可選鏈。這一點(diǎn)很像在可選值后面放一個(gè)嘆號(hào)來(lái)強(qiáng)制拆得其封包內(nèi)的值。它們的主要的區(qū)別在于當(dāng)可選值為空時(shí)可選鏈即刻失敗,然而一般的強(qiáng)制解析將會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤。

為了反映可選鏈可以調(diào)用空(nil),不論你調(diào)用的屬性、方法、下標(biāo)腳本等返回的值是不是可選值,它的返回結(jié)果都是一個(gè)可選值。你可以利用這個(gè)返回值來(lái)檢測(cè)你的可選鏈?zhǔn)欠裾{(diào)用成功,有返回值即成功,返回nil則失敗。

調(diào)用可選鏈的返回結(jié)果與原本的返回結(jié)果具有相同的類(lèi)型,但是原本的返回結(jié)果被包裝成了一個(gè)可選值,當(dāng)可選鏈調(diào)用成功時(shí),一個(gè)應(yīng)該返回Int的屬性將會(huì)返回Int?。

下面幾段代碼將解釋可選鏈和強(qiáng)制解析的不同。

首先定義兩個(gè)類(lèi)PersonResidence。

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

Residence具有一個(gè)Int類(lèi)型的numberOfRooms,其值為 1。Person具有一個(gè)可選residence屬性,它的類(lèi)型是Residence?。

如果你創(chuàng)建一個(gè)新的Person實(shí)例,它的residence屬性由于是被定義為可選型的,此屬性將默認(rèn)初始化為空:

    let john = Person()

如果你想使用感嘆號(hào)(!)強(qiáng)制解析獲得這個(gè)人residence屬性numberOfRooms屬性值,將會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤,因?yàn)檫@時(shí)沒(méi)有可以供解析的residence值。

    let roomCount = john.residence!.numberOfRooms
    //將導(dǎo)致運(yùn)行時(shí)錯(cuò)誤

當(dāng)john.residence不是nil時(shí),會(huì)運(yùn)行通過(guò),且會(huì)將roomCount 設(shè)置為一個(gè)int類(lèi)型的合理值。然而,如上所述,當(dāng)residence為空時(shí),這個(gè)代碼將會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤。

可選鏈提供了一種另一種獲得numberOfRooms的方法。利用可選鏈,使用問(wèn)號(hào)來(lái)代替原來(lái)!的位置:

    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 來(lái)鏈接可選residence?屬性,如果residence存在則取回numberOfRooms的值。

因?yàn)檫@種嘗試獲得numberOfRooms的操作有可能失敗,可選鏈會(huì)返回Int?類(lèi)型值,或者稱作“可選Int”。當(dāng)residence是空的時(shí)候(上例),選擇Int將會(huì)為空,因此會(huì)出現(xiàn)無(wú)法訪問(wèn)numberOfRooms的情況。

要注意的是,即使numberOfRooms是非可選IntInt?)時(shí)這一點(diǎn)也成立。只要是通過(guò)可選鏈的請(qǐng)求就意味著最后numberOfRooms總是返回一個(gè)Int?而不是Int。

你可以自己定義一個(gè)Residence實(shí)例給john.residence,這樣它就不再為空了:

    john.residence = Residence()

john.residence 現(xiàn)在有了實(shí)際存在的實(shí)例而不是nil了。如果你想使用和前面一樣的可選鏈來(lái)獲得numberOfRoooms,它將返回一個(gè)包含默認(rèn)值 1 的Int?

    if let roomCount = john.residence?.numberOfRooms {
        println("John's residence has \(roomCount) room(s).")
    } else {
        println("Unable to retrieve the number of rooms.")
    }
    // 打印 "John's residence has 1 room(s)"。

為可選鏈定義模型類(lèi)

你可以使用可選鏈來(lái)多層調(diào)用屬性,方法,和下標(biāo)腳本。這讓你可以利用它們之間的復(fù)雜模型來(lái)獲取更底層的屬性,并檢查是否可以成功獲取此類(lèi)底層屬性。

后面的代碼定義了四個(gè)將在后面使用的模型類(lèi),其中包括多層可選鏈。這些類(lèi)是由上面的PersonResidence模型通過(guò)添加一個(gè)Room和一個(gè)Address類(lèi)拓展來(lái)。

Person類(lèi)定義與之前相同。

    class Person {
        var residence: Residence?
    }

Residence類(lèi)比之前復(fù)雜些。這次,它定義了一個(gè)變量 rooms,它被初始化為一個(gè)Room[]類(lèi)型的空數(shù)組:

    class Residence {
        var rooms = [Room]()
        var numberOfRooms: Int {
        return rooms.count
        }
        subscript(i: Int) -> Room {
            return rooms[i]
        }
        func printNumberOfRooms() {
            println("The number of rooms is \(numberOfRooms)")
        }
        var address: Address?
    }

因?yàn)?code>Residence存儲(chǔ)了一個(gè)Room實(shí)例的數(shù)組,它的numberOfRooms屬性值不是一個(gè)固定的存儲(chǔ)值,而是通過(guò)計(jì)算而來(lái)的。numberOfRooms屬性值是由返回rooms數(shù)組的count屬性值得到的。

為了能快速訪問(wèn)rooms數(shù)組,Residence定義了一個(gè)只讀的下標(biāo)腳本,通過(guò)插入數(shù)組的元素角標(biāo)就可以成功調(diào)用。如果該角標(biāo)存在,下標(biāo)腳本則將該元素返回。

Residence中也提供了一個(gè)printNumberOfRooms的方法,即簡(jiǎn)單的打印房間個(gè)數(shù)。

最后,Residence定義了一個(gè)可選屬性叫addressaddress?)。Address類(lèi)的屬性將在后面定義。 用于rooms數(shù)組的Room類(lèi)是一個(gè)很簡(jiǎn)單的類(lèi),它只有一個(gè)name屬性和一個(gè)設(shè)定room名的初始化器。

    class Room {
        let name: String
        init(name: String) { self.name = name }
    }

這個(gè)模型中的最終類(lèi)叫做Address。它有三個(gè)類(lèi)型是String?的可選屬性。前面兩個(gè)可選屬性buildingNamebuildingNumber作為地址的一部分,是定義某個(gè)建筑物的兩種方式。第三個(gè)屬性street,用于命名地址的街道名:

    class Address {
        var buildingName: String?
        var buildingNumber: String?
        var street: String?
        func buildingIdentifier() -> String? {
            if buildingName {
                return buildingName
            } else if buildingNumber {
                return buildingNumber
            } else {
                return nil
            }
        }
    }

Address類(lèi)還提供了一個(gè)buildingIdentifier的方法,它的返回值類(lèi)型為String?。這個(gè)方法檢查buildingNamebuildingNumber的屬性,如果buildingName有值則將其返回,或者如果buildingNumber有值則將其返回,再或如果沒(méi)有一個(gè)屬性有值,返回空。

通過(guò)可選鏈調(diào)用屬性

正如上面“ 可選鏈可替代強(qiáng)制解析”中所述,你可以利用可選鏈的可選值獲取屬性,并且檢查屬性是否獲取成功。然而,你不能使用可選鏈為屬性賦值。

使用上述定義的類(lèi)來(lái)創(chuàng)建一個(gè)人實(shí)例,并再次嘗試后去它的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是空,所以這個(gè)可選鏈和之前一樣失敗了,但是沒(méi)有運(yùn)行時(shí)錯(cuò)誤。

通過(guò)可選鏈調(diào)用方法

你可以使用可選鏈的來(lái)調(diào)用可選值的方法并檢查方法調(diào)用是否成功。即使這個(gè)方法沒(méi)有返回值,你依然可以使用可選鏈來(lái)達(dá)成這一目的。

ResidenceprintNumberOfRooms方法會(huì)打印numberOfRooms的當(dāng)前值。方法如下:

    func printNumberOfRooms(){
        println(“The number of rooms is \(numberOfRooms)”)
    }

這個(gè)方法沒(méi)有返回值。但是,沒(méi)有返回值類(lèi)型的函數(shù)和方法有一個(gè)隱式的返回值類(lèi)型Void(參見(jiàn)Function Without Return Values)。

如果你利用可選鏈調(diào)用此方法,這個(gè)方法的返回值類(lèi)型將是Void?,而不是Void,因?yàn)楫?dāng)通過(guò)可選鏈調(diào)用方法時(shí)返回值總是可選類(lèi)型(optional type)。即使這個(gè)方法本身沒(méi)有定義返回值,你也可以使用if語(yǔ)句來(lái)檢查是否能成功調(diào)用printNumberOfRooms方法:如果方法通過(guò)可選鏈調(diào)用成功,printNumberOfRooms的隱式返回值將會(huì)是Void,如果沒(méi)有成功,將返回nil

    if john.residence?.printNumberOfRooms?() {
        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."。

使用可選鏈調(diào)用下標(biāo)腳本

你可以使用可選鏈來(lái)嘗試從下標(biāo)腳本獲取值并檢查下標(biāo)腳本的調(diào)用是否成功,然而,你不能通過(guò)可選鏈來(lái)設(shè)置下標(biāo)腳本。

注意:
當(dāng)你使用可選鏈來(lái)獲取下標(biāo)腳本的時(shí)候,你應(yīng)該將問(wèn)號(hào)放在下標(biāo)腳本括號(hào)的前面而不是后面??蛇x鏈的問(wèn)號(hào)一般直接跟在表達(dá)語(yǔ)句的后面。

下面這個(gè)例子用在Residence類(lèi)中定義的下標(biāo)腳本來(lái)獲取john.residence數(shù)組中第一個(gè)房間的名字。因?yàn)?code>john.residence現(xiàn)在是nil,下標(biāo)腳本的調(diào)用失敗了。

    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."。

在下標(biāo)腳本調(diào)用中可選鏈的問(wèn)號(hào)直接跟在john.residence的后面,在下標(biāo)腳本括號(hào)的前面,因?yàn)?code>john.residence是可選鏈試圖獲得的可選值。

如果你創(chuàng)建一個(gè)Residence實(shí)例給john.residence,且在他的rooms數(shù)組中有一個(gè)或多個(gè)Room實(shí)例,那么你可以使用可選鏈通過(guò)Residence下標(biāo)腳本來(lái)獲取在rooms數(shù)組中的實(shí)例了:

    let johnsHouse = Residence()
    johnsHouse.rooms += Room(name: "Living Room")
    johnsHouse.rooms += Room(name: "Kitchen")
    john.residence = johnsHouse
    if let firstRoomName = john.residence?[0].name {
        println("The first room name is \(firstRoomName).")
    } else {
        println("Unable to retrieve the first room name.")
    }
    // 打印 "The first room name is Living Room."。

連接多層鏈接

你可以將多層可選鏈連接在一起,可以掘取模型內(nèi)更下層的屬性方法和下標(biāo)腳本。然而多層可選鏈不能再添加比已經(jīng)返回的可選值更多的層。 也就是說(shuō):

如果你試圖獲得的類(lèi)型不是可選類(lèi)型,由于使用了可選鏈它將變成可選類(lèi)型。 如果你試圖獲得的類(lèi)型已經(jīng)是可選類(lèi)型,由于可選鏈它也不會(huì)提高可選性。

因此:

如果你試圖通過(guò)可選鏈獲得Int值,不論使用了多少層鏈接返回的總是Int?。 相似的,如果你試圖通過(guò)可選鏈獲得Int?值,不論使用了多少層鏈接返回的總是Int?

下面的例子試圖獲取johnresidence屬性里的addressstreet屬性。這里使用了兩層可選鏈來(lái)聯(lián)系residenceaddress屬性,它們兩者都是可選類(lèi)型:

    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的值現(xiàn)在包含一個(gè)Residence實(shí)例,然而john.residence.address現(xiàn)在是nil,因此john.residence?.address?.street調(diào)用失敗。

從上面的例子發(fā)現(xiàn),你試圖獲得street屬性值。這個(gè)屬性的類(lèi)型是String?。因此盡管在可選類(lèi)型屬性前使用了兩層可選鏈,john.residence?.address?.street的返回值類(lèi)型也是String?。

如果你為Address設(shè)定一個(gè)實(shí)例來(lái)作為john.residence.address的值,并為addressstreet屬性設(shè)定一個(gè)實(shí)際值,你可以通過(guò)多層可選鏈來(lái)得到這個(gè)屬性值。

    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).")
    } else {
        println("Unable to retrieve the address.")
    }
    // 打印 "John's street name is Laurel Street."。

值得注意的是,“!”符號(hào)在給john.residence.address分配address實(shí)例時(shí)的使用。john.residence屬性是一個(gè)可選類(lèi)型,因此你需要在它獲取address屬性之前使用!解析以獲得它的實(shí)際值。

鏈接可選返回值的方法

前面的例子解釋了如何通過(guò)可選鏈來(lái)獲得可選類(lèi)型屬性值。你也可以通過(guò)可選鏈調(diào)用一個(gè)返回可選類(lèi)型值的方法并按需鏈接該方法的返回值。

下面的例子通過(guò)可選鏈調(diào)用了Address類(lèi)中的buildingIdentifier 方法。這個(gè)方法的返回值類(lèi)型是String?。如上所述,這個(gè)方法在可選鏈調(diào)用后最終的返回值類(lèi)型依然是String?

    if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
        println("John's building identifier is \(buildingIdentifier).")
    }
    // 打印 "John's building identifier is The Larches."。

如果你還想進(jìn)一步對(duì)方法返回值執(zhí)行可選鏈,將可選鏈問(wèn)號(hào)符放在方法括號(hào)的后面:

    if let upper = john.residence?.address?.buildingIdentifier()?.uppercaseString {
        println("John's uppercase building identifier is \(upper).")
    }
    // 打印 "John's uppercase building identifier is THE LARCHES."。

注意:
在上面的例子中,你將可選鏈問(wèn)號(hào)符放在括號(hào)后面是因?yàn)槟阆胍溄拥目蛇x值是buildingIdentifier方法的返回值,不是buildingIdentifier方法本身。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)