(譯)Swift2.2-可選鏈

官方文檔鏈接

原文鏈接


可選鏈(Optional Chaining)是為了在一個可能當前值為niloptional類型里固该,查詢和調(diào)用屬性眠蚂,方法和下標腳本的一個過程骏掀。如果這個可選類型包含了一個值宠纯,屬性卸夕,方法或是下標腳本,那么就會調(diào)用成功婆瓜;如果這個可選類型為nil快集,那么屬性,方法或下表腳本調(diào)用返回值就為nil廉白。多個連續(xù)的調(diào)用可以被鏈接在一起个初,但是如果在這個鏈接里有的節(jié)點為nil,那么就會導(dǎo)致整個鏈接失敗猴蹂。

注意:
在Swift中院溺,可選鏈和Objective-C中消息為`nil`有些類似,但是Swift可以使用在任何類型中晕讲,并且可以檢查調(diào)用是否成功。

使用可選鏈調(diào)用來強制展開

你可以在你希望調(diào)用的屬性马澈,方法或者下標腳本后面瓢省,如果這些值為非nil,那么你可以在可選值的后面使用一個問號(?)來替代可選鏈。這和在可選值后面放一個感嘆號(?)痊班,強制解包有些類似勤婚。主要的不同就是可選鏈會在可選值為nil的調(diào)用失敗,因為強制解包會在可選值為nil的時候觸發(fā)運行時錯誤涤伐。

為了反應(yīng)可選鏈可以被一個nil值調(diào)用馒胆,可選鏈調(diào)用的結(jié)果總是可選值,不論這個屬性凝果,方法或下標腳本返回的是不是非可選值祝迂。你可以使用這個可選返回值來檢查可選鏈調(diào)用成功(返回的可選變量包含一個值),或者由于在鏈接里有一個nil值就會調(diào)用失敗器净。

特別地型雳,可選鏈地調(diào)用的結(jié)果與原本煩人返回結(jié)果有相同的類型,但是包裝成了一個可選類型山害。當通過可選鏈的方式纠俭,一個Int型的屬性會返回一個Int?

下面的代碼片段解釋了可選鏈調(diào)用和強制展開的不同浪慌。

首先冤荆,聲明了兩個類,分別為PersonResidence

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

Residence市實例有一個名為numberOfRoomsInt類型的屬性权纤,有一個默認值1钓简。Person實例有一個可選的residence類型Residence?乌妒。

如果你創(chuàng)建了一個新的Person實例,它的residence屬性默認初始化為nil涌庭,在下面的代碼芥被,john有一個residence屬性為nil

let john = Person()

如果你想訪問這個personresidencenumberOfRooms屬性坐榆,可以在residence后面加一個感嘆號來強制解包它的值拴魄,那么你就會觸發(fā)一個運行時錯誤,因為沒有可展開的residence席镀。

let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error

上面的代碼如果改成john.residence有一個非nil值就會調(diào)用成功匹中。并且設(shè)置一個Int型的roomCount存儲房間的數(shù)量。但是豪诲,當residence為空的時候上面這段代碼會觸發(fā)運行時錯誤顶捷。

可選鏈調(diào)用提供了一種到另一種訪問numberOfRooms的方法,使用問號(屎篱?)來代替原來嘆號(7辍)的位置:

if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")
} else {
    print("Unable to retrieve the number of rooms.")
}
// Prints "Unable to retrieve the number of rooms."

residence后面添加問號之后,Swift就會在residence不為空的情況下訪問numberOfRooms交播。

因為訪問numberOfRooms有可能失敗重虑,可選鏈會返回Int?類型,或稱為“可空的Int”秦士。如上例所示缺厉,當residencenil的時候,可空的Int將會為nil隧土,表明無法訪問numberOfRooms提针。

要注意的是,即使numberOfRooms是不可空的Int時曹傀,這一點也成立辐脖。只要是通過可選鏈,就意味著最后numberOfRooms返回一個Int?而不是Int

