一个下午让你掌握Swift基础 ( 9/9 ) 可选值

简介

这是一个Swift语言教程,基于最新的iOS 9,Xcode 7.3和Swift 2.2,会为你介绍Swift编程非常基础的内容。从电脑如何工作的全程基本原理到语言结构,你会足够了解这门语言,来处理数据和管理代码的行为。

快速链接


可选值

你目前遇到的所有变量和常量都有实际的值。当你有一个字符串变量的时候,比如 var name,它有一个相关的字符串值,例如“张嘉夫”。它可能是一个空字符串,像 “”,但尽管如此,还是有一个可以访问到的值。

那是 Swift 自带的安全特色之一:如果说类型是 Int 或 String 那么就有一个实际的整数或字符串在那里,有保证的。

这篇文章会为你介绍可选值(optionals)的概念,一个特殊的 Swift 类型,不只可以表示一个值,也可以没有值。在这篇文章的最后,你会知道为什么需要可选值,以及如何安全的使用它们。

介绍 nil

有时候,有一个没有值的值是很有用的。想象一个场景,你需要访问一个人的认证信息。你想要存储这个人的名字、年龄和职业。名字和年龄都是必须有值的东西—每个人都有。但不是每个人都有工作,所以职业这个值的缺失就是你需要处理的事情。

让我们实际看看这个情况。

在不知道可选值的情况下,这是你可能去表示一个人的名字、年龄和职业的方式:

var name: String = "张嘉夫"
var age: Int = 23
var occupation: String = "软件开发者 & 作家"

但如果我被解雇了呢?也许我中了彩票然后再也不想工作了(我真的想!)。这时候能够访问一个不存在的值就会很有用了。

为什么你不能就用一个空的字符串呢?好吧,你可以!让我们讨论一下那样会怎么做,以及为什么可选值是一个更好的选项。

标记值

一个有效值,用来表示不存在的,叫做标记值(sentinel value)。这是前一个例子里你的空字符串就会是这样。

让我们看一下另一个例子。假设你写了一些代码,从服务器上请求什么东西,那么你可以用一个变量来存储服务器返回的任意错误代码:

var errorCode: Int = 0

在成功的时候,你可以用零来表示没有错误。意味着 0 是标记值。

就像职业的空字符串,这可以奏效,但对于程序员有潜在的混淆的风险。0 实际上可能是一个有效的错误代码—也或许在未来会是。无论怎么样,你不能完全确定这是没有错误的。

这两种情况下,如果有一个特殊类型可以表示值的缺失就会更好。这样什么时候有值什么时候没有就很明确了。
Nil 表示值的缺失,你会看到 Swift 如何用相当优雅的方式来直接把这个概念并入语言中。

其他有一些编程语言只用标记值。有一些,比如 Objective-C,有 ni 的概念,但仅仅是零的同义词。只是另一个标记值罢了。

Swift 介绍了一个全新的类型,叫做可选值来处理一个值可能是或不是 nil。这意味着如果你在处理一个非可选值类型,那么它就被保证了一定有值,不需要去担心有没有值。总是有的。相似的,如果你在用一个可选值类型,那你就必须要考虑 nil 情况了。它移除了使用标记值带来的歧义。

介绍可选值

可选值是 Swift 对于同时表示值和值的缺失问题的解决方案。一个可选值类型可以引用一个值或是 nil。

把可选值想象为一个盒子:它要么包含一个值,要么没有。当它没有包含值的时候,它被认为包含了 nil。盒子本身总是存在;它一直在这里,你可以打开然后看看里面。

另一方面,字符串或整数,周围并没有这个盒子。相关那里总有一个值,比如 “hello” 或 42。记住,非可选值类型确保有一个实际值。

**注意:**学过物理的这时候可能会想起薛定谔的猫。可选值和它有一点像,除了这不是一个生死攸关的问题!

用下面的语法声明可选值类型:

var errorCode: Int?

它和标准声明之间的唯一区别就是类型末尾的问号。这个例子里,errorCode 是“ Int 可选值”。这意味着变量本身就像一个盒子,包含一个 Int 或 nil。

设置值很简单。你可以设置它为一个 Int,像这样:

errorCode = 100

或者你可以设置它为 ni,像这样:

