(譯)Swift2.2-可選鏈

官方文檔鏈接

原文鏈接


可選鏈(Optional Chaining)是為了在一個(gè)可能當(dāng)前值為niloptional類型里,查詢和調(diào)用屬性,方法和下標(biāo)腳本的一個(gè)過程。如果這個(gè)可選類型包含了一個(gè)值,屬性,方法或是下標(biāo)腳本,那么就會調(diào)用成功;如果這個(gè)可選類型為nil,那么屬性,方法或下表腳本調(diào)用返回值就為nil。多個(gè)連續(xù)的調(diào)用可以被鏈接在一起,但是如果在這個(gè)鏈接里有的節(jié)點(diǎn)為nil,那么就會導(dǎo)致整個(gè)鏈接失敗。

注意:
在Swift中,可選鏈和Objective-C中消息為`nil`有些類似,但是Swift可以使用在任何類型中,并且可以檢查調(diào)用是否成功。

使用可選鏈調(diào)用來強(qiáng)制展開

你可以在你希望調(diào)用的屬性,方法或者下標(biāo)腳本后面,如果這些值為非nil,那么你可以在可選值的后面使用一個(gè)問號(?)來替代可選鏈。這和在可選值后面放一個(gè)感嘆號(?),強(qiáng)制解包有些類似。主要的不同就是可選鏈會在可選值為nil的調(diào)用失敗,因?yàn)閺?qiáng)制解包會在可選值為nil的時(shí)候觸發(fā)運(yùn)行時(shí)錯(cuò)誤。

為了反應(yīng)可選鏈可以被一個(gè)nil值調(diào)用,可選鏈調(diào)用的結(jié)果總是可選值,不論這個(gè)屬性,方法或下標(biāo)腳本返回的是不是非可選值。你可以使用這個(gè)可選返回值來檢查可選鏈調(diào)用成功(返回的可選變量包含一個(gè)值),或者由于在鏈接里有一個(gè)nil值就會調(diào)用失敗。

特別地,可選鏈地調(diào)用的結(jié)果與原本煩人返回結(jié)果有相同的類型,但是包裝成了一個(gè)可選類型。當(dāng)通過可選鏈的方式,一個(gè)Int型的屬性會返回一個(gè)Int?。

下面的代碼片段解釋了可選鏈調(diào)用和強(qiáng)制展開的不同。

首先,聲明了兩個(gè)類,分別為PersonResidence

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

Residence市實(shí)例有一個(gè)名為numberOfRoomsInt類型的屬性,有一個(gè)默認(rèn)值1。Person實(shí)例有一個(gè)可選的residence類型Residence?

如果你創(chuàng)建了一個(gè)新的Person實(shí)例,它的residence屬性默認(rèn)初始化為nil,在下面的代碼,john有一個(gè)residence屬性為nil。

let john = Person()

如果你想訪問這個(gè)personresidencenumberOfRooms屬性,可以在residence后面加一個(gè)感嘆號來強(qiáng)制解包它的值,那么你就會觸發(fā)一個(gè)運(yùn)行時(shí)錯(cuò)誤,因?yàn)闆]有可展開的residence。

let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error

上面的代碼如果改成john.residence有一個(gè)非nil值就會調(diào)用成功。并且設(shè)置一個(gè)Int型的roomCount存儲房間的數(shù)量。但是,當(dāng)residence為空的時(shí)候上面這段代碼會觸發(fā)運(yùn)行時(shí)錯(cuò)誤。

可選鏈調(diào)用提供了一種到另一種訪問numberOfRooms的方法,使用問號(?)來代替原來嘆號(!)的位置:

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

residence后面添加問號之后,Swift就會在residence不為空的情況下訪問numberOfRooms

因?yàn)樵L問numberOfRooms有可能失敗,可選鏈會返回Int?類型,或稱為“可空的Int”。如上例所示,當(dāng)residencenil的時(shí)候,可空的Int將會為nil,表明無法訪問numberOfRooms。

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

通過賦給john.residence一個(gè)Residence的實(shí)例變量,這樣john.residence不為nil了:

john.residence = Residence()

現(xiàn)在就可以正常訪問john.residence.numberOfRooms,其值為默認(rèn)的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.")
}
// Prints "John's residence has 1 room(s)."

為可選鏈定義模型類

通過使用可選鏈可以調(diào)用多層屬性,方法,和下標(biāo)腳本。這樣可以通過各種模型向下訪問各種子屬性。并且判斷能否訪問子屬性的屬性,方法或下標(biāo)。

