博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【函数式 Swift】可选值
阅读量:7003 次
发布时间:2019-06-27

本文共 5455 字,大约阅读时间需要 18 分钟。

可选值(Optionals)是 Swift 引入的一项非常棒的特性,本文将基于原书中的案例以及函数式编程思想进行讨论。


概述

首先推荐大家阅读原书中的可选值章节,有很多使用细节以及与 Objective-C 的对比讨论,我认为如果将这一章单独拿出来会是一篇讲解 Swift 可选值很不错的文章。

关于 Swift Optionals,我曾写过一篇,试图借助 Swift 源码来解析可选值,其中开篇提到:

Swift 引入的 Optionals,很好的解决了 Objective-C 时代 “nil or not nil” 的问题,配合 Type-Safe 特性,帮我们减少了很多隐藏的问题。

这里主要有两方面意思:

  • “nil or not nil”:在 Objective-C 中,nil 是一个极其常见的值,指向一个为空的对象,很重要的是向 nil 发消息是安全的,回忆一下我们曾经写过的代码,有意无意的都使用了该特性,换句话说,有很多情况我们是不对为空做区别处理的,这样一来,我们的代码通常会简洁一些,可是一旦由于 nil 的存在或是传递导致了 Crash,就不得不去追查任何可能为 nil 的情况。
  • “Type-Safe”:Swift 是一个类型安全的语言,我们需要清楚的了解被操作的值的类型,编译器会对代码时进行类型检查,把不匹配的类型标记为错误,这样能够帮助我们尽早的发现程序中的问题。而对于类似 Objective-C 中的 nil 来说,一定程度与类型安全产生了冲突,因而,引入 Optionals 来与 Type-Safe 配合很好的解决这一问题。

下面将结合原书案例进一步讨论。

注:本文将不涉及对可选值基础应用的讲解,如需学习,请查阅、或。

案例:Dictionary

使用两个 Dictionary 分别存储国家及其首都、城市名及其人口数量:

let capitals = [    "France": "Paris",    "Spain": "Madrid",    "The Netherlands": "Amsterdam",    "Belgium": "Brussels"]let cities = ["Paris": 2241, "Madrid": 3165, "Amsterdam": 827, "Berlin": 3562] // 单位为“千”复制代码

问题:编写函数接收输入的国家名,输出其首都城市的人口数量。

与之前章节一样,问题本身并不复杂,为了对比,我们先编写一个 Objective-C 版本的函数:

@property (nonatomic, copy) NSDictionary *capitals;@property (nonatomic, copy) NSDictionary *cities;_capitals = [[NSDictionary alloc] initWithObjectsAndKeys:            @"Paris", @"France",            @"Madrid", @"Spain",            @"Amsterdam", @"The Netherlands",            @"Brussels", @"Belgium",            nil];_cities = [[NSDictionary alloc] initWithObjectsAndKeys:            @2241, @"Paris",            @3165, @"Madrid",            @827, @"Amsterdam",            @3562, @"Berlin",            nil];- (NSInteger)populationOfCapital:(NSString *)country {    return [_cities[_capitals[country]] integerValue] * 1000;}复制代码

populationOfCapital 函数就是我们的解决方案,只需要一行代码即可,下面测试一下:

// Case 1NSLog(@"%ld", [self populationOfCapital:@"France"]); // 2241000// Case 2NSLog(@"%ld", [self populationOfCapital:@"China"]); // 0复制代码

对于 Case 1 一切正常,而对于 Case 2,由于 _capitals 中不包含 China,所以 _capitals[country]nil,按照 Objective-C 的 nil 处理办法,我们得到了 0,但是,我们无法区分这个是正常的结果还是异常的输出,如果我们修改一下 _capitals_cities

