Swift 4 Optionals 可选值

什么是可选值类型

Swift introduces a whole new type, optional, that handles the possibility a value could be nil. If you’re handling a non-optional type, then you’re guaranteed to have a value and don’t need to worry about the existence of a valid value. Similarly, if you are using an optional type then you know you must handle the nil case. It removes the ambiguity introduced by using sentinel values

看下官方解释:

Swift创造了一个全新类型:可选值,用来处理数据的值可能为空的情况。

首先,我们先引入 Sentinel values的概念。

Sentinel values(翻译成警戒值吗)

警戒值的定义:警戒值代表一种特殊的情况,比如某个值为空的时候,这个时候的值就是警戒值。

A valid value that represents a special condition such as the absence of a value is known as a sentinel value.

这里举个栗子来解释,假如我们从服务器请求某些数据的时候,我们用一个变量来接收服务器的错误码。

var errorCode = 0

在请求成功的情况下,我们用0来表示没有错误返回,那么这个时候0就是一个Sentinel value。但是对于程序员来说,0是一个具有相当迷惑性的值。因为在未来的某些时候,0可能就是一个真实的错误码。在不参考相关文档的时候,我们不能十分确定,0就是成功的情况。

在上面这种情况发生时,我们会想是否能有一种特殊的类型,能够表示一个变量有值或无值的两种情况呢?

在其他的语言中也大量使用到了警戒值,例如在OC中有nil的概念。但是,在OC中nil也仅仅是zero的同义词,换句话说就是另一个警戒值罢了。

为了解决这个问题,Swift创造一个全新的类型:可选值,专门用来处理数据可能为空的情况。

It removes the ambiguity introduced by using sentinel values.

使用可选值类型,我们就可以消除警戒值带来的歧义问题。

optionals介绍

想象可选值类型就是一个盒子,这个盒子有两种情况,一种是填充了值,一种是为空,没有填充任何值。当它没有填充值的时候,我们称之为nil。这里我们需要注意的是,盒子自始至终都是存在的,变化的只有盒子的内容。

  • 语法形式
var errorCode : Int?
errorCode = 100
errorCode = nil

我们在类型后添加一个问号来标识这个数据是可选值类型,表明这个变量盒子包含了两种可能性:有值或者为空。专业描述起来就是,这个可选值变量对有值或无值进行了打包(wrap)操作。

Unwarpping optionals(解包)

在可选值类型打包完后,实际使用的时候如何获取盒子(box)里的内容呢?
首先看下下面代码的执行结果

var result: Int? = 30
print(result)

执行结果:

Optional(30)

与此同时,你会在看到一条警告,“Expression implicitly coerced from 'Int?' to Any”.意思警告你,你把可选值类型隐式地强制转换成任意类型来处理,这就意味着你的代码可能出现了错误。屏蔽警告你可以修改代码为print(result as any)

警告可能还不够直白的看出可选值和非可选值的区别,下面我们尝试下对可选值直接做操作:

print(result + 1)

上面的代码会触发一个错误

Value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'?

为什么报错,因为我们直接对一个可选值盒子做了加一操作,而不是对可选值盒子里面的内容做加一操作,这肯定是错误的。

Force unwrapping (强制解包)

上面的错误已经直白的告诉我们,可选值没有进行解包(unwrapped),那么我们解包即可:

var authorName: String? = "Matt Galloway"
var authorAge: Int? = 30

有两种方法可以解包,首先看下第一种方法:强制解包

var unwrappedAuthorName = authorName!
print("Author is \(unwrappedAuthorName)")

打印:

Author is Matt Galloway

好了,我们的目的达到了~
变量名后的感叹号告诉编译器,我想看下盒子里面的内容并取出来里面的值。但是,使用强制解包对程序可能会有点小危险,我们应该谨慎的使用。
看下下面的代码就知道了:

authorName = nil
print("Author is \(authorName!)")

上面的代码会产生一个运行时错误

fatal error: unexpectedly found nil while unwrapping an Optional value

错误产生的原因就是我们企图对一个值为空的变量进行解包。更糟糕的是,这个错误在编译时是无法预见的,而是在运行时产生。

那么如何修改代码让其更安全呢?

我们可以检查下可选值的内容值

if authorName != nil {
  print("Author is \(authorName!)")
} else {
  print("No author.")
}

