【Swift 3.1】17 - 可选链 (Optional Chaining)

【Swift 3.1】17 - 可选链 (Optional Chaining)

自从苹果2014年发布Swift,到现在已经两年多了,而Swift也来到了3.1版本。去年利用工作之余,共花了两个多月的时间把官方的Swift编程指南看完。现在整理一下笔记,回顾一下以前的知识。有需要的同学可以去看官方文档>>


可选链是在一个可能为nil的可选类型上查询和调用属性、方法和下标的一个过程。如果这个可选类型有值,那么属性、方法或者下标调用成功;如果可选类型值为nil,调用属性、方法或者下标就会返回nil。多个查询可以连在一起,只要任何一个环节为nil,那么整个链条失败。

可选链作为强制解包的一个方法 (Optional Chaining as an Alternative to Forced Unwrapping)

我们想访问可选类型的属性、方法或者下标,在这个可选类型的后面加上?来表示可选链。可选链返回的结果永远是可选类型的值,即使属性、方法或者下标不是可选类型。

首先,创建两个类PersonResidence:

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

创建一个Person实例,residence属性默认为nil

let john = Person()

如果要访问Person实例的residencenumberOfRooms属性,需要在residence后面用!解包,这将会触发运行时错误,因为residence没有值:

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

下面使用可选链来访问numberOfRooms的值:

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实例赋给john.residence,那么john.residence就不为nil了,接着在使用可选链解包,解包成功:

john.residence = Residence()

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

为可选链定义更多Class模型 (Defining Model Classes for Optional Chaining)

我们可以使用多层次的可选链调用属性、方法和下标。下面将用四个类来演示:

第一个是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?
}

第三个是Room

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

最后一个是Address

class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if let buildingNumber = buildingNumber, let street = street {
            return "\(buildingNumber) \(street)"
        } else if buildingName != nil {
            return buildingName
        } else {
            return nil
        }
    }
}

使用可选链访问属性 (Accessing Properties Through Optional Chaining)

创建一个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,所以这个可选链还是调用失败。

也可以尝试使用可选链来设置属性值:

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

尝试设置john.residence的属性address也不成功,因为目前john.residence的值还是为nil。把someAddress赋给address也是可选链的一部分,但是=右边的代码其实没有运行。直接通过上面的代码不容易看出,我们可以定义一个方法来创建someAddress来测试下:

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

我们可以发现,createAddress()并没有调用,因为Function was called.没有打印。

使用可选链调用方法 (Calling Methods Through Optional Chaining)

我们可以使用可选链来调用方法,并检查那个方法是否调用成功,即使那个方法没有返回值。注意:没有返回值的方法,有一个默认返回类型Void

使用可选链调用没有返回值的方法,方法的返回值将会是Void?,而不是Void,因为使用可选链访问的结果都是可选类型。

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

也可以使用类似上面这种检查方法来检查使用可选链赋值是否成功。使用可选链赋值会返回一个Void?

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

使用可选链访问下标 (Accessing Subscripts Through Optional Chaining)

注意:使用可选链访问下标,要把?放在中括号前面,而不是后面。

例如:

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.residencenil,所以调用下标失败。

如果我们创建Residence实例,并给rooms数组赋值,然后再把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.")
}
// Prints "The first room name is Living Room."
访问可选类型的下标 (Accessing Subscripts of Optioinal Type)

如果一个下标返回一个可选类型的值,例如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]

连接多层次链 (Linking Multiple Levels of Chaining)

如果想要获取的类型不是可选类型,因为可选链,他将会变成可选类型;如果想要获取的类型是可选类型,因为可选链,那么它依然是可选类型。例如,如果想要获取的值是Init类型,那么将会返回Init?类型,不管可选链有多少层;如果想要获取的值是Init?类型,那么仍然返回Init?类型,不管可选链有多少层。

例如:

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现在是有值的,但是john.residence.addressnil,所以john.residence?.address?.streetnil

如果设置一个真实的Address实例给john.residence.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.")
}
// Prints "John's street name is Laurel Street."

链接有可选类型返回值的方法 (Chaining on Methods with Optional Return Values)

下面是使用可选链访问有可选类型返回值的方法:

if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
    print("John's building identifier is \(buildingIdentifier).")
}
// Prints "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\".")
    }
}
// Prints "John's building identifier begins with "The"."

第十七部分完。下个部分:【Swift 3.1】18 - 错误处理 (Error Handling)


如果有错误的地方,欢迎指正!谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容