_capitals = [[NSDictionary alloc] initWithObjectsAndKeys:            @"Paris", @"France",            @"Madrid", @"Spain",            @"Amsterdam", @"The Netherlands",            @"Brussels", @"Belgium",            @"Tokyo", @"Japan", // Add Japan            nil];_cities = [[NSDictionary alloc] initWithObjectsAndKeys:            @2241, @"Paris",            @3165, @"Madrid",            @827, @"Amsterdam",            @3562, @"Berlin",            @0, @"Tokyo", // Add Tokyo            nil];// Case 3NSLog(@"%ld", [self populationOfCapital:@"Japan"]); // 0复制代码

我们忽略 Tokyo 是不是真的“零人口”,Case 3 输出 0 是正确的,但我们无法区分 Case 2 和 Case 3 的输出。这也就是 “nil or not nil” 问题。

那么 Swift 如何使用 Optional 解决这个问题呢?我们知道 populationOfCapital 函数应该能够区分 nilnot nil 的情况,而这正好匹配了可选值的定义:

// Optional.swift(swift/stdlib/public/core/Optional.swift)public enum Optional
: ExpressibleByNilLiteral { case none // nil case some(Wrapped) // not nil}复制代码

因此,populationOfCapital 的返回值类型应为 Int?capitalscities 查询失败时对应 nil 的情况,查询成功则对应 not nil,所以 Swift 中 populationOfCapital 函数定义如下:

func populationOfCapital(country: String) -> Int? {    if let capital = capitals[country] {        if let population = cities[capital] {            return population * 1000        } else {            return nil        }    } else {        return nil    }}let cities = [    "Paris": 2241,     "Madrid": 3165,     "Amsterdam": 827,     "Berlin": 3562,     "Tokyo": 0]let capitals = [    "France": "Paris",    "Spain": "Madrid",    "The Netherlands": "Amsterdam",    "Belgium": "Brussels",    "Japan": "Tokyo"]// Case 4populationOfCapital(country: "France") // 2241000populationOfCapital(country: "China") // nilpopulationOfCapital(country: "Japan") // 0复制代码

可以看到,借助可选值,Case 4 的输出满足了我们的需求,China 和 Japan 的情况也得到了区分。问题已经解决了,不过 populationOfCapital 完成的不够“美观”,也并没有利用到函数式编程思想,下面对它进行优化。

首先,我们先来看看 populationOfCapital 到底要完成什么工作,本质上就是从一个值转变为另一个值,即从国家名转变为人口数量,只是在转变过程中可能存在“失败”的情况,需要对这些“失败”的情况作出判断,通过,我们知道值的转变对应的就是 map 方法,所以我们需要开发的工具库就是支持可选值的 map 方法,称之为:flatMap

要为 Optional 添加 flatMap 函数,并且存在“失败”的情况,所以 flatMap 函数的输出类型应为 U?,而输入,则应该是该可选值不为 nil 时所要做的转换(转换本身也是一个函数):

extension Optional {    func flatMap(f: Wrapped -> U?) -> U? {        guard let x = self else {            return nil        }        return f(x)    }}复制代码

工具库就绪后,我们可以改写 populationOfCapital 函数:

func populationOfCapital_map(country: String) -> Int? {    return capitals[country].flatMap { capital in        return cities[capital]    }.flatMap { population in        return population * 1000    }}// Case 5populationOfCapital_map(country: "France") // 2241000populationOfCapital_map(country: "China") // nilpopulationOfCapital_map(country: "Japan") // 0复制代码

借助工具库,我们不再需要进行多次判断,而是直接关注“成功”的情况即可。事实上,在 Optional 类库中已经存在一个同样功能的 flatMap 函数,开发中直接调用即可,其源码如下:

// Optional.swift(swift/stdlib/public/core/Optional.swift)public func flatMap(_ transform: (Wrapped) throws -> U?)    rethrows -> U? {    switch self {        case .some(let y):            return try transform(y)        case .none:            return .none    }}复制代码

为什么使用可选值?

原书中详细讨论了为什么 Swift 使用可选值,大致总结以下两点:

  1. 安全特性:不同于 Objective-C 默认近似零的处理方式,Swift 通过显式的可选类型以及类型安全特性,帮助避免由于缺失值而导致的意外崩溃或是逻辑含糊;
  2. 明确的函数签名:Objective-C 中函数的参数并无是否为空的强制要求,我们在使用时往往也比较模糊,仿佛空和非空均支持(这种情况目前有所改观,借助 nullableNS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END 能够帮助我们设计空和非空的情况),而由于可选值的存在,使得 Swift 中的函数签名是否支持 nil 变得非常清晰,更有利于我们写出健壮的代码。

参考资料


本文属于《函数式 Swift》读书笔记系列,同步更新于 ,欢迎关注!

转载地址:http://gjytl.baihongyu.com/

你可能感兴趣的文章