Swift學習筆記8--Optional Chaining

Optional Chaining

自判斷連接(Optional Chaining)是一種能夠請求和調用屬性、方法及子腳本的過程,它的自判斷性體現於請求或調用的目標當前可能爲空(  nil )。若是自判斷的目標有值,那麼調用就會成功;相反,若是選擇的目標爲空(  nil ),則這種調用將返回空(  nil )。屢次請求或調用能夠被連接在一塊兒造成一個鏈,若是任何一個節點爲空(  nil )將致使整個鏈失效。 php

 

筆記:   Swift 的自判斷鏈和Objective-C中的消息爲空有些相像,可是  Swift 可使用在任意類型中,而且失敗與否能夠被檢測到。 git

 

自判斷連接可替代強制拆包 

經過在想調用的屬性、方法、或子腳本的自判斷值(  optional value )(非空)後面放一個問號,能夠定義一個自判斷連接。這一點很像在自判斷值後面放一個聲明符號來強制拆得其封包內的值。他們的主要的區別在於當自判斷值爲空時自判斷連接即刻失敗,然而通常的強制拆包將會引起運行時錯誤。 github

爲了反映自判斷連接能夠調用空(  nil ),不論你調用的屬性、方法、子腳本等返回的值是否是自判斷值,它的返回結果都是一個自判斷值。你能夠利用這個返回值來檢測你的自判斷連接是否調用成功,有返回值即成功,返回nil則失敗。 數組

調用自判斷連接的返回結果與本來的返回結果具備相同的類型,可是本來的返回結果被包裝成了一個自判斷值,當自判斷連接調用成功時,一個應該返回  Int 的屬性將會返回  Int? 。 ide

下面幾段代碼將解釋自判斷連接和強制拆包的不一樣。函數

首先定義兩個類  Person 和  Residence 。 ui

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

Residence 具備一個  Int 類型的  numberOfRooms ,其值爲1。  Person 具備一個自判斷  residence 屬性,它的類型是  Residence? 。 this

若是你建立一個新的Person實例,它的residence屬性因爲是被定義爲自判斷型的,此屬性將默認初始化爲空:spa

let john = Person()

若是你想使用聲明符!強制拆包得到這我的  residence 屬性  numberOfRooms屬性值,將會引起運行時錯誤,由於這時沒有能夠供拆包的  residence 值。 code

let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error」 //將致使運行時錯誤

當  john.residence 不是  nil 時,會運行經過,且會將  roomCount  設置爲一個  int 類型的合理值。然而,如上所述,當  residence 爲空時,這個代碼將會致使運行時錯誤。 

自判斷連接提供了一種另外一種得到numberOfRooms的方法。利用自判斷連接,使用問號來代替原來   的位置: 

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來連接自判斷  residence? 屬性,若是  residence 存在則取回 numberOfRooms 的值。 

由於這種嘗試得到  numberOfRooms 的操做有可能失敗,自判斷連接會返回 Int? 類型值,或者稱做「自判斷  Int 」。當  residence 是空的時候(上例),選擇  Int 將會爲空,所以會出先沒法訪問  numberOfRooms 的狀況。 

要注意的是,即便numberOfRooms是非自判斷  Int (  Int? )時這一點也成立。只要是經過自判斷連接的請求就意味着最後  numberOfRooms 老是返回一個 Int? 而不是  Int 。 

你能夠本身定義一個  Residence 實例給  john.residence ,這樣它就再也不爲空了: 

john.residence = Residence()

john.residence  如今有了實際存在的實例而不是nil了。若是你想使用和前面同樣的自判斷連接來得到  numberOfRoooms ,它將返回一個包含默認值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)"。

爲自判斷連接定義模型類 

你可使用自判斷連接來多層調用屬性,方法,和子腳本。這讓你能夠利用它們之間的複雜模型來獲取更底層的屬性,並檢查是否能夠成功獲取此類底層屬性。

後面的代碼定義了四個將在後面使用的模型類,其中包括多層自判斷連接。這些類是由上面的  Person 和  Residence 模型經過添加一個  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 {   return rooms[i]  }  func printNumberOfRooms() {   println("The number of rooms is \(numberOfRooms)")  }  var address: Address? }

由於  Residence 存儲了一個  Room 實例的數組,它的  numberOfRooms 屬性值不是一個固定的存儲值,而是經過計算而來的。  numberOfRooms 屬性值是由返回 rooms 數組的  count 屬性值獲得的。 

爲了能快速訪問  rooms 數組,  Residence 定義了一個只讀的子腳本,經過插入數組的元素角標就能夠成功調用。若是該角標存在,子腳本則將該元素返回。 

Residence 中也提供了一個  printNumberOfRooms 的方法,即簡單的打印房間個數。 

最後,  Residence 定義了一個自判斷屬性叫  address (  address? )。 Address 類的屬性將在後面定義。 用於  rooms 數組的  Room 類是一個很簡單的類,它只有一個  name 屬性和一個設定  room 名的初始化器。 

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 {    return buildingName   } else if buildingNumber {    return buildingNumber   } else {    return nil   }  } }

