函数式Swift之Optional

前言

Swift的可选类型可以用来表示可能缺失或是计算失败的值。今天和大家分享的笔记是如何有效地利用可选类型,以及它们在函数式编程范式中的使用方法。

为什么使用可选值

Optional是Swift的一个特色,它解决了“有”和“无”这两个困扰了Objective-C许久的哲学概念,同时代码安全性也得到了很大的增加。

Optional的定义

详情参考http://www.jianshu.com/p/a0bf5aa7f21d

Optional的运用

首先先创建一个存储几个欧洲城市的人口数量的字典:

let cities = ["Paris": 2241, "Madrid": 3165, "Amsterdam": 827, "Berlin": 3562]

然后我们来查询某个城市的人口数量:

//let madridPopulation: Int = cities["Madrid"]//无法通过类型检查
let madridPopulation: Int? = cities["Madrid"]
print(madridPopulation)//Optional(3165)

为什么会无法通过类型检查呢?是因为你不能保证每次查询都会有结果,有可能某Madrid键不存在于cities中,所以我们无法保证每次查询都返回的是Int值。所以说madridPopulation的类型为可选类型。
对于一个可选类型的使用,我们可以检查查询是否成功:

if (madridPopulation != nil) {
    //强制解包
    print("The population of Madrid is \(madridPopulation! * 1000)")
} else {
    print("Unknown city: Madrid")
}

如果查询成功,我们设置了一个运算,这时候出现了一个后缀运算符“!”,该运算符是强制将一个可选类型转换为一个不可选类型,是为了获取可选值中实际的Int值。
在开发的时候,有时候会忘记打"!",虽然编译器会告诉你应该添加一个"!",但是还是挺不方便的。未经检验的可选类型强制解包,解开后发现是nil,这时候你的程序将会崩溃,在控制台就会出现该信息:

fatal error: unexpectedly found nil while unwrapping an Optional value

这种强制解包的做法不推荐大家使用,老司机们可能知道Swift有一个特殊的可选绑定(optional binding)机制,它可以显示地处理异常情况,从而避免运行时错误,但是有个缺点就是比较繁琐,判断比较多。

//可选绑定机制
if let madridPopulation = cities["Madrid"] {
    print("The population of Madrid is \(madridPopulation * 1000)")
} else {
    print("Unknown city: Madrid")
}

Swift还给!运算符提供了一个更安全的替代--??运算符。让我们来看看??运算符的定义吧:

@warn_unused_result
public func ??<T>(optional: T?, @autoclosure defaultValue: () throws -> T) rethrows -> T

@warn_unused_result
public func ??<T>(optional: T?, @autoclosure defaultValue: () throws -> T?) rethrows -> T?

如果大家看起来有点懵,那就上代码吧:

let num1 = 1 ?? 3//num1 = 1
let num2 = nil ?? 3// num2 = 3

就是操作符??左边不为nil的时候将左边的值赋给num,这个时候对于右边的值,编译器是不管的,不去运算的。为什么对于右边的值可以不用管,这要归功于@autoclosure defaultValue: () throws -> T (Swift标准库中定义通过使用 @autoclosure 类型标签来避开创建显示闭包的需求),再仔细的说就是一言不合上代码:

infix operator ?? { associativity right precedence 110 }
func ??<T>(optional: T?, @autoclosure defaultValue: () -> T) -> T {
   if let x = optional {
        return x
    } else {
        return defaultValue()
    }
}

总结就是操作符??左边左边不为nil 就将左边的值赋给num,然后就没有然后了。如果左边为nil,这时候就需要去计算右边的值了,再把右边的值赋给num。

可选链

可选链,它是Swift的一个特殊机制。
考虑下面客户订单的模型的代码片段:

struct Order {
    let orderNumber: Int
    let person: Person?
}

struct Person {
    let name: String
    let address: Address?
}

struct Address {
    let streetName: String
    let city: String
    let state: String?
}

