【Swift學習】Swift編程之旅---可選鏈(二十一)

  可選鏈Optional Chaining是一種能夠在當前值可能爲nil的可選值上請求和調用屬性、方法及下標的方法。若是可選值有值,那麼調用就會成功;若是可選值是nil,那麼調用將返回nil。多個調用能夠鏈接在一塊兒造成一個調用鏈,若是其中任何一個節點爲nil,整個調用鏈都會失敗,即返回nilswift

  

  經過在想調用的屬性、方法、或下標的可選值(optional value)後面放一個問號(?),能夠定義一個可選鏈。這一點很像在可選值後面放一個歎號(!)來強制展開它的值。它們的主要區別在於當可選值爲空時可選鏈式調用只會調用失敗,然而強制展開將會觸發運行時錯誤。數組

爲了反映可選鏈式調用能夠在空值(nil)上調用的事實,不論這個調用的屬性、方法及下標返回的值是否是可選值,它的返回結果都是一個可選值。你能夠利用這個返回值來判斷你的可選鏈式調用是否調用成功,若是調用有返回值則說明調用成功,返回nil則說明調用失敗。app

特別地,可選鏈式調用的返回結果與本來的返回結果具備相同的類型,可是被包裝成了一個可選值。例如,使用可選鏈式調用訪問屬性,當可選鏈式調用成功時,若是屬性本來的返回結果是Int類型,則會變爲Int?類型。ide

下面幾段代碼將解釋可選鏈式調用和強制展開的不一樣。函數

首先定義兩個類PersonResidenceui

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

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

若是建立一個新的Person實例,由於它的residence屬性是可選的,john屬性將初始化爲nil3d

let john = Person()

若是使用歎號(!)強制展開得到這個johnresidence屬性中的numberOfRooms值,會觸發運行時錯誤,由於這時residence沒有能夠展開的值:code

let roomCount = john.residence!.numberOfRooms
// 這會引起運行時錯誤

 

john.residence爲非nil值的時候,上面的調用會成功,而且把roomCount設置爲Int類型的房間數量。正如上面提到的,當residencenil的時候上面這段代碼會觸發運行時錯誤。blog

可選鏈式調用提供了另外一種訪問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

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還定義了一個可選屬性address,其類型爲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

 

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

能夠經過可選鏈式調用在一個可選值上訪問它的屬性,並判斷訪問是否成功。

下面的代碼建立了一個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

上面代碼中的賦值過程是可選鏈式調用的一部分,這意味着可選鏈式調用失敗時,等號右側的代碼不會被執行。對於上面的代碼來講,很難驗證這一點,由於像這樣賦值一個常量沒有任何反作用。下面的代碼完成了一樣的事情,可是它使用一個函數來建立Address實例,而後將該實例返回用於賦值。該函數會在返回前打印「Function was called」,這使你能驗證等號右側的代碼是否被執行。

func createAddress() -> Address {
    print("Function was called.")

    let someAddress = Address()
    someAddress.buildingNumber = "29"
    someAddress.street = "Acacia Road"

    return someAddress
}
john.residence?.address = createAddress()

 

沒有任何打印消息,能夠看出createAddress()函數並未被執行。

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

能夠經過可選鏈式調用來調用方法,並判斷是否調用成功,即便這個方法沒有返回值。

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.」

 


一樣的,能夠據此判斷經過可選鏈式調用爲屬性賦值是否成功。在上面
,咱們嘗試給中的屬性賦值,即便。經過可選鏈式調用給屬性賦值會返回,經過判斷返回值是否爲就能夠知道賦值是否成功:john.residenceaddressresidencenilVoid?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

若是你建立一個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 中Dictionary類型的鍵的下標,能夠在下標的結尾括號後面放一個問號來在其可選返回值上進行可選鏈式調用:

var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0]++
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屬性賦值將會成功。

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

上面的例子展現瞭如何在一個可選值上經過可選鏈式調用來獲取它的屬性值。咱們還能夠在一個可選值上經過可選鏈式調用來調用方法,而且能夠根據須要繼續在方法的可選返回值上進行可選鏈式調用。

在下面的例子中,經過可選鏈式調用來調用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()方法的可選返回值上進行可選鏈式調用,而不是方法自己。

相關文章
相關標籤/搜索