天天看點

Swift學習筆記(十四)可選鍊

可選鍊

可選鍊(Optional Chaining)是一種可以請求和調用屬性、方法及子腳本的過程,它的自判斷性展現于請求或調用的目标目前可能為空(nil)。如果自判斷的目标有值,那麼調用就會成功;相反,如果選擇的目标為空(nil),則這種調用将傳回空(nil)。多次請求或調用可以被連結在一起形成一個鍊,如果任何一個節點為空(nil)将導緻整個鍊失效。

注意: Swift 的自判斷鍊和 Objective-C 中的消息為空有些相像,但是Swift可以使用在任意類型中,并且失敗與否可以被檢測到。

可選鍊可替代強制解析

Person具有一個自判斷residence屬性,它的類型是Residence?

class Person {
    var residence: Residence?
}
           

Residence具有一個Int類型的numberOfRooms,其值預設為 1。

class Residence {
    var numberOfRooms = 1
}
           

如果你建立一個新的 Person 執行個體,它的 residence 屬性由于是被定義為自判斷型的,此屬性将預設初始化為空:

let john = Person()
           

如果你想使用感歎号(!)強制解析獲得這個人residence屬性numberOfRooms屬性值,将會引發運作時錯誤,因為這時沒有可以供解析的residence值。

let roomCount = john.residence!.numberOfRooms
//将導緻運作時錯誤
           

當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類拓展來。

class Person {
    var residence: Residence?
}
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?
}
class Room {
    let name: String
    init(name: String) {
        self.name = name
    }
}
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}
           

通過可選鍊調用屬性

由于john.residence是空,是以這個可選鍊和之前一樣失敗了,但是沒有運作時錯誤

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。
           

你可以通過可選鍊去強制設定屬性值

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

強制設定john.residence的屬性address将會失敗,因為john.residence目前為空,是以可以先給john.residence指派,再指派address屬性。

通過可選鍊調用方法

可選鍊調用方法的傳回值類型總是可選類型,即使傳回值類型為void也會傳回void?

if (john.residence?.address = someAddress) != nil {
    println("It was possible to set the address.")
} else {
    println("It was not possible to set the address.")
}
// prints "It was not possible to set the address."
           

通過可選鍊調用子腳本

你可以使用可選鍊來嘗試從子腳本擷取值并檢查子腳本的調用是否成功,然而,你不能通過可選鍊來設定子代碼。

注意: 當你使用可選鍊來擷取子腳本的時候,你應該将問号放在子腳本括号的前面而不是後面。可選鍊的問号一般直接跟在自判斷表達語句的後面。

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

如果你建立一個Residence執行個體給john.residence,且在他的rooms數組中有一個或多個Room執行個體,那麼你可以使用可選鍊通過Residence子腳本來擷取在rooms數組中的執行個體了:

<pre name="code" class="plain">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 {
    println("The first room name is \(firstRoomName).")
} else {
    println("Unable to retrieve the first room name.")
}
// prints "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]++
testScores["Brian"]?[0] = 72
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
           

連接配接多層連結

你可以将多層可選鍊連接配接在一起,可以掘取模型内更下層的屬性方法和子腳本。然而多層可選鍊不能再添加比已經傳回的可選值更多的層

如果你試圖獲得的類型不是可選類型,由于使用了可選鍊它将變成可選類型。 如果你試圖獲得的類型已經是可選類型,由于可選鍊它也不會提高自判斷性。(即如果你試圖通過可選鍊獲得Int值,不論使用了多少層連結傳回的總是Int?)

let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
//此處的!表示john.residence一定有值一定能通路到address屬性,若改成?則表示不一定有值,隻有在有值的時候才能通路到address
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."。
           

連結自判斷傳回值的方法

通過調用傳回可選類型值的方法并按需連結方法的傳回值。

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