理解 Swift 中的问号感叹号

对于写惯了 OC 代码的程序员来说,不判空直接调用对象方法可能已经成为习惯了;而当方法的返回值是对象时,通常也是拿来就用。这些情况在 Swift 下都不存在了,因为 Swift 中出现了一个全新的概念:Optional(? & !)。

博客地址:http://davidleee.com
原文链接:http://davidleee.com/2017/07/14/Dive-in-Swift-Optional/

Optional 用于表示一种值可能为空的对象类型。一个 Optional 对象表示了两种可能性:要么对象有值,你可以通过 “unwrap” 去获取到这个值;要么对象里面没有任何东西。

unwrap(解包):在对象后加 “?” 或 “!” 称为将对象 “unwrap”,可以获取到 Optional 里面的关联值

Optional 这个概念在 C 语言或 Objective-C 里面并不存在。在 OC 中最接近的概念是:本来要返回对象的方法可能会返回 nil,这个 nil 表示“没有有效的对象可以返回”;然而,这只在对象身上有效,它不能作用在结构体、基础 C 类型或枚举上。这些类型的变量如果没有值,OC 会用 NSNotFound 来表示,它需要方法的调用者意识到这些特殊返回值的存在,并作出特殊的处理。

Optional 解决了上述问题,在 Swift 中,Optional 可以处理任何类型的空值,而不需要用一个特殊的常量去表示。

举个栗子:
当我们需要将字符串转换为数字时,在 Swift 中会使用 Int 的构造方法,如下:

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)

此时,convertedNumber 就是一个 Optional,看一下文档可以发现,这个构造方法返回的是 Int?

原因是这个构造器可能会失败: possibleNumber 也许并不能被转化为数字。这里的 ? 表示返回的对象是一个可选值,它可能是某个 Int 类型的对象,也可能什么都没有。(它不可能是别的类型的对象,因为 Swift 是强类型的)

nil

这里可以套用 OC 中的概念,nil 表示空值,但是在 Swift 中,它只能被赋值给 Optional 对象。当声明一个 Optional 的变量又没有给它赋值时,它会自动被赋值为 nil

var surveyAnswer: String?
// surveyAnswer == nil

本质上 Swift 的 nil 跟 OC 的 nil 是不一样的。
在 OC 中,nil 是指向一个不存在对象的指针;在 Swift 中,nil 不是一个指针,它是一个带有特定类型的表示数值缺失的值,任何类型的 Optional 都可以设置为 nil 而不只是对象类型。

If 和强制解包

可以使用 if 来判断一个 Optional 对象是否有值,就像常见的判空操作。在判空后,这个 Optional 对象可以使用 ! 来强制解包,这相当于告诉编译器:“我确定这个 Optional 对象肯定有值,直接取出来用吧!”

举个栗子:

if convertedNumber != nil {
    print("convertedNumber has an integer value of \(convertedNumber!).")
}

! 被用在一个空值时,你的程序就会“卡蹦”一声崩掉!

Optional Binding

这个机制可以用来判断一个 Optional 对象是否有值,如果有值就将它复制给一个局部变量或常量,否则不执行任何操作。

我们用 Optional Binding 来改写上一小节中的例子:

if let actualNumber = Int(possibleNumber) {
    print("\"\(possibleNumber)\" 是一个整型数字 \(actualNumber)")
} else {
    print("\"\(possibleNumber)\" 不能被转化为整型")
}

Int() 返回的对象有值,这个值就会被直接赋给前面的 actualNumber ,所以这个变量就不是一个 Optional,可以不需要解包而直接使用了。

在这种用法下,if 原来的作用还是存在的,可以用逗号分隔不同类型的判断,比如这样:

if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
    print("\(firstNumber) < \(secondNumber) < 100")
}
// 如果其中一个 Optional 没有值,或者最后那个判断的结果为 false,整个 if 判断会直接返回 false

通过 Optional Binding 声明的变量的作用于只在这个 if 之内,除非用 guard 去声明,详情参见官方文档 Early Exit

隐式解包

有时候,在特定的代码结构下,一个 Optional 对象可以被确保永远都有值(或者说理应永远都有值)。这种时候,每次使用这个对象都进行判空和解包就显得非常多余了,于是我们可以在声明这个对象的时候用隐式解包来处理:

let forcedString: String!
print(forcedString) // 不需要写成 “forcedString!”

事实上,例子中的 forcedString 还是一个 Optional 没变,但是我们让它在使用的时候自动解包,不需要我们手动加 ! 了。

链式调用

如果我们要取得的对象被包裹在了一层又一层的 Optional 之中,取得它的过程可能非常繁琐:

var label: UILabel?
if label != nil {
    if let temp1 = label.text {
        if let temp2 = temp1.hashText {
            ...
        }
    }
}

这时候,可以使用链式调用的方式改写:

if let hashText = label?.text?.hashText {
    ...
}

在这句话当中,? 表达的意思是:“如果这个对象有值就取出来,继续下面的步骤;如果没有值,就当我没写过这句话吧”。

默认值

在一些情况下,我们会想要 Optional 对象为 nil 的时候给出一个默认值。比如我们使用一个 String?label.text 赋值时,我们并不希望设置一个 nil 上去,因为那会让 UILabel 的高度变为0。
一种很简便的写法是这样的:

let s: String?
s = ...
label.text = s ?? "placeholder"

这样,当 s 为空时,label.text 的值就会是 “placeholder”。

总结

Swift 中的 Optional 其实是一个 enum:

enum Optional<T> {
    case none
    case some(T)
}

而它现在所见到的使用方法都可以认为是 Swift 的语法糖:

let x: String?
// 等价于
let x = Optional<String>.none

let x: String? = "hello"
// 等价于
let x = Optional<String>.some("hello")

let y = x!
// 等价于
switch(x) {
    case .some(let value): y = value
    case .none: // 抛个异常并整死你的应用:)
}

if let y = x {
    y.doSomething()
}
// 等价于
switch(x) {
    case .some(let y):  y.doSomething()
    case .none: break
}

什么?你想问这是哪门子的 enum?推荐你去看看官方文档 Enumeration

参考资料

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

推荐阅读更多精彩内容

  • 01-常量与变量 学习swift第一步打印Hello World print("Hello World") swi...
    iOS_恒仔阅读 5,115评论 2 19
  • Swift 介绍 简介 Swift 语言由苹果公司在 2014 年推出,用来撰写 OS X 和 iOS 应用程序 ...
    大L君阅读 3,182评论 3 25
  • 对各种值为"空"的情况处理不当,几乎是所有Bug的来源。 在我们的例子里,尽管tmp的值是nil,但调用tmp的r...
    AKyS佐毅阅读 10,497评论 1 13
  • Swift 简介 查看Swift当前版本 简介 Swift 语言由苹果公司在 2014 年推出,用来撰写 OS X...
    mian小爬阅读 321评论 0 1
  • 常量与变量使用let来声明常量,使用var来声明变量。声明的同时赋值的话,编译器会自动推断类型。值永远不会被隐式转...
    莫_名阅读 436评论 0 1