errorCode = nil

图表可以帮助你形象化理解正在发生什么:

可选值盒子永远存在。当你给变量赋值 100 的时候,你在用值来填满盒子。当你给变量赋值 nil 的时候,你在清空盒子。

花几分钟想一下这个概念。用盒子的类比会是一个巨大的帮助,在你读完这篇文章剩下的部分的时候,然后开始使用可选值。

迷你练习

创建一个叫做 myFavoriteSong 的 String 可选值。如果你有一首最爱的歌,就把它设置为表示那首歌。如果你有不止一首或者没有最爱的,就设置可选值为 nil。

拆包可选值

有可选值一切都很好,但你应该会好奇怎么看到盒子里面,操作它所包含的值。

让我们看看打印可选值的值得时候会发生什么:

let ageInteger: Int? = 23
print(ageInteger)

如下输出:

Optional(30)

那不是你真正想要的—尽管如果仔细想一想,它还是有意义的。你的代码输出了盒子。结果说:“ ageInteger 是一个可选值,包含值 30”。

要看这个值类型如何区别于非可选值类型,那就让我们看看如果尝试把 ageInteger 当一个正常整数来使用会怎么样:

print(ageInteger + 1)

代码触发了一个错误:

error: value of optional type 'Int?' not unwrapped; did you mean to use'!' or '?'?(错误:可选值类型 'Int?' 的值没有被拆包;你是不是想使用 '!' 或 '?'?)

不正确是因为你尝试把一个整数加到一个盒子上—不是盒子里面的值,而是盒子本身。这是没有意义的!

强制拆包

错误信息给了解决方案的指示:它告诉你可选值“没有被拆包”。你需要从盒子里把值拆出来。就像过圣诞节!
来看看怎么做。考虑如下声明:

var authorName: String? = "张嘉夫"

你可以用两种不同的方法来拆包可选值。第一种叫做强制拆包(force unwrapping),像这样做:

var unwrappedAuthorName = authorName!
print("作者是\(unwrappedAuthor)")

变量名后面的感叹号告诉编译器你想看到盒子里面,并且把值拿出来。这是结果:

作者是张嘉夫

很棒!这就是你想要的。

使用词语“强制”和感叹号 ! 应该给你传达了一种危险感觉,这值得注意。你应该尽量少的使用强制拆包。要知道为什么,考虑一下如果可选值不包含值得时候会发生什么:

authorName = nil
var unwrappedAuthorName = authorName!
print("作者是\(unwrappedAuthorName)")

这个代码产生了如下运行时错误:

fatal error: unexpectedly found nil while unwrapping an Optional value(致命错误:未料到地,拆包一个可选值的时候发现是 nil)

发生了这个错误是因为在你尝试拆包变量的时候它没有包含任何值。糟糕的是你是在运行时得到这个错误,而不是编译时,意味着只有在用非法的输入执行这部分代码的时候才会发生,你才能注意到它。更糟糕的是,如果这个代码在一个 app 内部,运行时错误会导致 app 崩溃!

怎么才能让它安全呢?

要在这里停止运行时错误,你可以包住代码,然后用一个检查来拆包可选值,像这样:

if authorName != nil {
    var unwrappedAuthorName = authorName!
    print("作者是\(unwrappedAuthorName)")
} else {
    print("没有作者。")
}

if 语句检查可选值是不是包含 nil。如果不是,意味着它包含一个你可以拆包的值。

这个代码现在安全了,但仍不失最佳状态。如果你依赖这个技术,每次你想拆包可选值的时候都要记住检查 nil。这会开始变得单调乏味,有一天你会忘记,然后再次导致运行时错误的可能性。

然后回到绘图板!

If let 绑定

幸运的是,Swift 包含了一个叫做 if let 绑定(binding)的特色,让你可以安全的访问可选值里面的值。像这样使用:

if let unwrappedAuthorName: String = authorName {
    print("作者是\(unwrappedAuthorName)")
} else {
    print("没有作者。")
}

你会立马注意到这次没有感叹号了,并且 unwrappedAuthorName 的类型是一个普通的 String,不是一个可选值 String。

**注意:**一般的不会在 if let 语句指定拆包变量的类型,这里是为了显示清楚。