下面這段代碼定義了四個(gè)模型類,這些例子包括多層可空鏈?zhǔn)秸{(diào)用。為了方便說明,在PersonResidence的基礎(chǔ)上增加了RoomAddress,以及相關(guān)的屬性,方法以及下標(biāo)。

class Person {
    var residence: Residence?
}

Residence類也比之前更完整。這次,Residence類定義一個(gè)名為rooms的變量屬性,初始化為[Room]類型的空數(shù)組。

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() {
        print("The number of rooms is \(numberOfRooms)")
    }
    var address: Address?
}
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() {
        print("The number of rooms is \(numberOfRooms)")
    }
    var address: Address?
}

現(xiàn)在Residence有了一個(gè)存儲Room類型的數(shù)組,numberOfRooms屬性需要計(jì)算,而不是作為單純的變量。計(jì)算后的numberOfRooms返回rooms數(shù)組的count屬性值?,F(xiàn)在的Residence還提供訪問rooms數(shù)組的快捷方式, 通過可讀寫的下標(biāo)來訪問指定位置的數(shù)組元素。此外,還提供printNumberOfRooms方法,這個(gè)方法的作用就是輸出這個(gè)房子中房間的數(shù)量。最后,Residence定義了一個(gè)可空屬性address,其類型為Address?Address類的定義在下面會說明。

Room類被用在rooms數(shù)組里,它是一個(gè)簡單類有一個(gè)名為name的屬性,并且有一個(gè)初始化器來設(shè)置房間的名字

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

最后一個(gè)類是Address,這個(gè)類有三個(gè)String?類型的可空屬性。buildingName以及buildingNumber屬性表示建筑的名稱和號碼,用來表示某個(gè)特定的建筑。第三個(gè)屬性表示建筑所在街道的名稱:

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

Address提供buildingIdentifier()方法,返回值為String?。 如果buildingName不為空則返回buildingName, 如果buildingNumber不為空則返回buildingNumber。如果這兩個(gè)屬性都為空則返回nil

通過可選鏈訪問屬性

正如上文使用可選鏈來強(qiáng)制展開中所述,可以通過可空鏈?zhǔn)秸{(diào)用訪問屬性的可空值,并且判斷訪問是否成功。

上面使用類定義來創(chuàng)建一個(gè)新的Person實(shí)例,然后訪問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.")
}
// Prints "Unable to retrieve the number of rooms."

因?yàn)?code>john.residence為nil,所以毫無疑問這個(gè)可空鏈?zhǔn)秸{(diào)用失敗。

通過可空鏈?zhǔn)秸{(diào)用來設(shè)定屬性值:

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

在這個(gè)例子中,通過john.residence來設(shè)定address屬性也是不行的,因?yàn)?code>john.residence為nil

func createAddress() -> Address {
    print("Function was called.")
    let someAddress = Address()
    someAddress.buildingNumber = "29"
    someAddress.street = "Acacia Road"
    return someAddress
}
john.residence?.address = createAddress()

你可以使用createAddress()函數(shù),就就會知道它沒有被調(diào)用,因?yàn)槭裁炊紱]有打印。

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

可以通過可空鏈?zhǔn)秸{(diào)用來調(diào)用方法,并判斷是否調(diào)用成功,即使這個(gè)方法沒有返回值。

Residence中的printNumberOfRooms()方法輸出當(dāng)前的numberOfRooms值:

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

這個(gè)方法沒有返回值。但是沒有返回值的方法隱式返回Void類型,如無返回值函數(shù)中所述。這意味著沒有返回值的方法也會返回()或者空的元組。

如果在可空值上通過可空鏈?zhǔn)秸{(diào)用來調(diào)用這個(gè)方法,這個(gè)方法的返回類型為Void?,而不是Void,因?yàn)橥ㄟ^可空鏈?zhǔn)秸{(diào)用得到的返回值都是可空的。這樣我們就可以使用if語句來判斷能否成功調(diào)用printNumberOfRooms()方法,即使方法本身沒有定義返回值。通過返回值是否為nil可以判斷調(diào)用是否成功:

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.")
}
// Prints "It was not possible to print the number of rooms."

同樣的,可以判斷通過可空鏈?zhǔn)秸{(diào)用來給屬性賦值是否成功。在上面的例子中,我們嘗試給john.residence中的address屬性賦值,即使residencenil。通過可空鏈?zhǔn)秸{(diào)用給屬性賦值會返回Void?,通過判斷返回值是否為nil可以知道賦值是否成功:

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

通過可選鏈來訪問下標(biāo)腳本

通過可空鏈?zhǔn)秸{(diào)用,我們可以用下標(biāo)來對可空值進(jìn)行讀取或?qū)懭?,并且判斷下?biāo)調(diào)用是否成功。

注意: 
當(dāng)通過可空鏈?zhǔn)秸{(diào)用訪問可空值的下標(biāo)的時(shí)候,應(yīng)該將問號放在下標(biāo)方括號的前面而不是后面??煽真?zhǔn)秸{(diào)用的問號一般直接跟在可空表達(dá)式的后面。

下面這個(gè)例子用下標(biāo)訪問john.residencerooms數(shù)組中第一個(gè)房間的名稱,因?yàn)?code>john.residence為nil,所以下標(biāo)調(diào)用毫無疑問失敗了:

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

