Swift3.0 - 可选类型

在编程世界中有一种非常通用的模式,那就是某个操作是否要返回一个有效值。

Objective-C 中,对 nil 发送消息是安全的。如果这个消息签名返回一个对象,那么
nil 会被返回;如果消息返回的是一个结构体那么它的值都将为零。但在实际开发中我们常常会碰到由于对象为 nil 而导致的崩溃或者不想出现的问题,比如数组、字典、block的判空处理等等。

Tony Hoare在1965年设计了 null 引用,他对此设计表示痛心疾首,并将这个问题称为“价值10亿美元的错误”:

那时候,我正在为一门面向对象的语言(ALGOL W)设计第一个全面的引用类型系统。我的目标是在编译器自动执行的检查的保证下,确保对于引用的所有使用都是安全的。但是我没能抵挡住 null 引用的诱惑,因为它太容易实现了。这导致了不计其数的错误,漏洞以及系统崩溃。这个问题可能在过去四十年里造成了有十亿美元的损失。

前面叙述这么多是为了引出编程语言中更安全的设计 - swift中的可选类型

swift定义后缀 ? 来作为标准库中的定义的命名型类型 Optional<Wrapped>语法糖。换句话说,下面两个 声明是等价的:

var optionalInteger: Int?
var optionalInteger: Optional<Int>

在上述两种情况下,变量 optionalInteger 都被声明为可选整型类型。注意在类型和 ? 之间没有空格。

类型 Optional<Wrapped> 是一个枚举,有两个成员,nonesome(Wrapped),用来表示可能有也可能没有的值。任意类型都可以被显式地声明(或隐式地转换)为可选类型。如果你在声明或定义可选变量或属性的时候没有提供初始值,它的值则会自动赋为默认值 nil

如果一个可选类型的实例包含一个值,那么你就可以使用后缀运算符 ! 来获取该值(即强制解包),正如下面描述的:

optionalInteger = 42
optionalInteger! // 42

使用 ! 运算符解包值为 nil 的可选值会导致运行错误。

你也可以使用可选链式调用和可选绑定来选择性地在可选表达式上执行操作。如果值为 nil ,不会执行任何操作,因此也就没有运行错误产生。

  • 处理可选值值缺失的方法

一个可选的值是一个具体的值或者是 nil 以表示值缺失。在类型后面加一个问号来标记这个变量的值是可选的。你可以一起使用 iflet来处理值缺失的情况:

var optionalString: String? = "Hello"
print(optionalString == nil)  //false

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}

上面的代码中如果变量optionalName的可选值是 nil,条件会判断为 false,大括号中的代码会被跳过。如果不是 nil,会将值解包并赋给 let 后面的常量 name,这样代码块中就可以使用这个值了。

还有另外一种处理值缺失的方法,使用 ?? 操作符来提供一个默认值。如果可选值缺失的话,可以使用默认值来代替。

 let nickName: String? = nil
 let fullName: String = "John Appleseed"
 let informalGreeting = "Hi \(nickName ?? fullName)"

因为 nickNamenil,所以 informalGreeting的值就变成了 "Hi John Appleseed"

  • 隐式解析可选类型

当可以被访问时,Swift 语言定义后缀 ! 作为标准库中命名类型 Optional<Wrapped>的语法糖,来实现自动 解包的功能。换句话说,下面两个声明等价:

var implicitlyUnwrappedString: String!
var explicitlyUnwrappedString: Optional<String>

由于隐式解包修改了包涵其类型的声明语义,嵌套在元组类型或泛型的可选类型(比如字典元素类型或数组元素
类型),不能被标记为隐式解包。例如:

let tupleOfImplicitlyUnwrappedElements: (Int!, Int!) // 错误
let implicitlyUnwrappedTuple: (Int, Int)! // 正确
let arrayOfImplicitlyUnwrappedElements: [Int!] // 错误 
let implicitlyUnwrappedArray: [Int]! // 正确

由于隐式解析可选类型和可选类型有同样的表达式析可选类型。比如,你可以将隐式解析可选类型的值赋给变量、常量和可选属性,反之亦然。

正如可选类型一样,你在声明隐式解析可选类型的变量或属性的时候也不用指定初始值,因为它有默认值 nil
可以使用可选链式调用来在隐式解析可选表达式上选择性地执行操作。如果值为 nil ,就不会执行任何操作,因此也不会产生运行错误。

  • 使用可选链式调用代替强制展开

通过在想调用的属性、方法、或下标的可选值后面放一个问号 ? ,可以定义一个可选链。这一点很像在可选 值后面放一个叹号 ! 来强制展开它的值。它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制展开将会触发运行时错误。

为了反映可选链式调用可以在空值 nil 上调用的事实,不论这个调用的属性、方法及下标返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功,如果调用 有返回值则说明调用成功,返回 nil 则说明调用失败。

特别地,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用 可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是 Int 类型,则会变为 Int? 类型。

代码示例,首先定义两个类 PersonResidence :

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

Residence 有一个 Int 类型的属性 numberOfRooms ,其默认值为 1 。 Person 具有一个可选的 residence 属性,其类型为 Residence?

假如你创建了一个新的 Person 实例,它的 residence 属性由于是是可选型而将初始化为 nil ,在下面的代码中, john 有一个值为 nilresidence 属性:

 let john = Person()

如果使用叹号 ! 强制展开获得这个 johnresidence 属性中的 numberOfRooms 值,会触发运行时错误,因为这时 residence 没有可以展开的值:

let roomCount = john.residence!.numberOfRooms // 这会引发运行时错误

john.residence 为非 nil 值的时候,上面的调用会成功,并且把 roomCount 设置为 Int 类型的房间数量。正如上面提到的,当 residencenil 的时候上面这段代码会触发运行时错误。

可选链式调用提供了另一种访问 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 ,它现在将返回值为 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.")
}
// 打印 “John's residence has 1 room(s).”
  • 强制解包的时机

当你能确定你的某个值不可能是 nil 时使用 ! 进行强制解包,你应当不希望如果它不巧意外地是 nil 的话,这句程序会直接挂掉。

总之,使用强制解包时一定要慎重!!!

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

推荐阅读更多精彩内容