swift基礎(chǔ)—可選鏈?zhǔn)秸{(diào)用

可選鏈?zhǔn)秸{(diào)用 是一種可以在當(dāng)前值可能為 nil 的可選值上請求和調(diào)用屬性、方法及下標(biāo)的方法。如果可選值有值,那么調(diào)用就會成功;如果可選值是 nil ,那么調(diào)用將返回 nil 。多個調(diào)用可以連接在一起形成一個調(diào)用 鏈,如果其中任何一個節(jié)點為 nil ,整個調(diào)用鏈都會失敗,即返回 nil 。

注意
Swift 的可選鏈?zhǔn)秸{(diào)用和 Objective-C 中向 nil 發(fā)送消息有些相像,但是 Swift 的可選鏈?zhǔn)秸{(diào)用可以應(yīng)用于任意類型,并且能檢查調(diào)用是否成功。

使用可選鏈?zhǔn)秸{(diào)用代替強制展開

通過在想調(diào)用的屬性、方法、或下標(biāo)的可選值后面放一個問號( ? ),可以定義一個可選鏈。這一點很像在可選 值后面放一個嘆號( ! )來強制展開它的值。它們的主要區(qū)別在于當(dāng)可選值為空時可選鏈?zhǔn)秸{(diào)用只會調(diào)用失 敗,然而強制展開將會觸發(fā)運行時錯誤。

為了反映可選鏈?zhǔn)秸{(diào)用可以在空值( nil )上調(diào)用的事實,不論這個調(diào)用的屬性、方法及下標(biāo)返回的值是不是可選值,它的返回結(jié)果都是一個可選值。你可以利用這個返回值來判斷你的可選鏈?zhǔn)秸{(diào)用是否調(diào)用成功,如果調(diào)用 有返回值則說明調(diào)用成功,返回 nil 則說明調(diào)用失敗。

特別地,可選鏈?zhǔn)秸{(diào)用的返回結(jié)果與原本的返回結(jié)果具有相同的類型,但是被包裝成了一個可選值。例如,使用 可選鏈?zhǔn)秸{(diào)用訪問屬性,當(dāng)可選鏈?zhǔn)秸{(diào)用成功時,如果屬性原本的返回結(jié)果是 Int 類型,則會變?yōu)?Int? 類型。

下面幾段代碼將解釋可選鏈?zhǔn)秸{(diào)用和強制展開的不同。

首先定義兩個類 PersonResidence :

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

Residence 有一個 Int 類型的屬性 numberOfRooms ,其默認值為 1Person 具有一個可選的 residence 屬性,其類型為 Residence?

假如你創(chuàng)建了一個新的 Person 實例,它的 residence 屬性由于是是可選型而將初始化為 nil ,在下面的代碼中, john 有一個值為 nilresidence 屬性:

let john = Person()

如果使用嘆號( ! )強制展開獲得這個 johnresidence 屬性中的 numberOfRooms 值,會觸發(fā)運行時錯誤,因為 這時 residence 沒有可以展開的值:

let roomCount = john.residence!.numberOfRooms 
// 這會引發(fā)運行時錯誤

john.residence 為非 nil 值的時候,上面的調(diào)用會成功,并且把 roomCount 設(shè)置為 Int 類型的房間數(shù)量。正如上 面提到的,當(dāng) residencenil 的時候上面這段代碼會觸發(fā)運行時錯誤。

可選鏈?zhǔn)秸{(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.")
}
// 打印 “Unable to retrieve the number of rooms.”

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

因為訪問 numberOfRooms 有可能失敗,可選鏈?zhǔn)秸{(diào)用會返回 Int? 類型,或稱為“可選的 *Int *”。如上例所示,當(dāng) residencenil 的時候,可選的 Int 將會為 nil ,表明無法訪問 numberOfRooms 。訪問成功時,可選的 Int 值會通過可選綁定展開,并賦值給非可選類型的 roomCount 常量。

要注意的是,即使 numberOfRooms 是非可選的 Int 時,這一點也成立。只要使用可選鏈?zhǔn)秸{(diào)用就意味著 numberOfRooms 會返回一個 Int? 而不是 Int 。

可以將一個 Residence 的實例賦給 john.residence ,這樣它就不再是 nil 了:

john.residence = Residence()

john.residence 現(xiàn)在包含一個實際的 Residence 實例,而不再是 nil 。如果你試圖使用先前的可選鏈?zhǔn)秸{(diào)用訪問 numberOfRooms ,它現(xiàn)在將返回值為 1Int? 類型的值:

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).”