给定一个Order,如何才能查找到客户的状态呢?

let address = Address(streetName: "软件园", city: "成都", state: nil)
let person = Person(name: "小陈陈", address: address)
let order = Order(orderNumber: 2012090301, person: person)

方法一:使用显示解包运算符
print(order.person!.address!.state!)
这种做法不安全,如果中间任意一个数据缺失,就会导致运行异常,比如说这里的state = nil,执行打印语句就会崩溃。
方法二:可选绑定

if let myPerson = order.person{
    if let myAddress = myPerson.address{
        if let myState = myAddress.state{
            print(myState)
        }
    }
}

这种做法相当于方法一是安全多了的,但是貌似太繁琐了,要写的判断很多,如果结构再复杂一点,写的判断就更多了。
方法三:可选链

if let myState = order.person?.address?.state{
    print("This order will be shipped to \(myState)")
}else {
    print("Unknown person, address, or state.")
}

这种做法就是尝试用?运算符去可选类型进行解包,而不是强制将它们解包,当任意一个组成项失败时,整条语句将返回nil。

分支上的可选值

前面我们说的可选绑定机制,有点分支语句的感觉,一步步判断过滤。Swfit还有其他两种分支语句,switch和guard,它们也非常适合与可选值搭配使用。

switch
switch madridPopulation{
    case 0?: print("Nobody in Madrid")
    case (1..<1000)?: print("Less than a million in Madrid")
    case .Some(let x): print("\(x) people in Madrid")
    case .None: print("We don't know about Madrid")
}

这里如果有人对.Some()和.None有点懵的小伙伴,请大家再次去看看Optional的定义(http://www.jianshu.com/p/a0bf5aa7f21d)。
如果大家对一个特定的值木有兴趣的话,也可以直接匹配.Some()和.None。

guard

Swift里面的一个神奇的东西,它的设计旨在当一些条件不满足的时候,可以尽早的退出当前作用域。我们重写一下打印给定城市居民数量的代码:

func populationDescriptionForCity(city: String) -> String? {
    guard let population = cities[city] else { return nil}
    return "The population of Madrid is \(population * 1000)"
}
print(populationDescriptionForCity("Madrid"))//Optional("The population of Madrid is 3165000")
可选映射

从名字我们也可以猜测它是如果可选值是nil,则结果也是nil。不然就执行一些其它运算,类似于:

func incrementOptional(optional: Int?) -> Int? {
    guard let x = optional else {return nil}
    return x + 1
}
flatMap

flatMap函数检查一个可选值是否为nil,若不是,我们将其传递给参数函数,若是nil。那么结果也是nil,我们用链式调用来举个例子:

func populationOfCapital3(country: String) -> Int? {
    return capitals[country].flatMap { capital in
        return cities[capital]
    }.flatMap { population in
        return population * 1000
    }
}

总结

类型系统有助于你捕捉难以察觉的细微错误,其中一些错误很容易在开发过程中被发现,但是会一直留存在代码里面,这样就像是埋来一颗炸弹一样,很不安全。坚持使用可选值能够从根本上杜绝这种错误。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • 关于 Swift 重要这个文档所包含的准备信息, 是关于开发的 API 和技术的。这个信息可能会改变, 根据这个文...
    无沣阅读 4,299评论 1 27
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young阅读 3,794评论 1 10
  • Hello Word 在屏幕上打印“Hello, world”,可以用一行代码实现: 你不需要为了输入输出或者字符...
    restkuan阅读 3,175评论 0 6
  • 136.泛型 泛型代码让你可以写出灵活,可重用的函数和类型,它们可以使用任何类型,受你定义的需求的约束。你可以写出...
    无沣阅读 1,466评论 0 4
  • 姓名:张汉超 公司:东莞耀升机电有限公司 组别:4月25-27日六项精进245期学员 【日精进打卡第221天】 【...
    张汉超阅读 128评论 0 0