通過賦給john.residence一個Residence的實例變量,這樣john.residence不為nil了:

john.residence = Residence()

現(xiàn)在就可以正常訪問john.residence.numberOfRooms皆愉,其值為默認的1揖曾,類型為Int?

if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")
} else {
    print("Unable to retrieve the number of rooms.")
}
// Prints "John's residence has 1 room(s)."

為可選鏈定義模型類

通過使用可選鏈可以調(diào)用多層屬性,方法亥啦,和下標腳本炭剪。這樣可以通過各種模型向下訪問各種子屬性。并且判斷能否訪問子屬性的屬性翔脱,方法或下標奴拦。

下面這段代碼定義了四個模型類,這些例子包括多層可空鏈式調(diào)用届吁。為了方便說明错妖,在PersonResidence的基礎(chǔ)上增加了RoomAddress绿鸣,以及相關(guān)的屬性,方法以及下標暂氯。

class Person {
    var residence: Residence?
}

Residence類也比之前更完整潮模。這次,Residence類定義一個名為rooms的變量屬性痴施,初始化為[Room]類型的空數(shù)組擎厢。

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?
}
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?
}

現(xiàn)在Residence有了一個存儲Room類型的數(shù)組,numberOfRooms屬性需要計算辣吃,而不是作為單純的變量动遭。計算后的numberOfRooms返回rooms數(shù)組的count屬性值。現(xiàn)在的Residence還提供訪問rooms數(shù)組的快捷方式神得, 通過可讀寫的下標來訪問指定位置的數(shù)組元素厘惦。此外,還提供printNumberOfRooms方法哩簿,這個方法的作用就是輸出這個房子中房間的數(shù)量宵蕉。最后,Residence定義了一個可空屬性address节榜,其類型為Address?羡玛。Address類的定義在下面會說明。

Room類被用在rooms數(shù)組里全跨,它是一個簡單類有一個名為name的屬性缝左,并且有一個初始化器來設(shè)置房間的名字

class Room {
    let name: String
    init(name: String) { self.name = name }
}

最后一個類是Address亿遂,這個類有三個String?類型的可空屬性浓若。buildingName以及buildingNumber屬性表示建筑的名稱和號碼,用來表示某個特定的建筑蛇数。第三個屬性表示建筑所在街道的名稱:

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耳舅, 如果buildingNumber不為空則返回buildingNumber碌上。如果這兩個屬性都為空則返回nil

通過可選鏈訪問屬性

正如上文使用可選鏈來強制展開中所述浦徊,可以通過可空鏈式調(diào)用訪問屬性的可空值馏予,并且判斷訪問是否成功。

上面使用類定義來創(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.")
}
// Prints "Unable to retrieve the number of rooms."

因為john.residencenil霞丧,所以毫無疑問這個可空鏈式調(diào)用失敗。

通過可空鏈式調(diào)用來設(shè)定屬性值:

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

在這個例子中冕香,通過john.residence來設(shè)定address屬性也是不行的蛹尝,因為john.residencenil后豫。

func createAddress() -> Address {
    print("Function was called.")
    let someAddress = Address()
    someAddress.buildingNumber = "29"
    someAddress.street = "Acacia Road"
    return someAddress
}
john.residence?.address = createAddress()

你可以使用createAddress()函數(shù),就就會知道它沒有被調(diào)用突那,因為什么都沒有打印挫酿。

通過可選鏈來調(diào)用方法

可以通過可空鏈式調(diào)用來調(diào)用方法,并判斷是否調(diào)用成功愕难,即使這個方法沒有返回值早龟。

Residence中的printNumberOfRooms()方法輸出當前的numberOfRooms值:

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

這個方法沒有返回值。但是沒有返回值的方法隱式返回Void類型务漩,如無返回值函數(shù)中所述拄衰。這意味著沒有返回值的方法也會返回()或者空的元組。

