Tino又想吃肉了

Optional与解包

Word count: 1.3kReading time: 4 min
2021/04/08

谈谈swift中的Optional类型与如何正确解包

首先,在了解解包之前,我们来了解什么是Optional类型。

对于var num : Int而言,它声明了一个名为num的整形变量,而对于var num : Int?而言,这时它声明的是一个什么变量呢?没错,它不是一个Int变量了,它是一个Optional变量,更具体地来说,一个Optional<Int>变量。

可选型Optional的存在,是swift区别与Objective-C的一大特点,这个特性有效减小了程序崩溃的概率,然而在我们第一次接触的时候,它也大大提高了编码难度。

Optional与解包

本质上,Optional在底层是一个带泛型参数的enum枚举类型,这使它可以为nil
那么有几种解包的方法呢?

  • 强制解包
    最符合我们第一直觉的可能是强制解包。然而它并不安全。强制解包使用!作为运算符,它表示程序员向编译器保证这个可选型在程序运行中永远不会为nil值,那么当然,如果它碰巧是一个nil值的话,将可能会导致程序崩溃。
    并且这个方式使我们在每个需要用到这个可选型的时候都需要强制解包一次,增加了很多无谓的代码量,也埋下了很多隐患。

  • if let
    if let方法是一个安全的解包方式,以上文中的num为例子,我们可以使用if let来对它解包,形如if let unwrappedNum = num{},当解包成功时,就会执行花括号里的代码。

  • guard let
    同样的,guard let也是一个跟if let相似的安全解包方式,形如guard let unwrappedNum = num else{throw error/return nil}

在使用if let或者guard let解包之后,我们就可以使用解包得到的unwrappedNum,不用每次需要使用都解包一次

可选链

可选链的概念在于我们可以同时解包与调用方法,如果可选型为nil值就不会执行后面的方法,所以这是一种安全且方便的方法,因为你不用先解包再调用方法。
然而,在实践中,当我们定义一个类,里面包含了一个非可选型的val:Int和一个可选型的next:class,我们就当这个例子是链表类。那么,当我们通过可选链获取下一个节点的val时,会发生什么呢?
形如head?.val = head?.next?.val

然而,这样并不能通过编译,Xcode会要求你对head?.next?.val解包。

因为通过一个可选链,它返回的类型必然是一个可选链类型,即使该类型在定义里是非可选类型

那么,这时你想:那我给他解包不就好了。我们先试试强制解包。head?.next?.val!

然而,编译器又报错了。Cannot force unwrap value of non-optional type 'Int'

这时你可能很疑惑,不是说可选链的返回值必然是可选型吗?为什么我们不能直接强制解包呢?

其实,问题就出在这里。直接在val后面打上问号,是对val强制解包,不是对返回值强制解包,正确的写法应该是(head?.next?.val)!.这时候编译器终于不报错了。

然而,当你接下来,想要获取下一个节点时,你写了这样的代码head?.next = head?.next?.next。发现,Xcode并没有报错!。这是为什么呢?

其实,我们在等式的左边,由于没有括号,所以我们赋值的对象是val和next,而从可选链返回为可选型这个规则,其实是(head?.val)。所以第一次,我们向val赋值,等式右边必须是一个Int类型的值,也就是我们必须强制解包或者给它一个非可选型的默认值。而第二次,next在定义里已经是一个可选型,那么我们就可以赋一个可选型给它。(这也是某位同学卡住的点)

思考

  • 解包的确在编写程序的角度上给我们带来了一些麻烦,比如当一个可选链很长时如何正确地使用?!??
  • 上面的讨论还存在一个问题,就是(head?.val)是一个可选型,通过print语句可以验证。然而,当我们使用括号括起来后,我们对它赋值,形如(head?.val) = head?.next?.val。这时候Xcode又报错了。然而这次的报错很值得玩味,Cannot assign to immutable expression of type 'Int?'. 这进一步验证了可选链的返回类型是可选型这一说法,然而我们却不能向可选型赋值,它是immutable的。那么当我们在head?.next = head?.next?.next这里,对可选型next赋值时,底层里发生了什么呢?由于Optional类型本质上只是一个enum类型,那么它是怎么封装原本的类型的,并且封装后,Optional类型真的跟原本的类型完全解耦了吗?在解包时,Optional类型又是怎么剖离出原本的类型的?

这些都是笔者还没发解答的问题,或许得等到有了读懂源码的水平后才能解答了…


Tino Wu

CATALOG
  1. 1. 首先,在了解解包之前,我们来了解什么是Optional类型。
  2. 2. Optional与解包
  3. 3. 可选链
  4. 4. 思考