swift基礎—可選鏈式調用

可選鏈式調用 是一種可以在當前值可能為 nil 的可選值上請求和調用屬性璧亚、方法及下標的方法。如果可選值有值,那么調用就會成功;如果可選值是 nil ,那么調用將返回 nil 。多個調用可以連接在一起形成一個調用 鏈铅忿,如果其中任何一個節(jié)點為 nil 剪决,整個調用鏈都會失敗,即返回 nil 檀训。

注意
Swift 的可選鏈式調用和 Objective-C 中向 nil 發(fā)送消息有些相像柑潦,但是 Swift 的可選鏈式調用可以應用于任意類型,并且能檢查調用是否成功峻凫。

使用可選鏈式調用代替強制展開

通過在想調用的屬性渗鬼、方法、或下標的可選值后面放一個問號( ? )荧琼,可以定義一個可選鏈譬胎。這一點很像在可選 值后面放一個嘆號( ! )來強制展開它的值。它們的主要區(qū)別在于當可選值為空時可選鏈式調用只會調用失 敗命锄,然而強制展開將會觸發(fā)運行時錯誤堰乔。

為了反映可選鏈式調用可以在空值( nil )上調用的事實,不論這個調用的屬性脐恩、方法及下標返回的值是不是可選值镐侯,它的返回結果都是一個可選值。你可以利用這個返回值來判斷你的可選鏈式調用是否調用成功驶冒,如果調用 有返回值則說明調用成功苟翻,返回 nil 則說明調用失敗。

特別地骗污,可選鏈式調用的返回結果與原本的返回結果具有相同的類型崇猫,但是被包裝成了一個可選值。例如需忿,使用 可選鏈式調用訪問屬性邓尤,當可選鏈式調用成功時,如果屬性原本的返回結果是 Int 類型,則會變?yōu)?Int? 類型汞扎。

下面幾段代碼將解釋可選鏈式調用和強制展開的不同季稳。

首先定義兩個類 PersonResidence :

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

Residence 有一個 Int 類型的屬性 numberOfRooms ,其默認值為 1 澈魄。 Person 具有一個可選的 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 值的時候,上面的調用會成功鲫构,并且把 roomCount 設置為 Int 類型的房間數量浓恶。正如上 面提到的,當 residencenil 的時候上面這段代碼會觸發(fā)運行時錯誤结笨。

可選鏈式調用提供了另一種訪問 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 有可能失敗伐憾,可選鏈式調用會返回 Int? 類型,或稱為“可選的 *Int *”赫模。如上例所示树肃,當 residencenil 的時候,可選的 Int 將會為 nil 瀑罗,表明無法訪問 numberOfRooms 胸嘴。訪問成功時,可選的 Int 值會通過可選綁定展開斩祭,并賦值給非可選類型的 roomCount 常量筛谚。

要注意的是,即使 numberOfRooms 是非可選的 Int 時停忿,這一點也成立驾讲。只要使用可選鏈式調用就意味著 numberOfRooms 會返回一個 Int? 而不是 Int

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

john.residence = Residence()

john.residence 現在包含一個實際的 Residence 實例吮铭,而不再是 nil 。如果你試圖使用先前的可選鏈式調用訪問 numberOfRooms 颅停,它現在將返回值為 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).”

為可選鏈式調用定義模型類

通過使用可選鏈式調用可以調用多層屬性谓晌、方法和下標。這樣可以在復雜的模型中向下訪問各種子屬性癞揉,并且判斷能否訪問子屬性的屬性纸肉、方法或下標溺欧。

下面這段代碼定義了四個模型類,這些例子包括多層可選鏈式調用柏肪。為了方便說明姐刁,在 PersonResidence 的基礎上增加了 Room 類和 Address 類,以及相關的屬性烦味、方法以及下標聂使。

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

class Person {
    var residence: Residence?
}

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

現在 Residence 有了一個存儲 Room 實例的數組柏靶,numberOfRooms 屬性被實現為計算型屬性,而不是存儲型屬性溃论。 numberOfRooms 屬性簡單地返回 rooms 數組的 count 屬性的值屎蜓。