如果在可空值上通過可空鏈式調(diào)用來調(diào)用這個方法饵骨,這個方法的返回類型為Void?翘悉,而不是Void,因為通過可空鏈式調(diào)用得到的返回值都是可空的居触。這樣我們就可以使用if語句來判斷能否成功調(diào)用printNumberOfRooms()方法妖混,即使方法本身沒有定義返回值。通過返回值是否為nil可以判斷調(diào)用是否成功:

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.")
}
// Prints "It was not possible to print the number of rooms."

同樣的轮洋,可以判斷通過可空鏈式調(diào)用來給屬性賦值是否成功制市。在上面的例子中,我們嘗試給john.residence中的address屬性賦值弊予,即使residencenil祥楣。通過可空鏈式調(diào)用給屬性賦值會返回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.")
}
// Prints "It was not possible to set the address."

通過可選鏈來訪問下標腳本

通過可空鏈式調(diào)用汉柒,我們可以用下標來對可空值進行讀取或?qū)懭胛笸剩⑶遗袛嘞聵苏{(diào)用是否成功。

注意: 
當通過可空鏈式調(diào)用訪問可空值的下標的時候碾褂,應(yīng)該將問號放在下標方括號的前面而不是后面兽间。可空鏈式調(diào)用的問號一般直接跟在可空表達式的后面正塌。

下面這個例子用下標訪問john.residencerooms數(shù)組中第一個房間的名稱嘀略,因為john.residencenil,所以下標調(diào)用毫無疑問失敗了:

if let firstRoomName = john.residence?[0].name {
    print("The first room name is \(firstRoomName).")
} else {
    print("Unable to retrieve the first room name.")
}
// Prints "Unable to retrieve the first room name."

在這個例子中乓诽,問號直接放在john.residence的后面帜羊,并且在方括號的前面,因為john.residence是可空值鸠天。

類似的讼育,可以通過下標,用可空鏈式調(diào)用來賦值:

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

這次賦值同樣會失敗,因為residence目前是nil窥淆。

如果你創(chuàng)建一個Residence實例卖宠,添加一些Room實例并賦值給john.residence,那就可以通過可選鏈和下標來訪問數(shù)組中的元素:

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.")
}
// Prints "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] += 1
testScores["Brian"]?[0] = 72
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]

上面的例子中定義了一個testScores數(shù)組忧饭,包含了兩個鍵值對扛伍, 把String類型的key映射到一個整形數(shù)組。這個例子用可空鏈式調(diào)用把“Dave”數(shù)組中第一個元素設(shè)為91词裤,把”Bev”數(shù)組的第一個元素+1刺洒,然后嘗試把”Brian”數(shù)組中的第一個元素設(shè)為72。前兩個調(diào)用是成功的吼砂,因為這兩個key存在逆航。但是key“Brian”在字典中不存在,所以第三個調(diào)用失敗渔肩。

多層鏈接

可以通過多個鏈接多個可空鏈式調(diào)用來向下訪問屬性因俐,方法以及下標。但是多層可空鏈式調(diào)用不會添加返回值的可空性周偎。

也就是說:

如果你訪問的值不是可空的抹剩,通過可空鏈式調(diào)用將會放回可空值。
如果你訪問的值已經(jīng)是可空的蓉坎,通過可空鏈式調(diào)用不會變得“更”可空澳眷。
因此:

通過可空鏈式調(diào)用訪問一個Int值,將會返回Int?蛉艾,不過進行了多少次可空鏈式調(diào)用钳踊。
類似的,通過可空鏈式調(diào)用訪問Int?值勿侯,并不會變得更加可空拓瞪。

if let johnsStreet = john.residence?.address?.street {
    print("John's street name is \(johnsStreet).")
} else {
    print("Unable to retrieve the address.")
}
// Prints "Unable to retrieve the address."