在這個(gè)例子中,問號直接放在john.residence的后面,并且在方括號的前面,因?yàn)?code>john.residence是可空值。

類似的,可以通過下標(biāo),用可空鏈?zhǔn)秸{(diào)用來賦值:

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

這次賦值同樣會失敗,因?yàn)?code>residence目前是nil

如果你創(chuàng)建一個(gè)Residence實(shí)例,添加一些Room實(shí)例并賦值給john.residence,那就可以通過可選鏈和下標(biāo)來訪問數(shù)組中的元素:

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 {
    print("The first room name is \(firstRoomName).")
} else {
    print("Unable to retrieve the first room name.")
}
// Prints "The first room name is Living Room."

訪問可選類型的下標(biāo)腳本

如果一個(gè)下標(biāo)腳本返回了一個(gè)可選類型--例如Swift的Dictionary類型--在下標(biāo)右中括號前加一個(gè)問號:

var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]

上面的例子中定義了一個(gè)testScores數(shù)組,包含了兩個(gè)鍵值對, 把String類型的key映射到一個(gè)整形數(shù)組。這個(gè)例子用可空鏈?zhǔn)秸{(diào)用把“Dave”數(shù)組中第一個(gè)元素設(shè)為91,把”Bev”數(shù)組的第一個(gè)元素+1,然后嘗試把”Brian”數(shù)組中的第一個(gè)元素設(shè)為72。前兩個(gè)調(diào)用是成功的,因?yàn)檫@兩個(gè)key存在。但是key“Brian”在字典中不存在,所以第三個(gè)調(diào)用失敗。

多層鏈接

可以通過多個(gè)鏈接多個(gè)可空鏈?zhǔn)秸{(diào)用來向下訪問屬性,方法以及下標(biāo)。但是多層可空鏈?zhǔn)秸{(diào)用不會添加返回值的可空性。

也就是說:

如果你訪問的值不是可空的,通過可空鏈?zhǔn)秸{(diào)用將會放回可空值。
如果你訪問的值已經(jīng)是可空的,通過可空鏈?zhǔn)秸{(diào)用不會變得“更”可空。
因此:

通過可空鏈?zhǔn)秸{(diào)用訪問一個(gè)Int值,將會返回Int?,不過進(jìn)行了多少次可空鏈?zhǔn)秸{(diào)用。
類似的,通過可空鏈?zhǔn)秸{(diào)用訪問Int?值,并不會變得更加可空。

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

john.residence包含Residence實(shí)例,但是john.residence.addressnil。因此,不能訪問john.residence?.address?.street

需要注意的是,上面的例子中,street的屬性為String?。john.residence?.address?.street的返回值也依然是String?,即使已經(jīng)進(jìn)行了兩次可空的鏈?zhǔn)秸{(diào)用。

如果把john.residence.address指向一個(gè)實(shí)例,并且為address中的street屬性賦值,我們就能過通過可空鏈?zhǔn)秸{(diào)用來訪問street屬性

如果你設(shè)置一個(gè)Address實(shí)例作為john.residence.address,并且為地址的street屬性設(shè)置一個(gè)實(shí)際的值,你可以通過多層鏈接訪問street屬性的值:

let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence?.address = johnsAddress
if let johnsStreet = john.residence?.address?.street {
    print("John's street name is \(johnsStreet).")
} else {
    print("Unable to retrieve the address.")
}
// prints "John's street name is Laurel Street."

在上面的例子中,因?yàn)?code>john.residence是一個(gè)可用的Residence實(shí)例,所以對john.residenceaddress屬性賦值成功。

對返回可空值的函數(shù)進(jìn)行鏈接

上面的例子說明了如何通過可空鏈?zhǔn)秸{(diào)用來獲取可空屬性值。我們還可以通過可空鏈?zhǔn)秸{(diào)用來調(diào)用返回可空值的方法,并且可以繼續(xù)對可空值進(jìn)行鏈接。

在下面的例子中,通過可空鏈?zhǔn)秸{(diào)用來調(diào)用AddressbuildingIdentifier()方法。這個(gè)方法返回String?類型。正如上面所說,通過可空鏈?zhǔn)秸{(diào)用的方法的最終返回值還是String?

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

如果要進(jìn)一步對方法的返回值進(jìn)行可空鏈?zhǔn)秸{(diào)用,在方法buildingIdentifier()的圓括號后面加上問號:

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\".")
    }
}
// Prints "John's building identifier begins with "The"."
注意:
在上面的例子中在,在方法的圓括號后面加上問號是因?yàn)閎uildingIdentifier()的返回值是可空值,而不是方法本身是可空的。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容