理解Swift中Optional类型-有和无的哲学

本文深入探讨Swift中的Optional类型,包括其基础概念、类型转换方法及如何使用if-let绑定和nil合并来安全地处理Optional值。

原文连接:http://blog.barat.cc/ios/understanding-swift-optional/

nil的遗憾

当某个变量或表达式没有任何内容时,在Objective-C中可以使用nil来表示。nil在Objective-C中是一个「野孩子」,void指针指向数字 0,本质上来讲nil就是一个数字。来看看下面的代码在Objective-C会出现什么情况:

int i = (int)(nil)+20; //可以这样吗?

nil可以用来做运算吗?编译器会报错?或者它可以运行?

因为nil指向数字 0,确切的说nil就是Int类型数据,所以上述的代码不仅可以通过编译,而且得到的结果是 20。显然,Objective-C编译器对nil的处理方式非常简单粗暴,直接将nil当作是数字对待了。这样做表面看起来平安无事,但在某些特定的情境下,却可能造成歧义。既然nil不能表示「纯粹无」,的确需要表示「这个变量没有任何值」该怎么办?看来,Objective-C对此是无解了。

Optional基础概念

或许是因为上述例子中提到的问题,在某些特定的场景下的确需要表示「无」的存在,因此Swift中引入了Optional类型。

在深入讨论之前,先看看Optional是什么吧!

public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
  case None

  case Some(Wrapped)

  public init()

  public init(_ some: Wrapped)

  @warn_unused_result
  public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?

  @warn_unused_result
  public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?

  public init(nilLiteral: ())
}

可以看到Swift中Optional其实是一个枚举类型,其中包含了NoneSome两个值和应用某个规则并返回结果的mapflatMap两个方法,此外还有三个构造方法。我们可以使用上述的构造方法创造一个Optional类型的变量,但在实际开发过程中会更多的使用?表示一个变量是Optional类型。例如下面的示例:

var someNumber: Int?
var anotherNumber: Int = 100

var someStr: String?
var anotherStr: String = "Hello World"

使用问号?表示某个变量是Optional类型,如果没有显示赋值Swift会自动给Optional类型变量赋值nil。上述的代码中,声明了两个Int类型的变量,其中someNumber是Optional类型,而 anotherNumber是普通的Int类型并且值为 100 。我们可以把Optional类型想象成为一个容器,装东西用的盒子。someNumber所代表的盒子里面是空的,等着用户往里面装东西「当然,能装的东西已经规定好了,必须是Int类型」,而anotherNumber所代表的盒子里面已经装好了东西。如下图:

变量示意图

变量存在的意义在于:可以参与运算并完成一定的业务要求。接下来我们对上述示例代码中的变量进行一定的运算,再观察结果分析。假设,给someNumberanotherNumber分别加上100并输出结果,那么代码如下:

var someNumber: Int?    //值为nil
var anotherNumber: Int = 100    //值为100

someNumber = someNumber + 100    //编译错误,不能对nil进行操作
anotherNumber = anotherNumber + 100    //正常

为什么会这样呢?按照在Objective-C中的理解看来:一个变量的值是nil,则指向数字0,是可以进行运算的「一开始的示例中我们正是这么做的」。显然,nil在Swift中已经不再是指向数字0的指针,而是真的指向「纯粹无」。既然当前这个变量的值是「纯粹无」,在它被初始化之前当然是不允许进行操作的。

变量someNumber盒子中没有任何值,所以不能进行运算,那么我们做如下的赋值再尝试:

var someNumber: Int?

someNumber = 100 //赋值
someNumber = someNumber + 100 //还是无法通过编译,不能对Optional直接操作

在Swift中对一个Optional类型的变量直接进行操作,是不允许的。这又是为什么呢?大家还记得吗?Optional是枚举类型,不经过任何转换直接和Int类型相加,当然是不允许的。那么,如果想要对Optional的值进行运算,要怎么办呢?

Optional类型转换

在对一个Optional类型的变量进行操作之前,需要先将其转换成可操作的具体类型。你可以把它理解成:在吃掉盒子里面存放的苹果之前,需要先将苹果从盒子中取出来。这个过程可以使用符号!来完成。

var someNumber: Int?

someNumber = 100 //赋值
someNumber = someNumber! + 100  //将苹果从盒子中取出来,再加上100

但是在将苹果从盒子中取出来的时候,你却需要面对一个严肃的哲学问题:盒子中确实有苹果吗?如果上述代码中缺少赋值表达式someNumber = 100,那么这段代码虽然可以躲过编译器的检查,但却会在程序运行过程中出现异常,导致应用崩溃。所以,为了保证程序的健壮性,在吃掉苹果之前,应该判断盒子中是否真的存在苹果,大致如下所示:

var someNumber: Int?

// someNumber = 100
if(someNumber != nil) {
    someNumber = someNumber! + 100
} else {
    print("盒子中根本木有苹果")
}

上述代码安全了,可是每个Optional类型的变量在使用之前,都需要对其进行if-else判断显然是一件很麻烦的事情,而人类是最喜欢偷懒的群体,那该怎么办呢?

其中第一个办法,称之为if-let绑定,通过if-let的判断对其进行操作,大致如下:

let authorName: String? = "Barat Semet"
let authorAge: Int? = 30

if let name: String = authorName,
    age: Int = authorAge {
        print("本文作者 \(name) 今年 \(age) 岁了")
} else {
    print("作者名称or年龄未指定")
}

另外一种方法称之为nil合并,使用两个??符号连接在一起表示:如果存在值则获取当前值,如果不存在则获取给定的默认值,大致代码如下:

var someNumber: Int? 

var number: Int = someNumber ?? 0 //若someNumber不为nil则获取其值,若为nil则获取0

上述打代码大致等价于:

var someNumber: Int?
var number: Int

if let unwrapped:Int = someNumber {
    number = unwrapped
} else {
    number = 0
}

swift-case和guard

我们已经看到了在处理Optional时如何使用if-elseif-let语法判断其值,此外Swift还提供了switch-caseguard语法,使我们的操作更加便捷。

使用switch-case结构对Optional的值进行匹配时,需要给每个case分之跟着的表达式加上?符号即可。如果对其中的某部分的值不关心,也可以使用SomeNone来简单处理,如下代码所示:

var changshaProgrammer: Double? = 10000000 //长沙市序猿总数

switch changshaProgrammer {
    case 0?: print("长沙市木有程序猿类")
    case (1..<1000)?: print("长沙市程序猿类竟然不到1000人")
    case .Some(let x): print("长沙市有 \(x)程序猿类")
    case .None: print("我们不知道长沙市有多少程序猿类")
}

除了上述这种方式之外,我们也可以使用guard语法做如下判断:

func sayProgrammerCount(count: Double) -> String {
    guard let pop: Double = count else {
        return "我们不知道长沙有多少程序猿类"
    }
    return "长沙有程序猿类 \(pop) 人"
}

print(sayProgrammerCount(1000000))

最后的总结

和Objective-C不同,在Swift代码中我们拿到某个表达式返回的Optional值时,如果非常确定该表达式的结果不会是nil则可以使用!将其强制转换为我们需要的结果。否则,在我们不确定的情况,一定要使用if-elseif-letswait-caseguard之中的一种对Optional返回的结果进行判断后再处理。

参考

  1. Understanding Optionals in Swift
  2. A Beginner’s Guide to Optionals in Swift
  3. The Swift Programming Language (Swift 2.1) The Basics
  4. The Swift Programming Language (Swift 2.1) Optional Chaining
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值