john.residence包含Residence實例,但是john.residence.addressnil罐监。因此吴藻,不能訪問john.residence?.address?.street瞒爬。

需要注意的是弓柱,上面的例子中,street的屬性為String?侧但。john.residence?.address?.street的返回值也依然是String?矢空,即使已經(jīng)進行了兩次可空的鏈式調(diào)用。

如果把john.residence.address指向一個實例禀横,并且為address中的street屬性賦值屁药,我們就能過通過可空鏈式調(diào)用來訪問street屬性

如果你設(shè)置一個Address實例作為john.residence.address,并且為地址的street屬性設(shè)置一個實際的值柏锄,你可以通過多層鏈接訪問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.")
}
// prints "John's street name is Laurel Street."

在上面的例子中酿箭,因為john.residence是一個可用的Residence實例复亏,所以對john.residenceaddress屬性賦值成功。

對返回可空值的函數(shù)進行鏈接

上面的例子說明了如何通過可空鏈式調(diào)用來獲取可空屬性值缭嫡。我們還可以通過可空鏈式調(diào)用來調(diào)用返回可空值的方法缔御,并且可以繼續(xù)對可空值進行鏈接。

在下面的例子中妇蛀,通過可空鏈式調(diào)用來調(diào)用AddressbuildingIdentifier()方法耕突。這個方法返回String?類型。正如上面所說评架,通過可空鏈式調(diào)用的方法的最終返回值還是String?

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

如果要進一步對方法的返回值進行可空鏈式調(diào)用眷茁,在方法buildingIdentifier()的圓括號后面加上問號:

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\".")
    }
}
// Prints "John's building identifier begins with "The"."
注意:
在上面的例子中在,在方法的圓括號后面加上問號是因為buildingIdentifier()的返回值是可空值纵诞,而不是方法本身是可空的上祈。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市浙芙,隨后出現(xiàn)的幾起案子雇逞,更是在濱河造成了極大的恐慌,老刑警劉巖茁裙,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件塘砸,死亡現(xiàn)場離奇詭異,居然都是意外死亡晤锥,警方通過查閱死者的電腦和手機掉蔬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來矾瘾,“玉大人女轿,你說我怎么就攤上這事『爵妫” “怎么了蛉迹?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長放妈。 經(jīng)常有香客問我北救,道長,這世上最難降的妖魔是什么芜抒? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任珍策,我火速辦了婚禮,結(jié)果婚禮上宅倒,老公的妹妹穿的比我還像新娘攘宙。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布蹭劈。 她就那樣靜靜地躺著疗绣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铺韧。 梳的紋絲不亂的頭發(fā)上持痰,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音祟蚀,去河邊找鬼工窍。 笑死,一個胖子當著我的面吹牛前酿,可吹牛的內(nèi)容都是我干的患雏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼罢维,長吁一口氣:“原來是場噩夢啊……” “哼淹仑!你這毒婦竟也來了畸悬?” 一聲冷哼從身側(cè)響起矾兜,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤滚朵,失蹤者是張志新(化名)和其女友劉穎纫骑,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體金拒,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡签孔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年异雁,在試婚紗的時候發(fā)現(xiàn)自己被綠了瑰艘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片是鬼。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖紫新,靈堂內(nèi)的尸體忽然破棺而出均蜜,到底是詐尸還是另有隱情,我是刑警寧澤芒率,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布囤耳,位于F島的核電站,受9級特大地震影響偶芍,放射性物質(zhì)發(fā)生泄漏充择。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一腋寨、第九天 我趴在偏房一處隱蔽的房頂上張望聪铺。 院中可真熱鬧化焕,春花似錦萄窜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽键兜。三九已至,卻和暖如春穗泵,著一層夾襖步出監(jiān)牢的瞬間普气,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工佃延, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留现诀,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓履肃,卻偏偏與公主長得像仔沿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子尺棋,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容