Residence 還提供了訪問 rooms 數組的快捷方式,即提供可讀寫的下標來訪問 rooms 數組中指定位置的元素钥勋。此外炬转,* Residence* 還提供了 printNumberOfRooms 方法,這個方法的作用是打印 numberOfRooms 的值笔诵。

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

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

Room 類是一個簡單類,其實例被存儲在 rooms 數組中沐旨。該類只包含一個屬性 name森逮,以及一個用于將該屬性設置為適當的房間名的初始化函數:

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粮呢。

通過可選鏈式調用訪問屬性

可以通過可選鏈式調用在一個可選值上訪問它的屬性婿失,并判斷訪問是否成功钞艇。

下面的代碼創(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 哩照,所以這個可選鏈式調用依舊會像先前一樣失敗。

還可以通過可選鏈式調用來設置屬性值:

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

在這個例子中舟误,通過 john.residence 來設定 address 屬性也會失敗葡秒,因為 john.residence 當前為 nil

上面代碼中的賦值過程是可選鏈式調用的一部分嵌溢,這意味著可選鏈式調用失敗時眯牧,等號右側的代碼不會被執(zhí)行。對于上面的代碼來說赖草,很難驗證這一點学少,因為像這樣賦值一個常量沒有任何副作用。下面的代碼完成了同樣 的事情秧骑,但是它使用一個函數來創(chuàng)建 Address 實例版确,然后將該實例返回用于賦值。該函數會在返回前打印“Funct ion was called”乎折,這使你能驗證等號右側的代碼是否被執(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() 函數并未被執(zhí)行骂澄。

通過可選鏈式調用調用方法

可以通過可選鏈式調用來調用方法吓蘑,并判斷是否調用成功,即使這個方法沒有返回值坟冲。 Residence 類中的 printNumberOfRooms() 方法打印當前的 numberOfRooms 值磨镶,如下所示:

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

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

如果在可選值上通過可選鏈式調用來調用這個方法脐嫂,該方法的返回類型會是 Void? ,而不是 Void 紊遵,因為通過可選 鏈式調用得到的返回值都是可選的雹锣。這樣我們就可以使用 if 語句來判斷能否成功調用 printNumberOfRooms() 方法,即使方法本身沒有定義返回值癞蚕。通過判斷返回值是否為 nil 可以判斷調用是否成功:

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? 桦山,通過判斷返回值是否為 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.”

通過可選鏈式調用訪問下標

通過可選鏈式調用攒射,我們可以在一個可選值上訪問下標醋旦,并且判斷下標調用是否成功。

注意
通過可選鏈式調用訪問可選值的下標時会放,應該將問號放在下標方括號的前面而不是后面饲齐。可選鏈式調用的問號一般直接跟在可選表達式的后面咧最。

下面這個例子用下標訪問 john.residence 屬性存儲的 Residence 實例的 rooms 數組中的第一個房間的名稱捂人,因為 john.residencenil ,所以下標調用失敗了:

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 是可選值捣鲸。 類似的瑟匆,可以通過下標,用可選鏈式調用來賦值:

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

這次賦值同樣會失敗栽惶,因為 residence 目前是 nil愁溜。
如果你創(chuàng)建一個 Residence 實例,并為其 rooms 數組添加一些 Room 實例外厂,然后將 Residence 實例賦值給 john.residence冕象,那就可以通過可選鏈和下標來訪問數組中的元素:

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.”
訪問可選類型的下標

如果下標返回可選類型值,比如 Swift 中 類型的鍵的下標汁蝶,可以在下標的結尾括號后面放一個問號 來在其可選返回值上進行可選鏈式調用:

var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
// "Dave" 數組現在是 [91, 82, 84]渐扮,"Bev" 數組現在是 [80, 94, 81]

上面的例子中定義了一個 testScores 數組,包含了兩個鍵值對穿仪,把 String 類型的鍵映射到一個 Int 值得數組席爽。這個例子用可選鏈式調用把 ”Dave“ 數組中的第一個元素設為 91意荤,把 ”Bev“ 數組的第一個元素 +1啊片,然后嘗試把 ”Brian“ 數組中的第一個元素設為 72。前兩個調用成功玖像,因為 testScores 字典中包含 ”Dave“”Bev“ 這兩個鍵紫谷。但是 testScores 字典中沒有 ”Brian“ 這個鍵,所以第三個調用失敗捐寥。

連接多層可選鏈式調用

可以通過連接多個可選鏈式調用在更深的模型層級中訪問屬性笤昨、方法以及下標。然而握恳,多層可選鏈式調用不會增加返回值的可選層級瞒窒。

也就是說:

  • 如果你訪問的值不是可選的,可選鏈式調用將會返回可選值乡洼。
  • 如果你訪問的值就是可選的崇裁,可選鏈式調用不會讓可選返回值變得“更可選”匕坯。

因此:

  • 通過可選鏈式調用訪問一個 Int 值,將會返回 Int? 拔稳,無論使用了多少層可選鏈式調用葛峻。
  • 類似的,通過可選鏈式調用訪問 Int? 值巴比,依舊會返回 Int? 值术奖,并不會返回 Int??

下面的例子嘗試訪問 john 中的 residence 屬性中的 address 屬性中的 street 屬性轻绞。這里使用了兩層可選鏈式調用采记, 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 現在包含一個有效的 Residence 實例。然而铲球, john.residence.address 的值當前為 nil 挺庞。因此,調用 john.residence?.address?.street 會失敗稼病。

需要注意的是选侨,上面的例子中, street 的屬性為 String? 然走。 john.residence?.address?.street 的返回值也依然是 String? 援制,即使已經使用了兩層可選鏈式調用。

如果為 john.residence.address 賦值一個 Address 實例芍瑞,并且為 address 中的 street 屬性設置一個有效值晨仑,我們就能過通過可選鏈式調用來訪問 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 屬性賦值將會成功洪己。

在方法的可選返回值上進行可選鏈式調用

上面的例子展示了如何在一個可選值上通過可選鏈式調用來獲取它的屬性值。我們還可以在一個可選值上通過可選鏈式調用來調用方法竟贯,并且可以根據需要繼續(xù)在方法的可選返回值上進行可選鏈式調用答捕。

在下面的例子中,通過可選鏈式調用來調用 AddressbuildingIdentifier() 方法屑那。這個方法返回 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".”

注意
在上面的例子中,在方法的圓括號后面加上問號是因為你要在 buildingIdentifier() 方法的可選返回值上進行可選鏈式調用蜘欲,而不是方法本身益眉。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市姥份,隨后出現的幾起案子郭脂,更是在濱河造成了極大的恐慌空繁,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件朱庆,死亡現場離奇詭異盛泡,居然都是意外死亡,警方通過查閱死者的電腦和手機娱颊,發(fā)現死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門傲诵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人箱硕,你說我怎么就攤上這事拴竹。” “怎么了剧罩?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵栓拜,是天一觀的道長。 經常有香客問我惠昔,道長幕与,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任镇防,我火速辦了婚禮啦鸣,結果婚禮上,老公的妹妹穿的比我還像新娘来氧。我一直安慰自己诫给,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布啦扬。 她就那樣靜靜地躺著中狂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扑毡。 梳的紋絲不亂的頭發(fā)上胃榕,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音僚楞,去河邊找鬼勤晚。 笑死枉层,一個胖子當著我的面吹牛泉褐,可吹牛的內容都是我干的。 我是一名探鬼主播鸟蜡,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼膜赃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了揉忘?” 一聲冷哼從身側響起跳座,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤端铛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后疲眷,有當地人在樹林里發(fā)現了一具尸體禾蚕,經...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年狂丝,在試婚紗的時候發(fā)現自己被綠了换淆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡几颜,死狀恐怖倍试,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情蛋哭,我是刑警寧澤县习,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站谆趾,受9級特大地震影響躁愿,放射性物質發(fā)生泄漏。R本人自食惡果不足惜沪蓬,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一攘已、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧怜跑,春花似錦样勃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至植锉,卻和暖如春辫樱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俊庇。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工狮暑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辉饱。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓搬男,卻偏偏與公主長得像,于是被迫代替她去往敵國和親彭沼。 傳聞我的和親對象是個殘疾皇子缔逛,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內容