问题
在Objective-C中实例对象会被自动初始化成nil,nil是一个指向一个不存在对象的指针,而且还可以对nil发送消息,当然消息不会被执行,就像对一个不存在的人发送命令一样,但同样也不会有导致任何问题,程序继续运行。例如:
NSString *string; |
程序不会崩溃,会打印出uppercase: (null)
同样的代码放到Swift:
var string: String |
编译器直接报错Variable 'string' used before been initialized
.根据错误提示,尝试去初始化string:
var string: String = nil |
好吧,问题来了,编译器继续提示错误Type 'String' does not conform to potocol NilLiteralConvertible
Optionals
上面的问题的原因是,Swift为了安全考虑,所有普通的变量和常量都必须包含一个值,而不能是没有值(nil)。但是有些时候确实是需要nil来指定变量暂时是没有值的,这就是Swift的Optional,可以指定任意的类型为Optional的,直接在类型后面加上?
即可:
var string: String? |
如果没有对Optional类型的变量提供初始值,那么就会自动被初始化成为nil,原来OC中的类型是的Optional类型,而Swift中特意把可能缺失值的类型单独拿出来作为一个特殊的类型来处理,这样在写代码的时候,你就可以很清楚的指定当前操作的变量是不是可能为nil。
当值可能会是缺失的时候,你就需要用到Optional,它就表明有两种情况,一是它有个值,就是x;或者它的值不存在,用nil来指示。
并且需要注意的是,Swift中的nil和OC中的nil是有区别的:OC中的nil是指向不存在对象的一个指针,而Swift中的nil是特定类型的的值的一种缺失状态的表示,所有类型的值缺失都用nil来表示,不仅仅对类的实例,Int都可以的缺失也可以用nil来表示,这个在OC中是用NSNotFound来表示。
Unwrapping an Optional
在使用Optional的时候,首先得先确定它是否包含了一个值,这个可以直接用和nil之间的比较来完成:
if someOptional != nil{ |
Forced Unwrapping Optional
当确定了optional包含了一个值的时候,就可以放心的使用它,使用的时候在optional后面加上!
,表示这个optional包含着的那个值:
if someOptional != nil{ |
Implicitly Unwrapped Optionals
有些时候对已经赋值了的Optional,我们很确定它一定是有值的,就没有必要再去每次检查和判断是否有值了,就可以在类型的后面加上!
来表示确定有值存在的Optional,这种类型的变量就叫做:Implicitly Unwrapped Optional.可以理解成一种当成普通变量来使用的optional变量,在使用的时候自动unwrapped,而不需要每次使用都加!
来强制解包。
let possibleString: String? = "an optional string" |
当然也可以跟检查optional一样去检查implicitly unwrapped optional
//to check |
如果尝试去使用一个并不包含任何职的implicitly unwrapped optional,就会导致运行时错误,程序直接崩溃。所以如果不确定有值,就用普通的Optional
在使用implicitly unwrapped optional的场景中,最常见也最熟悉的就是IBOutlet变量了,拖拽一个UIButton到代码中,就自动生成了如下代码:
@IBOutlet weak var button: UIButton! |
button的类型为implicitly unwrapped optional,因为outlet不能保证button就一定是存在的,比如在storyboard里面被删掉了,所以是Optional的;但是不能每次使用button都得去unwrapped,这样太麻烦,而且这个button就是应该存在的,如果不存在,要么是代码有问题,要么是storyboard有问题,所以implicitly unwrapped optional就是最好的选择。如果button为nil而又去使用,程序会崩溃,但这也就发现了问题所在。
Optional Bind
无论是没有进行检查的强制解包还是隐式解包,都可能会导致运行时的错误,所以我们更加推荐使用Optional绑定来显示的处理可能缺失值的问题。绑定的方法是用一个临时的变量来存储optional里面的那个值:
if let constName = someOptional{ |
Nil Coalesce Operator
a ?? b
,其中a是一个Optional,如果a有值那么返回a的值,如果a为nil返回默认值b,b的值也需要跟a的值相对应。??
其实就是三元操作符的简写:
a ?? b |
Optional Chaining
当直接在Optional上调用方法属性或者下标时,这个过程就是Optional Chaining。如果该Optional包含着一个值,相应的调用就成功。而如果没有值,那么调用就直接返回nil。Optional Chaining也可以用在多重调用上,只要其中的一个调用返回nil,整个调用就也返回nil。Optional Chaining跟Objective-C中的向nil发送消息的机制很像,这里的区别还是之前跟nil的区别一样,Optional Chaining对任何类型都ok,而不仅仅是对象类型。
使用Optional Chaining只需要在Optional变量后面加上?
,然后继续调用方法属性或者下标即可,这跟Forced Unwrapping Optional很类似,只是把!
换成了?
,区别就在于强制解包的变量如果是nil就会导致运行时错误,而Optional Chaining不会,只是相应的返回nil。
|
对于给定的order,如何找到消费者的地址的state呢?可以强制解包:
order.person!.address!.state! |
然后如果其中有一个值为nil就导致了运行时的错误。然后可以尝试之前推荐的Optional Bind:
if let aPerson = order.person{ |
这样做是很安全的,但你也看到了,写法太复杂了。所以Optional Chaining是这个场景最合适的,还可以跟Optional Bind结合起来:
if let aState = order.person?.address?.state?{ |
总结
以前在Objective-C里面的一个nil的指针,现在出来了这么多的写法,稍微总结一下就是四种形式:
?
跟在变量后面:Optional Chaining?
跟在类型的后面:Optionals!
跟在变量后面:Forced Unwrapping Optional!
跟在类型后面:Implicitly Unwrapped Optionals
使用Optional的时候应该显示的处理值为空的情况,Optional Bind就是这样。Nil Coalesce Operator也给Optional的访问提供了一个默认值。
为什么需要一个这么复杂的Optional呢?Swift的类型系统非常的严格,不仅默认了所有常规的变量都是必须有值的,而且严格要求处理Optional值缺失的情况。而在Objective-C中,可以把nil完全当做成一个常规的对象来使用。
Optional的提出更加让Swift用于了静态安全性,在代码运行之前,就避免了很多的错误,更好的规避了很多运行时错误。在Objective-C中,为了确保参数合理,很多时候需要重复的去检查参数:
- (NSAttributedString *)attributedCapital:(NSString *)country{ |
在checkCountry
中,还是要继续去检查参数,尽管调用它的时候刚刚检查过,但不能去提供一个不可靠的接口,所以还是得检查参数。这个问题在Swift中就不会出现:
func attributedCapital(country: String) -> NSAttributedString? |
函数直接就只接受String
类型的参数,保证了接受到的参数不是nil
。也就是把错误提前了,在传递nil
的适合编译器就会报错。既然函数只能处理非空的值,就显式的让调用者去处理为空的情况。