浅谈Swift中的Optional类型

学习Swift时,绝大多数初学者都会被Swift中的Optional这个类型所困扰,比如“问号是什么?”、“叹号是什么?”、“为什么这里要加问号/叹号?”等等,这篇文章将会结合本人的一些经验来谈谈Swift的Optional类型。

为什么会有Optional类型

Swift是一门静态语言,在Swift中,所有的变量常量都需要在编译期间确定类型,在确定类型之后,编译器就可以检查代码中的一些错误,比如我们在代码中将一个String类型的值赋给一个UIView类型的变量时,编译器会直接报错。

而Objective-C是一门动态语言,这个操作在中是允许的。

静态语言会在编译期间检查类型的合法性,使用静态语言我们可以依赖编译器来检查出一些不符合预期的问题,在运行时A类型就是A类型,不会出现牛头不对马嘴的错误。

Optional的本质

那么在Swift中,nil算是什么?nil既不是String类型也不是UIView类型,我们无法保证所有的变量都必定会有值,开发的时候不可避免地需要使用到nil要怎么办?Swift给出了这样一个解决方案,那就是Optional类型,我们先来看看Optional的本质是什么,下面是Optional类型的声明(在Xcode中打出Optional再按住command点击即可查看)

可以看到,Optional是带范型的枚举类型,该枚举类型有两种值分别是none和some,some类型还带了一个参数,从注释里我们可以看到,nil其实就是Optional枚举中的none这个值,那么到这里要理解l就很简单了,就像是一个用一个箱子来将内容存放起来,箱子里可能放了东西(some),也可能什么也没有(none),并且Swift还提供了一些语法糖来使得Optional使用起来更加方便(比如XXX?声明Optional<XXX>、双问号??提供默认值等)。

Optional类型解包

了解了Optional的本质后可知String?String并不是同一个类型,前者实质上是Optional<String>而后者是字符串类型String,当一个方法需要的参数是String类型时传入一个String?的话会因为类型不匹配而导致编译器报错。

那我们在开发过程中要如何去通过一个Optional类型的值去取出实际类型的值呢?既然Optional是一个枚举类型,那么我们就可以使用switch语句来进行一些判断。

开个玩笑,其实Swift有提供一些很方便的东西来代替这个switch的写法,在介绍这些之前,这里先定义一个方法以便之后使用和讲解。

使用 if letguard let

Swift提供了一些方法让开发者可以有条件地解开一个Optional得到一个新的变量,比较常见的就是if letguard let语句了,上文中提到的switch也是有条件解包的一种,但实际开发中不常使用。

if letguard let在解包的同时还可以判断这个Optional值是否为nil,至于在开发中需要使用if let还是guard let就见仁见智了,使用if let在解包失败后也可以继续执行剩下的代码,但if let使用过多可能会出现if嵌套地狱,而guard虽然可以减少嵌套层数,但是guard不成立时必须要执行退出当前流程的语句(return或throw等,如果在循环中还可以使用break和continue)。

强制解包

除了if letguard let,Swift还提供了强制解包的方法,也就是文章第一段中提到的叹号,使用叹号可以直接解包得到一个非Optional的对象。

强制解包写起来是很方便,但是开发中要谨慎使用,因为当text为nil的时候,解包失败会导致应用crash,Swift的文档对于强制解包的描述最后一句有说明“对一个nil进行强制解包会发生运行时错误”,因此在开发当中,除非能确认某个Optional值在使用时绝对不会是nil,否则请不要使用叹号进行强制解包。

提供默认值

除了通过以上方式进行解包以外,Swift还提供了??运算符用于解包并提供默认值。

某些时候我们并不关心某个变量是否为nil,我们只希望代码能不出问题地执行下去,比如上面的代码,当text为nil的时候调用printCount时在控制台中打印0也是可以接受的结果,这时候使用??提供默认值要比if letguard let以及强制解包要更加合适。

Optional Chaining

有的读者读到这里的时候可能就想问了,“前面解释了叹号的作用,那问号呢?”,不要着急问号这就来了。

在OC中如果某个变量是nil时,调用这个变量则会无事发生,Swift中也提供了一个类似的机制,那就是Optional的问号操作符,使用问号操作符对Optional中的对象进行操作就构成了一个可选链。

上边的代码中使用了问号操作符并调用了node的next属性,问号操作符会先判断该node是否为nil,如果不为nil则解包并继续执行,如果是nil则会打断当前的可选链并返回nil,所以虽然ListNode类中的text属性不是Optional类型,但node?.next?.next?.text的返回值的却是Optional的。

为了展示可选链被打断的情况,我们给ListNode类增加一个方法并测试一下,看一下结果如何。

调用testBreakOptionalChaining并且传入了一个链表的头节点,该链表只有两个节点,第一个节点的text值为”a”,第二个节点的text值为”b”,此时控制台输出如下。

虽然在代码中链式调用了四次printTextAndGoNext方法,但是控制台中植输出了两个值,因为第二个节点的next节点为nil,所以实际上后边的两次调用并没有被执行,这个可选链被打断了。

问号叹号小剧场

上文中介绍了使用叹号进行强制解包和使用问号来写一个可选链,但其实问号和叹号除了在这两个地方会被用到,在声明一个Optional类型的时候也可以使用问号和叹号,下面是声明Optional类型的几种方式。

在开发中并不建议使用方式1来声明Optional类型,应该使用Swift提供的“类型+问号/叹号”声明Optional类型的语法糖。

在声明Optional类型的变量时,问号和叹号的效果也是不一样的,使用问号声明的效果与方法1等同,使用叹号声明则有一些小小的差别,使用叹号声明的Optional类型,在被调用时默认会被强制解包。

因此使用“类型+叹号”声明Optional类型的方式在实际开发中应当谨慎使用,当然也不是说完全不能用,总结一下,如果某个变量/属性在使用时随时可能为nil,那么就应该使用“类型+问号”来声明;如果某个变量/属性在开始的时候可能为nil,但是后续使用的时候必定会有值,那么就可以使用“类型+叹号”来声明。

Tips:UIViewController的view属性就是使用叹号声明的(var view: UIView!),在init时候为nil,但是随后执行loadView后就有了值。

结语

说了那么多,其实Optional很简单,它就像是一个箱子,在打开箱子之前你不知道里面有没有东西,使用问号就相当于告诉你箱子里可能有东西也可能什么也没有,所以你在打开的时候需要检查一下到底是有还是没有,而使用叹号就相当于告诉你这个箱子里是有东西的,打开的时候不需要检查了,所以你打开箱子的时候看到空空如也的箱子就会产生混乱(运行时错误),并不知道这箱子里的东西是被偷了还是对方骗了你。

点赞
  1. Little Vegetable Chicken说道:

    Oh, this is the most fantastic blog I've ever seen!

发表评论

电子邮件地址不会被公开。 必填项已用*标注