為可選鏈?zhǔn)秸{(diào)用定義模型類

通過使用可選鏈?zhǔn)秸{(diào)用可以調(diào)用多層屬性、方法和下標(biāo)。這樣可以在復(fù)雜的模型中向下訪問各種子屬性,并且判斷能否訪問子屬性的屬性、方法或下標(biāo)。

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

Person 類的定義基本保持不變:

class Person {
    var residence: Residence?
}

Residence 類比之前復(fù)雜些,增加了一個名為 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?
}

現(xiàn)在 Residence 有了一個存儲 Room 實例的數(shù)組,numberOfRooms 屬性被實現(xiàn)為計算型屬性,而不是存儲型屬性。 numberOfRooms 屬性簡單地返回 rooms 數(shù)組的 count 屬性的值。

Residence 還提供了訪問 rooms 數(shù)組的快捷方式,即提供可讀寫的下標(biāo)來訪問 rooms 數(shù)組中指定位置的元素。此外,* Residence* 還提供了 printNumberOfRooms 方法,這個方法的作用是打印 numberOfRooms 的值。

此外,Residence 還提供了 printNumberOfRooms 方法,這個方法的作用是打印 numberOfRooms 的值。

最后,Residence 還定義了一個可選屬性 address,其類型為 Address?。

Room 類是一個簡單類,其實例被存儲在 rooms 數(shù)組中。該類只包含一個屬性 name,以及一個用于將該屬性設(shè)置為適當(dāng)?shù)姆块g名的初始化函數(shù):

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 && street != nil {
             return "\(buildingNumber) \(street)"
         } else {
            return nil
         } 
     }
}

Address 類提供了 buildingIdentifier() 方法,返回值為 String?,如果 buildingName 有值則返回 buildingName?;蛘撸绻?buildingNumberstreet 均有值則返回 buildingNumber。否則,返回 nil。

通過可選鏈?zhǔn)秸{(diào)用訪問屬性

可以通過可選鏈?zhǔn)秸{(diào)用在一個可選值上訪問它的屬性,并判斷訪問是否成功。

下面的代碼創(chuàng)建了一個 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.residencenil ,所以這個可選鏈?zhǔn)秸{(diào)用依舊會像先前一樣失敗。

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

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

在這個例子中,通過 john.residence 來設(shè)定 address 屬性也會失敗,因為 john.residence 當(dāng)前為 nil。

上面代碼中的賦值過程是可選鏈?zhǔn)秸{(diào)用的一部分,這意味著可選鏈?zhǔn)秸{(diào)用失敗時,等號右側(cè)的代碼不會被執(zhí)行。對于上面的代碼來說,很難驗證這一點,因為像這樣賦值一個常量沒有任何副作用。下面的代碼完成了同樣 的事情,但是它使用一個函數(shù)來創(chuàng)建 Address 實例,然后將該實例返回用于賦值。該函數(shù)會在返回前打印“Funct ion was called”,這使你能驗證等號右側(cè)的代碼是否被執(zhí)行。

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ù)并未被執(zhí)行。

通過可選鏈?zhǔn)秸{(diào)用調(diào)用方法

可以通過可選鏈?zhǔn)秸{(diào)用來調(diào)用方法,并判斷是否調(diào)用成功,即使這個方法沒有返回值。 Residence 類中的 printNumberOfRooms() 方法打印當(dāng)前的 numberOfRooms 值,如下所示:

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

這個方法沒有返回值。然而,沒有返回值的方法具有隱式的返回類型 Void。這 意味著沒有返回值的方法也會返回 () ,或者說空的元組。

如果在可選值上通過可選鏈?zhǔn)秸{(diào)用來調(diào)用這個方法,該方法的返回類型會是 Void? ,而不是 Void ,因為通過可選 鏈?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.")
}
// 打印 “It was not possible to print the number of rooms.”

同樣的,可以據(jù)此判斷通過可選鏈?zhǔn)秸{(diào)用為屬性賦值是否成功。通過可選鏈?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.")
}
// 打印 “It was not possible to set the address.”

通過可選鏈?zhǔn)秸{(diào)用訪問下標(biāo)

通過可選鏈?zhǔn)秸{(diào)用,我們可以在一個可選值上訪問下標(biāo),并且判斷下標(biāo)調(diào)用是否成功。

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

下面這個例子用下標(biāo)訪問 john.residence 屬性存儲的 Residence 實例的 rooms 數(shù)組中的第一個房間的名稱,因為 john.residencenil ,所以下標(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.")
}
// 打印 “Unable to retrieve the first room name.”

在這個例子中,問號直接放在 john.residence 的后面,并且在方括號的前面,因為 john.residence 是可選值。 類似的,可以通過下標(biāo),用可選鏈?zhǔn)秸{(diào)用來賦值:

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

這次賦值同樣會失敗,因為 residence 目前是 nil。
如果你創(chuàng)建一個 Residence 實例,并為其 rooms 數(shù)組添加一些 Room 實例,然后將 Residence 實例賦值給 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.")
}
// 打印 “The first room name is Living Room.”
訪問可選類型的下標(biāo)