这个特殊语法摆脱了可选值类型。如果可选值包含值,代码执行 if 语句的 if 那边,自动拆包 unwrappedAuthorName 变量,因为你知道这是安全的,可以这么做。

如果可选值不包含值,代码执行 if 语句的 else 那边。这个例子里,unwrappedAuthorName 甚至都不存在。
你可以看到 if let 绑定与强制拆包相比有多么安全,你应该任何可能的时候选择它。

你甚至可以一次拆包多个值,像这样:

let authorName: String? = "张嘉夫"
let authorAge: Int? = 23
if let name: String = authorName,
    age: Int = authorAge {
    print("作者是\(age)岁的\(name)。")
} else {
    print("没有作者或没有年龄。")
}

这段代码拆包了两个值。它只在两个可选值都包含值得时候才会执行 if 部分的语句。
现在你知道如何安全的看进一个可选值里面,并且取出值,如果有的话。

迷你练习

  1. 使用之前的 myFavoriteSong 变量,使用 if let 绑定来检查是否包含值。如果是,print 这个值。如果没有,print “我没有一首喜欢的音乐。”
  2. 把 myFavoriteSong 设置为当前的相反面;也就是说,如果是 nil,就设置为一个字符串,如果是一个字符串,就设置为 nil。观察你 print 的结果会有什么变化。

Nil 合并

有一个终极和更常用的方式来拆包可选值。当你想要取出可选值里的值,不论值是什么,就用它—如果是 nil,就会使用一个默认值。这叫做 nil 合并(coalescing)

这是它工作的方式:

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

nil 合并发生在第二行,带有两个问号(??)。这行表示 result 会等于 optionalInt 里的值,或者如果 optionalInt 包含的是 nil 就等于 0。

所以这个例子里,result 包含具体的 Int 值 10。

上面的代码和下面等同:

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

设置 optionalInt 为 nil,像这样:

optionalInt = nil
var result: Int = optionalInt ?? 0

现在你的 result 等于 0。

关键点

  • Nil 表示值的缺失。
  • 非可选值变量和常量一定会有一个非 nil 值。
  • 可选值(Optional)变量和常量就像盒子,可以包含一个值或是空的(nil)。
  • 要处理可选值里的值,首先必须从可选值里拆包。
  • 拆包可选值最安全的方式是使用 if let 绑定(binding)nil 合并。避免强制拆包(forced unwrapping),它可能会导致一个运行时错误。

接下来去哪儿?

这就是可选值啦,Swift 的一个核心特色,帮助让语言更安全,使用起来更简单。你会发现代码里会贯穿始终使用使用它们。他们帮你让代码更安全,通过保证明确处理值的缺失情况。在你的 Swift 体验中,你应该会越来越欣赏它。

特别是,你会在 10-12 篇教程中使用它们,并且在那里学习集合(collections)。

挑战

你已经学习了可选值背后的理论,并且在实践中见过他们了。现在轮到你搞一下了!

挑战 A:你就是编译器

下列哪个是有效语句?

var name: String? = "嘉夫"
var age: Int = nil
let distance: Float = 26.7
var middleName: String? = nil

挑战 B:分而治之

首先,创建一个函数,返回一个整数可以被另一个整数整数的次数。如果不能整除就返回 nil。命名函数为 divideIfWhole。