上面看似解决了问题,但是仍然不完美。如果我们依赖if判断,那么每次我们解包的时候都需要去做判断。如果某次忘了检查,程序还是会在运行时crash掉。

怎么解决这个问题呢,我们引入可选值绑定的概念。

Optional binding(可选值绑定)

Swift引入了一个新的特性:可选值绑定,使用这个新特性可以让我们更安全的访问可选值的内容。

if let unwrappedAuthorName = authorName {
  print("Author is \(unwrappedAuthorName)")
} else {
  print("No author.")
}

可选值绑定没有添加新的符号来进行绑定,而是使用了if let表达式来进行绑定,如果可选值内容不为空,可选值就会被解包,然后if判断就会正常执行上半部分,如果为空,那么else部分将会执行。

这样我们就避免使用强制解包带来的问题,强制解包只在我们确定可选值有内容时再使用。

因为if let命名比较烦,所以推荐let后的常量名和可选值名称相同(就像可选值的影子一样):

if let authorName = authorName {
  print("Author is \(authorName)")
} else {
  print("No author.")
}

我们甚至可以同时对多个可选值进行解包:

if let authorName = authorName,
   let authorAge = authorAge {
  print("The author is \(authorName) who is \(authorAge) years old.")
} else {
  print("No author or no age.")
}

上面的代码只有在if条件的两个可选值都有值时才会执行。

更甚者,我们可以将解包操作和bool值来一起进行判断:

if let authorName = authorName,
   let authorAge = authorAge,
   authorAge >= 40 {
  print("The author is \(authorName) who is \(authorAge) years old.")
} else {
  print("No author or no age or age less than 40.")
}

guard关键字

if let解包已经很好使了,但是有时候我们需要优先抛弃错误的情况,或者说有时候if let的判断语句太长,那么我们可以使用guard语法糖来简化操作。现实中这种情况应该很常见。

func calculateNumberOfSides(shape: String) -> Int? {
  switch shape {
  case "Triangle":
    return 3
  case "Square":
    return 4
  case "Rectangle":
    return 4
  case "Pentagon":
    return 5
  case "Hexagon":
    return 6
  default:
    return nil
  }
}

上面是一个多变形边数判断函数,我们调用它

func maybePrintSides(shape: String) {
  let sides = calculateNumberOfSides(shape: shape)

  if let sides = sides {
    print("A \(shape) has \(sides) sides.")
  } else {
    print("I don't know the number of sides for \(shape).")
  }
}

使用if let解包没有任何问题。但是,我们有时逻辑可能不是这样的

func maybePrintSides(shape: String) {
  guard let sides = calculateNumberOfSides(shape: shape) else {
    print("I don't know the number of sides for \(shape).")
    return
  }

  print("A \(shape) has \(sides) sides.")
}

我们优先排除了识别不了的图形的情况,这样做是不是更有可读性。

  • 语法格式:guard + 条件 + else + 代码块
    如果条件不成立,代码块就会执行,值得注意的是代码块必须要有return,否则编译器会报错。

Nil coalescing(空值合并)

swift有一种更好用的方式去解包可选值。不管可选值有没有值,如果没有值就使用默认值,这种方式就是空值合并。

var optionalInt: Int? = 10
var mustHaveResult = optionalInt ?? 0

空值合并使用2个问号操作符来实现,如果optionalInt有值,mustHaveResult值就是10,如果optionalInt=nil,那么mustHaveResult的值就是默认值0。

上面的代码等效于下面:

var optionalInt: Int? = 10
var mustHaveResult: Int
if let unwrapped = optionalInt {
  mustHaveResult = unwrapped
} else {
  mustHaveResult = 0
}

总结

  • nil表示没有值,和OC中的nil代表zero不同。
  • 非可选值类型的变量和常量必须要有一个非空(non-nil)的值。
  • 可选值类型的变量和常量就像一个盒子,里面可以包含值也可以什么都不填充。
  • 如果想要使用可选值类型的内部值,必须要先解包。
  • 解包可选值最安全的方式就是使用可选值绑定或者空值合并,只有在确定可选值包含内容时才可以使用强制解包

客官,路过左下角小❤️❤️点下,谢谢啊:-D

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

推荐阅读更多精彩内容