Swift中!和?的区别
毋庸置疑,这是学习Swift
最基本的知识点,但在实际的使用中我们往往容易忽略它们做为类型本身的差异,即使我们之前都曾了然过,因为我们在可选这个方面用得最多的是解包方面的操作符:!和??。这篇博文主要就上面这个类型差异以及Swift
与Objective-C
在混编时需要注意的事项展开,后者主要涉及两个编译器标识符:_Nonnull
和_Nullable
。
从IBOutlet中一窥究竟
细心的朋友们会发现,在Swift
中,Storyboard/Xib
中控件的Outlet
的类型全部都是以!结尾的,就像这样:1
@IBOutlet weak var resultIconView: UIImageView!
这里不难理解控件的输出口都是可选类型,因为控件是属于Storyboard/Xib
的,而在建立与placeholder
的连接的时候,该控件可能会因为某种原因而已经被移除了或者暂时没有这个连接(本质上是通过KVC
来完成关联的)。而我们在使用该输出口的时候,却并不需要进行解包。想必说到这里你已经回想起如何来回答像文章题目这样的问题:使用!声明的可选类型,表明其一定有值,相当于?声明的可选类型在使用时进行!强制解包。
如果此时它的为nil
然后又参与了额外的计算,那么程序就会崩溃!考虑如下代码:1
2
3
4
5
6var str: String = "a"
var a: Int! = Int(str)
// 如果有值的话,打印的结果还是optional(值),因为强制解包可选是可选的一种类型
print(a)
let b: Int = a + 1
通常情况下,控件的IBOutlet
是一定可以和控件本身关联成功的,所以这里使用!可以帮助我们去掉在使用时的解包操作。而对于暂时没有连接
的情况,使用!就会大问题了,即unexpectedly found nil while unwrapping an Optional value
。那么这种使用场景什么时候会出现?
这里举一个典型的🌰,在完成扇贝
的大耳狐英语
中关联家长的界面时,要求如果在iPhone
上,需要有一个将二维码保存到手机相册的按钮,而在iPad
中则没有这个按钮。在这个项目启动时,开发进度比较赶,我们就选择使用Storyboard
来快速完成静态界面的搭建。而为了区分iPhone
和iPad
,我们在init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
中,通过判断当前设备的类型,从而加载不同的Xib
文件。然后为了使用同一个Swift
文件,我们就把这种不存在的输出口的类型声明为?,也就是把原来的!改为?,如下:1
@IBOutlet weak var saveQRButton: UIButton?
去掉不安全且烦人的感叹号!
特别地,在Swift
中混编Objective-C
,当遇到调用的API
有返回值时,如果没有在API
中明确说明,返回值的类型都将会被标记为可选强制解析类型
。显然这会留下安全隐患,因为Objective-C
可能返回nil
。为了防患于未然,我们可以给其加上编译器标识符,就像这样:1
- (NSString * _Nullable)userName;
当然,这只是在编译器层面规避了问题,但是由于Objective-C
弱类型的特点,在_Nonnull
的修饰下,即便你返回一个nil
,你最多也只能得到一个⚠️,而如果返回的结果是要经过计算的,那么这个Warning
也会被隐藏掉。所以,要是你不能百分百保证其是_Nonnull
,那么建议你都使用_Nullable
。这样在Swift
中使用时,虽然你会多一步解包,但会更加安全。