前言
其實可空鍊式調用并沒有它的名字那麼陌生,簡而言之就是對于可選類型
Optional
(使用問号 ? 字尾表示)和強制展開類型(使用感歎号 ! 字尾表示)的使用方法。在平常寫代碼的時候隻是大概的清楚哪些值是可空的,哪些值是肯定存在的,但是并沒有深究可空的調用有何優點,有何使用時需要注意的事項。至少前面寫不少示例代碼的時候,我也是大都按照自己的想法去定義的。這一小節就是對可空調用的較長的描述,至于鍊式,就是多層調用,大概就是
school.classroom.student.name
這樣的調用方法。
小節知識點,看着挺多挺長,但是如果把
可空鍊式調用
這幾個忽略掉,就會發現都是我們已經非常熟悉的基本用法:
- 使用可空鍊式調用來強制展開
- 使用可空鍊式調用定義模型類
- 通過可空鍊式調用通路屬性
- 通過可空鍊式調用來調用方法
- 通過可控調用方法來通路下标
- 多層連結
- 對傳回可空值得函數進行連結
可空鍊式調用是一種可以請求和調用屬性、方法以及下标的過程,它的可空性展現在請求和調用的目前目前可能為空
nil
。如果可空的目标有值,那麼就會調用成功;如果選擇的目标為空,那麼這種調用将會傳回空
nil
。多個連續的調用可以被連結到一起形成一個調用鍊,如果其中任何一個節點為空
nil
,将會導緻整個調用失敗。
Swift的可空鍊式調用和Objective-C中的消息為空有點像,但是Swift可以使用在任何類型中,并且能夠檢查調用是否成功。一個可空調用相當于Objective-C中
if (message == nil) { } else { }
,隻是Objective-C中隻能用在對象或者變量中,遠沒有Swift中功能強大。
分條詳述
-
使用可空鍊式調用來強制展開
下面這幾句話讀起來有點拗口,多了解就好:通過在想調用非空的屬性、方法、或下标的可空值
後面放置一個問号optional value
,可以定義一個可空鍊。這一點很像在一個可空值後面放一個感歎号?
!
來強制展開其中值。它們的主要差別在于當可空值為空時可空鍊式隻是調用失敗,然而強制揭開将會出發運作錯誤。
簡單來說,就是當調用一個屬性或方法時,我們希望它是非空的,是有值的,但是這個我們并不能确定有值,就會用到可空鍊。譬如擷取網絡資料,然後展示,我們當然希望是擷取到資料的,但是有時候因為網絡、背景的問題,資料為空,此時如果用可空鍊來處理,就會簡單些。
需要強調的是,可空鍊式調用的傳回結果與原本的傳回結果具有相同的類型,但是被包裝成了一個可空類型值。例如,當可空鍊式調用成功時,一個
類型的結果将會傳回int
int?
類型。
下面幾行示例代碼展示可空鍊式調用和強制展開的一些不同點:
其實可空鍊式調用最常用的一個場景就是// 戶主資訊 class Person { var residence: Residence? } // 房子資訊,一共有多少房間 class Residence { var numberOfRooms = // 預設隻有一個房間 } // 建立一個新的 Person 執行個體 let personOne = Person() // 由于 residence 屬性為空,是以下面如果将 ? 換成 ! 強制解開,會報錯。另外,這裡代碼補全就是帶 ? 的。 print(personOne.residence?.numberOfRooms) // 輸出: nil // 初始化 residence 屬性 personOne.residence = Residence() // 此時 residence 已經有了初始值,但是膽碼補全的時候還是用了 ? ,此時如果将 ? 改為 ! , 并不會報錯,因為是對一個已經确定的非空值強制展開 print(personOne.residence!.numberOfRooms) // 輸出: 1 print(personOne.residence?.numberOfRooms) // 輸出: Optional(1) , 表示可空的
語句的判斷,當可空鍊式調用傳回是if
時執行操作A,不是nil
時執行操作B,如下:nil
// 可空鍊式調用用于 if 語句判斷 if let person = personOne.residence?.numberOfRooms { // 此時不為空 personOne.residence?.numberOfRooms = 1 } else { // 此時為空 personOne.residence?.numberOfRooms = nil }
-
通過可空鍊式調用定義模型類
通過使用可空鍊式調用可以調用多層屬性、方法和下标。這樣可以通過各種模型向下通路各種子屬性。并且可以根據是否為空判斷是否可以通路子屬性的屬性、方法或下标。
下面示例代碼定義了四個模型類,這些例子包括多層可空鍊式調用。同時,下面幾個知識點也會以這4個類為基礎說明:
// 戶主資訊 class Person { // 房子資訊,可能沒有房子,是以可選比較合适 var residence: Residence? } // 房子資訊 class Residence { // 房間數組 var rooms = [Room]() // 一共有多少房間 var numberOfRooms: Int { return rooms.count } // 房子位址 var address: Address? // 下标方法,選擇第幾間房 subscript(index: Int) -> Room { get { return rooms[index] // 取值 } set { rooms[index] = newValue // 設定值 } } // 列印房間數量 func printNumberOfRooms() { print("number of rooms : \(rooms.count)") } } // 房間資訊 class Room { // 房間名字 let name: String init(roomName name: String) { self.name = name } } // 位址資訊 class Address { var buildingName: String? // 建築名字 var buildingNumber: String? // 建築編号 var buildingStreet: String? // 建築所在街道名稱 // 方法,擷取建築物的唯一标示符,因為可能為空,是以傳回的String類型是可選的 func buildingIdentifier() -> String? { if let name = buildingName { return name } else if let number = buildingNumber { return number } else { return nil } } }
-
通過可空鍊式調用通路屬性
就是我們平常調用屬性、方法的那一套,先建立一個執行個體,然後去調用。其實,很多時候執行個體、屬性等是可空的還是強制展開的,系統都會幫我們去辨識的,我們需要去了解為什麼會出現補全的情況,有時候需要我們手動去決定可選還是強制展開。
下面是代碼示例,主要注意點都寫在注釋裡了:
// 建立一個戶主執行個體 let john = Person() if let roomCount = john.residence?.numberOfRooms { // 可空鍊式調用成功,這裡不會執行,因為 john.residence?.numberOfRooms 此時是 nil } else { // 可空鍊式調用失敗,會執行這裡的代碼 } // 通過可空鍊式調用設定屬性值 john.residence = Residence() // 首先執行個體化屬性 john.residence john.residence?.address = Address() // 執行個體化位址 // 此時,address 已經存在,才能修改它的屬性值 john.residence?.address?.buildingName = "北京" print(john.residence?.address?.buildingIdentifier()) // 輸出: Optional("北京") print(john.residence!.address!.buildingIdentifier()!) // 輸出:北京 /*************** 上面的代碼必須每一個屬性都是強制展開的,任何一個可空時,都會輸出 Optional("北京") ***************/
-
通過可空鍊式調用來調用方法
可以通過可空鍊式調用來調用方法,并判斷是否調用成功。即使這個方法沒有傳回值。其實沒有傳回值的方法隐式的傳回
類型。這意味着沒有傳回值的方法也會傳回Void
()
或者空的元組。
如果在可空值上通過可空鍊式調用來調用沒有傳回值的方法,那麼傳回的類型将會是可選空類型
,而不是Void?
。因為空過可空鍊式調用得到的傳回值都是可空Void
nil
的。
下面幾行示例代碼結合上面的定義的資料類型,展示出可空傳回值可傳回空元組的方法的差別:
同樣的,可以判斷通過可空鍊式調用來給屬性指派是否成功。首先,需要知道的是無法給一個// 不在可空鍊式調用上調用沒有傳回值的方法,預設是 Void 類型的傳回,傳回一個空的元組 let residence = Residence() print(residence.printNumberOfRooms()) // 輸出:number of rooms : 0 () // 在可空鍊式調用上調用沒有傳回值的方法,傳回的是一個可選的空類型 Void? ,因為隻有可選類型才能為 nil let tom = Person() print(tom.residence?.printNumberOfRooms()) // 輸出: nil
的屬性指派:nil
作為對比,對比下面這段代碼和上面的差別:let jack = Person() let jackResidence = Residence() jack.residence? = jackResidence // 這句話并沒有給 jack.residence? 指派 // 此時 jack.residence? == nil if (jack.residence? = jackResidence) != nil{ print("指派成功") } else { print("指派失敗") } // 輸出: 指派失敗
let jack = Person() let jackResidence = Residence() jack.residence = jackResidence // 這句話給 jack.residence 了記憶體位址 // 此時 jack.residence 已經被執行個體了 if (jack.residence? = jackResidence) != nil{ print("指派成功") } else { print("指派失敗") } // 輸出: 指派成功
-
通過可空鍊式調用來通路下标
通過可空鍊式調用,可以用下标來對空值進行讀取或寫入,并且判斷下标是否調用成功。注意:當通過可空鍊式調用通路可空值得下标的時候,應該将問号放在下标方括号的前面而不是後面。可空鍊式調用的問号一般直接跟在可控表達式的後面。問号跟在哪個表達式的後面表示哪個表達式是可控的。
其實通路下标和通路屬性、方法并沒有什麼不同,可用于
語句判斷,不能給一個值為if
的屬性指派:nil
如果下标傳回可空類型值,比如 Swift 中// 通路下标 let rose = Person() if let firstRoomName = rose.residence?.rooms].name { print("因為 rose.residence? 是可空值,并且此時值為 nil, 是以這句話并不會被列印") // 不執行 } else { print("被列印的話") // 執行 } // 給一個值為 nil 的屬性指派 rose.residence?] = Room(roomName: "Living Room") // 指派失敗,此時 rose.residence?[0] == nil rose.residence = Residence() // 建立執行個體 rose.residence?.rooms.append(Room(roomName: "FirstRoom")) // 添加值 // 再次嘗試通路下标 if let firstRoomNameAgain = rose.residence?.rooms].name { print("因為 rose.residence? 是可空值,并且此時值為 nil, 是以這句話并不會被列印") // 執行 } else { print("被列印的話") // 不執行 }
的Dictionary
下标。可以在下标的閉合括号後面放一個問号來連結下标的可空傳回值:key
var testScrore = ["john": [, , ], "jack": [, , ]] testScrore["john"]?[] = testScrore["jack"]?[] = testScrore["Hehe"]?[] = // 這句并不會執行,去掉問号會報錯 print(testScrore) // 輸出: ["jack": [33, 112, 130], "john": [140, 91, 78]]
-
多層連結
可以通過多個屬性的可空鍊式調用來向下通路屬性,方法以及下标。但是多層可空鍊式調用不會添加傳回值的可空性,也就是說:
- 如果通路的值不是可空的,通過可空鍊式調用将會傳回可空值。
- 如果通路值得值是已經可空的,通過可空鍊式調用并不會變得更空。
- 通過可空鍊式調用通路一個
值,将會傳回Int
,不論進行了多少次可空鍊式調用。Int?
- 類似的,通過可空鍊式調用通路
值,并不會變得更空。Int?
-
對傳回可空的函數進行連結
上面的例子說明了如何通過可空鍊式調用來擷取可控的屬性值。其實還可以通過可空鍊式調用來調用傳回值是可空值的方法,并且可以繼續對空值進行連結:
// 對傳回值可空的方法可以繼續往下連結 let liLei = Person() // 注意,在方法的圓括号後面加問号并不是指方法可空,而是指方法的傳回值可空。 if let beginWithPrefix = liLei.residence?.address?.buildingIdentifier()?.hasPrefix("China") { } else { }
結束語
這一小節其實是有點唬人的,它隻是把我們平常一直用但是沒有太在意的事情說了一遍,是以學起來也是挺輕松的。
我人很懶的,一直學習什麼的也是辦不到。這兩天抽空找了一部動漫看看,絕園的暴風雨,整部動漫圍繞莎士比亞的哈姆雷特、暴風雨兩個複仇劇展開,顯然這麼高大上的書我是沒讀過的,但是動漫本身相當不錯,角色都很有特點。喜歡看動漫的可以補一下。