如果下標(biāo)返回可選類型值,比如 Swift 中 類型的鍵的下標(biāo),可以在下標(biāo)的結(jié)尾括號后面放一個問號 來在其可選返回值上進行可選鏈?zhǔn)秸{(diào)用:

var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
// "Dave" 數(shù)組現(xiàn)在是 [91, 82, 84],"Bev" 數(shù)組現(xiàn)在是 [80, 94, 81]

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

連接多層可選鏈?zhǔn)秸{(diào)用

可以通過連接多個可選鏈?zhǔn)秸{(diào)用在更深的模型層級中訪問屬性、方法以及下標(biāo)。然而,多層可選鏈?zhǔn)秸{(diào)用不會增加返回值的可選層級。

也就是說:

  • 如果你訪問的值不是可選的,可選鏈?zhǔn)秸{(diào)用將會返回可選值。
  • 如果你訪問的值就是可選的,可選鏈?zhǔn)秸{(diào)用不會讓可選返回值變得“更可選”。

因此:

  • 通過可選鏈?zhǔn)秸{(diào)用訪問一個 Int 值,將會返回 Int? ,無論使用了多少層可選鏈?zhǔn)秸{(diào)用。
  • 類似的,通過可選鏈?zhǔn)秸{(diào)用訪問 Int? 值,依舊會返回 Int? 值,并不會返回 Int?? 。

下面的例子嘗試訪問 john 中的 residence 屬性中的 address 屬性中的 street 屬性。這里使用了兩層可選鏈?zhǔn)秸{(diào)用, residence 以及 address 都是可選值:

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 現(xiàn)在包含一個有效的 Residence 實例。然而, john.residence.address 的值當(dāng)前為 nil 。因此,調(diào)用 john.residence?.address?.street 會失敗。

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

如果為 john.residence.address 賦值一個 Address 實例,并且為 address 中的 street 屬性設(shè)置一個有效值,我們就能過通過可選鏈?zhǔn)秸{(diào)用來訪問 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.")
}
// 打印 “John's street name is Laurel Street.”

在上面的例子中,因為 john.residence 包含一個有效的 Residence 實例,所以對 john.residenceaddress 屬性賦值將會成功。

在方法的可選返回值上進行可選鏈?zhǔn)秸{(diào)用

上面的例子展示了如何在一個可選值上通過可選鏈?zhǔn)秸{(diào)用來獲取它的屬性值。我們還可以在一個可選值上通過可選鏈?zhǔn)秸{(diào)用來調(diào)用方法,并且可以根據(jù)需要繼續(xù)在方法的可選返回值上進行可選鏈?zhǔn)秸{(diào)用。

在下面的例子中,通過可選鏈?zhǔn)秸{(diào)用來調(diào)用 AddressbuildingIdentifier() 方法。這個方法返回 String? 類型的值。如上所述,通過可選鏈?zhǔn)秸{(diào)用來調(diào)用該方法,最終的返回值依舊會是 String? 類型:

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

如果要在該方法的返回值上進行可選鏈?zhǔn)秸{(diào)用,在方法的圓括號后面加上問號即可:

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

注意
在上面的例子中,在方法的圓括號后面加上問號是因為你要在 buildingIdentifier() 方法的可選返回值上進行可選鏈?zhǔn)秸{(diào)用,而不是方法本身。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 本章將會介紹 自動引用計數(shù)的工作機制自動引用計數(shù)實踐類實例之間的循環(huán)強引用解決實例之間的循環(huán)強引用閉包引起的循環(huán)強...
    寒橋閱讀 1,032評論 0 0
  • 126.析構(gòu)器 在一個類實例銷毀前,一個析構(gòu)器會立即調(diào)用。使用deinit 關(guān)鍵字來表示析構(gòu)器, 跟構(gòu)造器寫法類似...
    無灃閱讀 906評論 0 4
  • 官方文檔鏈接 原文鏈接 可選鏈(Optional Chaining)是為了在一個可能當(dāng)前值為nil的optiona...
    hrscy閱讀 411評論 0 3
  • 可選鏈(Optional Chaining)是一種可以請求和調(diào)用屬性、方法和子腳本的過程,用于請求或調(diào)用的目標(biāo)可能...
    零度_不結(jié)冰閱讀 367評論 0 0
  • runtime這個運行機制,它基本上是用C和匯編寫的一個庫 網(wǎng)上文章很多,引用幾篇好的,寫下關(guān)鍵詞,方便自己看,以...
    記憶岸閱讀 596評論 0 0

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