我们使用Swift这个苹果新推出的编程语言已经有一段时间了。其中的一个极大的优点就是苹果称为“optional types”的东西。几乎所有的objective-c程序员都知道用nil来表示某个引用类型的对象是没有值的。但是要把nil和某个变量的类型联系起来还是有些牵强。
这里,我们就来介绍一下Swift提供的optional type(可选类型)。先介绍一些实现的细节,然后指出optional type体系里的几个要点。
类型?
在我们开始进入代码前,先来看看为什么一个类型被定义为可选的。我们遇到的类型一般都是通常的,非可选的类型。包括一般的值类型,比如,Int,Bool和String。还有复杂点的引用类型,比如,UIView,等。
我们声明这些类型的变量的时候,Swift要求必须给这些变量赋值。这一要求非常的严格,如果你想在初始化一个变量之前使用它使编译不过的。
这个表面看起来很郁闷,但其实很有帮助。长远来说,不让这样的代码编译通过,Swift可以避免发生因为使用了未初始化的值而引发的潜在的运行时错误。
let x: Int = nil
然而,有人会尝试给let声明的常量赋值为nil。
这样的代码会直接引发一个错误:类型“Int”不是protocol ’NilLiteralConvertible’类型。对于非optional type的类型,不实现“NilLiteralConvertible”protocol是不可以使用nil来初始化的。所以,简单的x,只是一个Int型的实例,只能被赋值为Int的值而不是nil。
程序里的变量不可能全部都有初值,所以这个时候optional type就该出场了。Swift里,只要在任意类型的后面加一个问号以后就变成了optional type(可控类型)。比如,之前的例子中。只要给Int后面加一个问号就可以将x赋值为nil了。
这应该是很多写Objective-C的哥们需要的了。变量值可以是“实际”的值,也可以是nil。这只取决于你的代码处理的是哪种情况了。
装箱
你可能会说,“Int只是值类型,不是一个对象怎么能使用nil呢?NSInteger是不能这么用得。。”
的确,你说的没错。NSInteger没有nil值。或者更准确的说经过类型转化后你会得到一个整型值0。所以,在Objective-C得API里定义了很多的标记表明“无值”状态:0,1,NSIntergerMin,NSIntegerMax以及NSNotFound等,都是表明“nothing”的。
当你仔细考虑就会发现没有一个一致的方式表明“nothing”这个值,而是用不同的自定义值标记“nothing”会增加一定的复杂度。取一个数组中不存在的值返回的事NSNotFound,取一个table view中不存在的row的时候返回的事一个-1。
Swift的optional type提供了一种更加清晰的表示方法。但是如何让任何类型都具有optional type这个功能呢?这些都建立在泛型的基础之上:
上面的就是Swift核心库对于optional type的定义(稍微作了修改)。Swift定义了一个新的类型Optional,它由两个值,一个是“nothing”,叫做None,一个是把某个类型T的值给包装起来之后的值。Swift就是利用这一机制把某了类型的值包装起来,当然这个值可以是空(nothing),也可以不是空。
在这个例子中, 第一个整数就只是一个Int型的值。后面的类型后面跟了问号“?”的其实都是Optional<T>类型的。或者,简短的可以表示为Int?。
有了这个能力,Swift就可以给任何类型表示“nothing”的值nil,即使是Int。Optional type同时可以表示“real”的值,也可以表示“nothing”的值,而不需要其他的特殊的值。
拆箱
这样表示optional value同时也会引发一个问题。现在我们知道optional type是一个独立的类型:Optional<T>。所以,在需要T的地方,不如某个函数需要T类型的参数传入,那么optional<T>的值是不能用的:
我们需要把需要的值从optional的箱子里拿出来。并且,很重要的一点,在那之前需要检查这个值是否存在。Swift提供了叹号“!”操作符来提取值。
记得把x的值修改为100,而不是之前的nil。因为,叹号操作符只适用于optional type的值本身有“real”值的。如果没有的话是会抛出运行时异常的。
所以,在拆箱取值以前,我们需要先判断这个可选(optional)的值是否为空。就和我们在Objective-C中常做的类似。
但是如果这个x是从其他的方法返回回来的呢?我们可以直接调用这个函数来检验返回值, 完全不必要先给局部变量赋值,再检测是否为空。
Swift已经实现了这个功能,叫做optional binding(可选绑定)。使用if和let两个关键字就可以写出一行紧凑的代码来检测函数返回值是否存在。
这里我们已经不用叹号操作符来显示的强制拆箱。这是optional binding(可选绑定)的另一个好用的地方。直接在if语句的判定表达式里拆箱optional type(可选类型),就可以确定这个optional type是否有值,不用手动的使用叹号操作符来拆箱。
Chaining
现在我们建立一个准确的检测和拆箱optional value(可选值)的规则。比如,如何在optional value上调用方法?你肯定会在一个可能为空的对象上调用方法,这是一定会发生的。在Objective-C中,在nil对象上调用方法会返回一个nil。
幸好Swift也可以做到这样。使用optional chaining(可选链)的方式来调用可能为空(nil)的方法:
在对象和其调用的方法之间插入一个问号“?”操作符,我们就可以表明是要一个实际存在的值还是要一个“nothing”。这和Objective-C的调用非常类似。
注意:这样调用的方法的返回值一定都是optional type(可选类型)的,即使这个方法的返回值被定义为非可选类型(non-optional type)。所以,在optional value(可选值)上调用的方法链上得任意一点的返回值都是optional的。在处理返回值的时候一定要考虑到值可能为空的可能。
考虑下面的代码:
someMethod()方法的声明中制定返回值为Int型,z还是得到一个optional value(可选值)。因为,我们使用了optional chaining(可选链)来调用方法。这可能看起来有点迷惑,但是很有帮助。尤其是在optional binding(可选绑定)的时候。比如,上面代码中的if let z = y?.someMethod()表达式。
这样可以很简洁的处理一下问题:
- 如果y是nil(这里已经是nil),optional chaining(可选链)可以保证我们这样写代码而不报错
- 如果y是nil或者someMethod()方法返回nil,optional binding(可选绑定)不会把nil赋值给non-optional value(非可选值)z。
- 最终我们会得到z,但是不用手动拆箱。因为这是可选绑定的(optional binding)。
总之,对于处理nil值来说,Swift提供了一个非常清晰的系统。我们或得了额外的类型安全,避免了不必要的特殊定义的值,而且还是像Objective-C一样简洁。