然后,写代码尝试拆包函数的可选值结果。应该有两种情况:如果成功,print “Yep, 它整除 (answer) 次”,如果失败,print “除不尽 :[”。

最后,测试你的函数:

  1. 10 除以 2。应该 print “Yep,它整除 5 次。”
  2. 10 除以 3。应该 print “除不尽 :[”

提示 1:使用如下作为函数构造的开始:

func divideIfWhole(value: Int, by divisor: Int)

你需要添加返回类型,这应该是一个可选值!

提示 2:你可以使用模除操作符(%)来决定一个值是否能被另一个除尽;回想一下,这个操作返回两个数字相除的余数。例如,10 % 2 = 0 意味着 10 可以被 0 除尽,没有余数,但是 10 % 3 = 1 意味着 10 被 3 除了三次,有一个余数为 1。

挑战 C:重构与改善

上一个挑战里,你用 if 语句写的代码。在这个挑战里,重构代码,使用 nil 合并。这次,所有情况都让它输出 “它除了 X 次”,但如果不是整除,那 X 就应该是 0。

挑战和迷你练习源代码

https://yunpan.cn/cBDRwNvgpHy4G (提取码:f961)


介绍

欢迎来到Swift世界!Swift是一门苹果在2014年夏天发布的编程语言。从那之后,Swift发布了一个主要的版本跳跃,成为了开始在苹果平台:iOS,OS X,watchOS和tvOS开发的最简单的方式。

谁适合这篇教程

这篇教程适合懂一点编程、并且希望学习Swift的人。也许你已经为网站写过一些JavaScript代码,或者用Python写过一些简短的程序。这篇教程就是为你准备的!你会学习到编程的基本概念,同时也会成为Swift语言小能手。

如果你是赤裸裸的编程新手,这篇教程也是为你准备的!教程里贯穿有简短的锻炼和挑战来给你一些编程练习,同时测试你的知识。

需要准备什么

要看这篇教程,你需要准备如下的东西:

  • 一台运行OS X El Captian(10.11)的Mac,带有最新发布的更新并且安装了安全补丁。这样你才能够安装需要的开发工具:最新版本的Xcode。
  • Xcode 7.3 或更新的版本。Xcode是用Swift写代码的主要开发工具。最小也需要Xcode 7.3版本,因为那个版本包含Swift 2.2。你可以免费从Mac App Store下载Xcode的最新版本,这里:http://apple.co/1FLn51R

如果你还没有安装Xcode最新版本,在继续看下面的教程前要确定安装。

如何使用这篇教程

每篇教程都会介绍触手可及的话题理论,伴随大量Swift代码来示范在学习的实际的应用程序。

教程里的所有代码都是平台中立的;这意味着不是为iOS、OS X或任何其它平台而特定。代码在playgrounds里运行,你在本篇中已经学习了。

在剩下的教程里,你可以把代码在自己的playground里输入进去。这样你就可以和代码“玩耍”(play around),做一些改变立即就能看见代码运行的结果。

剩下的教程里会贯穿实际小练习,都是简短的练习,关于触手可及的主题。每篇的末尾也有挑战,会有编程问题也会有长一点的代码练习来测试你的知识。做完就能掌握大部分的Swift基础知识。

教程更新

教程会随Swift语言的更新而更新,会发布在我的简书和知乎专栏上,搜索“张嘉夫”即可关注我。

目录

本教程从一些基础工作开始来让你起步:

  • 第1篇,编程本质 & Playground基础 - 这就是,到编程世界的入门介绍!你会从电脑和编程的预览开始,然后剩余时间给Swift playground打个招呼。
  • 第2篇,变量 & 常量 - 你会学习到变量和常量,这是用来存储数据的“地方”。你也会学习数据类型以及Swift如何追踪数据类型并在代码中进行传输。
  • 第3篇,数字类型 & 操作 - 你会从基础的数字类型比如整形和浮点型数字开始,当然也包括布尔类型。也会看到一些在实际操作,从比较到算数操作如加减。
  • 第4篇,字符串 - 用字符串来存储文字-从按钮里的文字到图片的标注到这篇教程全部的文字,存储这所有的一切!你会学习到string和character类型,以及基于这些类型的一些常见操作。

在脑海中有基础数据类型后,就该用那些数据做一些事情了:

  • 第5篇,做判断 - 代码不总是直接从头运行到尾。你会学习在代码里如何做判决并且设定情况来运行某段代码。
  • 第6篇,重复步骤 - 继续不要让代码直线运行的主题,你会学习到如何使用循环来重复某些步骤。
  • 第7篇,函数 - 函数是Swift中用来构建代码的基础建筑。你会学习到如何定义函数来分组代码到可复用单元中。
  • 第8篇,闭包(Closures) - 闭包和函数很接近。你会学习到如何使用它们来轻松传递代码块。

最后一篇会回到数据:

  • 第9节,可选值 - 这篇讲可选值,Swift中的一种特殊类型,表示既有可能是一个真实的值也有可能没有值。这篇的最后你会知道为什么要用可选值以及如何安全地使用它们。

这些基础会让你快速开始Swift之路,做好接触更高级编程主题的准备。

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

推荐阅读更多精彩内容