Address 類還提供了一個  buildingIdentifier 的方法,它的返回值類型爲 String? 。這個方法檢查  buildingName 和  buildingNumber 的屬性,若是 buildingName 有值則將其返回,或者若是  buildingNumber 有值則將其返回,再或若是沒有一個屬性有值,返回空。 

經過自判斷連接調用屬性 

正如上面「   自判斷連接可替代強制拆包 」中所述,你能夠利用自判斷連接的自判斷值獲取屬性,而且檢查屬性是否獲取成功。然而,你不能使用自判斷連接爲屬性賦值。 

使用上述定義的類來建立一我的實例,並再次嘗試後去它的  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 是空,因此這個自判斷連接和以前同樣失敗了,可是沒有運行時錯誤。 

經過自判斷連接調用方法 

你可使用自判斷連接的來調用自判斷值的方法並檢查方法調用是否成功。即便這個方法沒有返回值,你依然可使用自判斷連接來達成這一目的。

Residence的printNumberOfRooms方法會打印numberOfRooms的當前值。方法以下:

func printNumberOfRooms(){
    println(「The number of rooms is \(numberOfRooms)」)
}

這個方法沒有返回值。可是,沒有返回值類型的函數和方法有一個隱式的返回值類型  Void (參見Function Without Return Values)。 

若是你利用自判斷連接調用此方法,這個方法的返回值類型將是  Void? ,而不是 Void ,由於當經過自判斷連接調用方法時返回值老是自判斷類型(optional type)。,即便是這個方法本是沒有定義返回值,你也可使用  if 語句來檢查是否能成功調用  printNumberOfRooms 方法:若是方法經過自判斷連接調用成功, printNumberOfRooms 的隱式返回值將會是  Void ,若是沒有成功,將返回  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."。

使用自判斷連接調用子腳本 

你可使用自判斷連接來嘗試從子腳本獲取值並檢查子腳本的調用是否成功,然而,你不能經過自判斷連接來設置子代碼。

 

注意: 當你使用自判斷連接來獲取子腳本的時候,你應該將問號放在子腳本括號的前面而不是後面。自判斷連接的問號通常直接跟在自判斷表達語句的後面。

 

下面這個例子用在  Residence 類中定義的子腳原本獲取  john.residence 數組中第一個房間的名字。由於  john.residence 如今是  nil ,子腳本的調用失敗了。 

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

在子代碼調用中自判斷連接的問號直接跟在  john.residence 的後面,在子腳本括號的前面,由於  john.residence 是自判斷連接試圖得到的自判斷值。 

若是你建立一個  Residence 實例給  john.residence ,且在他的  rooms 數組中有一個或多個  Room 實例,那麼你可使用自判斷連接經過  Residence 子腳原本獲取在  rooms 數組中的實例了: 

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

你能夠將多層自判斷連接鏈接在一塊兒,能夠掘取模型內更下層的屬性方法和子腳本。然而多層自判斷連接不能再添加比已經返回的自判斷值更多的層。 也就是說:

若是你試圖得到的類型不是自判斷類型,因爲使用了自判斷連接它將變成自判斷類型。 若是你試圖得到的類型已是自判斷類型,因爲自判斷連接它也不會提升自判斷性。

所以:

若是你試圖經過自判斷連接得到  Int 值,不論使用了多少層連接返回的老是 Int? 。 類似的,若是你試圖經過自判斷連接得到  Int? 值,不論使用了多少層連接返回的老是  Int? 。 

下面的例子試圖獲取  john 的  residence 屬性裏的  address 的  street 屬性。這裏使用了兩層自判斷連接來聯繫  residence 和  address 屬性,他們二者都是自判斷類型: 

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 的值如今包含一個  Residence 實例,然而 john.residence.address 如今是  nil ,所以 john.residence?.address?.street 調用失敗。 

從上面的例子發現,你試圖得到  street 屬性值。這個屬性的類型是  String?。所以儘管在自判斷類型屬性前使用了兩層自判斷連接, john.residence?.address?.street 的返回值類型也是  String? 。 

若是你爲  Address 設定一個實例來做爲  john.residence.address 的值,併爲  address 的  street 屬性設定一個實際值,你能夠經過多層自判斷連接來獲得這個屬性值。 

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

值得注意的是,「  ! 」符的在定義  address 實例時的使用( john.residence.address )。  john.residence 屬性是一個自判斷類型,所以你須要在它獲取  address 屬性以前使用   拆包以得到它的實際值。 

連接自判斷返回值的方法 

前面的例子解釋瞭如何經過自判斷連接來得到自判斷類型屬性值。你也能夠經過調用返回自判斷類型值的方法並按需連接方法的返回值。

下面的例子經過自判斷連接調用了  Address 類中的  buildingIdentifier  方法。這個方法的返回值類型是  String? 。如上所述,這個方法在自判斷連接調用後最終的返回值類型依然是  String? : 

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

若是你還想進一步對方法返回值執行自判斷連接,將自判斷連接問號符放在方法括號的後面:

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

 

注意: 在上面的例子中,你將自判斷連接問號符放在括號後面是由於你想要連接的自判斷值是  buildingIdentifier 方法的返回值,不是  buildingIdentifier方法自己。

相關文章
相關標籤/搜索