Swift中的Optionals

问题

在Objective-C中实例对象会被自动初始化成nil,nil是一个指向一个不存在对象的指针,而且还可以对nil发送消息,当然消息不会被执行,就像对一个不存在的人发送命令一样,但同样也不会有导致任何问题,程序继续运行。例如:

NSString *string;
NSLog(@"uppercase: %@", string.uppercaseString);

程序不会崩溃,会打印出uppercase: (null)

同样的代码放到Swift:

var string: String
println(string.uppercaseString)

编译器直接报错Variable 'string' used before been initialized.根据错误提示,尝试去初始化string:

var string: String = nil
println(string.uppercaseString)

好吧,问题来了,编译器继续提示错误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{
//the someOption contains a value
}
else{
//someOption contains non value
}

Forced Unwrapping Optional

当确定了optional包含了一个值的时候,就可以放心的使用它,使用的时候在optional后面加上!,表示这个optional包含着的那个值:

if someOptional != nil{
println("there is an value: \(someOptional!)")
}
else{
println("there is no value.")
}

Implicitly Unwrapped Optionals

有些时候对已经赋值了的Optional,我们很确定它一定是有值的,就没有必要再去每次检查和判断是否有值了,就可以在类型的后面加上!来表示确定有值存在的Optional,这种类型的变量就叫做:Implicitly Unwrapped Optional.可以理解成一种当成普通变量来使用的optional变量,在使用的时候自动unwrapped,而不需要每次使用都加!来强制解包。

let possibleString: String? = "an optional string"
let forcedString = possibleString! //need forced unwrapped

let assumedString: String! = "an implicitly unwrapped string"
let implicatedString = assumedString //no need to unwrapped

当然也可以跟检查optional一样去检查implicitly unwrapped optional

//to check
if assumedString != nil{
println(assumedString)
}

//to optional bind
if let definiteString = assumedString{
println(definiteString)
}

如果尝试去使用一个并不包含任何职的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{
println("there is a value: \(constName)")
}

Nil Coalesce Operator

a ?? b,其中a是一个Optional,如果a有值那么返回a的值,如果a为nil返回默认值b,b的值也需要跟a的值相对应。??其实就是三元操作符的简写:

a ?? b
a != nil ? 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。


struct Order{
let orderNum: Int
let person: Person?
//...
}

struct Person{
let name: String
let address: String?
//...
}

struct Address{
let streetName: String
let city: String
let state: String?
//...
}

对于给定的order,如何找到消费者的地址的state呢?可以强制解包:

order.person!.address!.state!

然后如果其中有一个值为nil就导致了运行时的错误。然后可以尝试之前推荐的Optional Bind:

if let aPerson = order.person{
if let aAddress = aPerson.address{
if let aState = aAddress.state{
//...
}
}
}

这样做是很安全的,但你也看到了,写法太复杂了。所以Optional Chaining是这个场景最合适的,还可以跟Optional Bind结合起来:

if let aState = order.person?.address?.state?{
print("the order' state is \(aState)"
}
else{
print("Unknown person, address or 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{

NSParameterAssert(country);
if ([self checkCountry:country]) {
NSString *capital = self.capitalCountryDic[country];
return [[NSAttributedString alloc] initWithString:capital attributes:@{/* ... */}];
}
return nil;
}

- (BOOL)checkCountry:(NSString *)country{

NSParameterAssert(country);
if ([self.capitalCountryDic.allKeys containsObject:country]) {
return YES;
}
return NO;
}

checkCountry中,还是要继续去检查参数,尽管调用它的时候刚刚检查过,但不能去提供一个不可靠的接口,所以还是得检查参数。这个问题在Swift中就不会出现:

func attributedCapital(country: String) -> NSAttributedString?

函数直接就只接受String类型的参数,保证了接受到的参数不是nil。也就是把错误提前了,在传递nil的适合编译器就会报错。既然函数只能处理非空的值,就显式的让调用者去处理为空的情况。