Swift学习笔记 | Optional
Optional###
Swift中的Optional作为一种类型,既可以存储一个值,也可以为空(也就是swift里的nil),通常在类型后面加一个?表示它是Optional类型的:
var number: Int? = 32
其实?
只不过是一个语法糖,Optional的实际类型是一个enum:
enum Optional<T>: _Reflectable, NilLiteralConvertible {
case None
case Some(T)
//...
}
上面的var number: Int? = 32
也就可以表示为:
var numbet: Optional<Int> = 32
当然我们习惯上更习惯于表示为Int?
Optional的作用###
一个Optional对象只存在两种状态:包含一个值,或者为空,我们都可以通过解包(unwrap)来获取。下面一段出自The Swift Programming Language (Swift 3)
The concept of optionals doesn’t exist in C or Objective-C. The nearest thing in Objective-C is the ability to return nil from a method that would otherwise return an object, with nil meaning “the absence of a valid object.” However, this only works for objects—it doesn’t work for structures, basic C types, or enumeration values. For these types, Objective-C methods typically return a special value (such as NSNotFound) to indicate the absence of a value. This approach assumes that the method’s caller knows there is a special value to test against and remembers to check for it. Swift’s optionals let you indicate the absence of a value for any type at all, without the need for special constants.
Swift通过引入Optional解决了Objective-C中“有”与“无”的问题,使代码的安全性得到了很大的提高,同时我们也应该知道,Swift是一种类型安全的语言。
在某些场景下,Optional能起到很大的作用:
- 当一些属性值可以为空时,比如一个Person类中,middleName,spouse这类的属性都可以为空
- 当一个方法可以返回空值,比如类型转换函数,官方文档的例子
//try to convert a String into an Int
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber is inferred to be of type "Int?", or "optional Int"
// 如果possibleNumber 是“hello”,则转换不会成功,就会返回nil
- 如果在一个字典中,使用key获取对应的value时,返回的也应该是Optional的值,因为你也可能找不到key对应的value,此时返回nil
- 一个方法可以返回一个值,如果方法内部产生了错误,也可以什么都不返回
- Delegate 属性(不总是需要被赋值)
- class中weak 类型的属性,他们所指向的值可以为空
- 一个大的资源可以随时被释放,以节约空间,所以正常情况下都是空的,只有使用时才会请求赋值。
Optional Binding###
出于类型安全的考虑,我们不能再把Optional当作Boolean值处理。像下面这条语句在swift中会遇到编译错误
var myString: String? = "Hello"
if myString {
print(myString)
}
但是你可以通过==
和!=
,将Optional值和nil做比较来判断它是否包含一个值。如果不包含任何值,则为空。
if myString != nil{
print("myString contain a string value of \(myString!)")
}
在上面的语句里,当我们确定myString包含一个值时,我们通过在myString后面添加一个!
来进行强制解包(forced unwrapping),获取Optional内包含的值。
但是实际上,Swift提供了一种更加方便的形式来完成这一过程,所谓的Optional Binding,看下面的代码:
if let actualString = myString {
print("myString contain a string value of \(actualString )")
} else {
print("myString is nil")
}
上面代码的意思是,如果optional string 包含一个值,我们就把这个值赋给actualString,然后就可以在if语句里继续使用它了,所以不再需要对其进行解包了,因为actualStr这样我们就用Optinal binding 代替了强制解包(forced unwrapping)。我们也可以使用if var actualString = myString 来获取actualString,则这个actualString就是var类型的。
隐式解包Optional###
相较于普通的Optional值,在Swift中我们还有一种特殊的Optional,在对它的成员或者方法进行访问时,编译器会自动进行解包,被称为隐式解包Optional(ImplicitlyUnwrappedOptional),在声明时,通过在类型后面添加!
来告诉编译器这是一个隐式解包Optional:
let possisbleString: String!
隐式解包的Optional本质上与普通的Optional值并没有什么不同,只是在访问时,编译器会自动帮我们完成在变量后插入!
的行为:
let possibleString: String! = "An implicity unwrapped optional string."
let implicitString: String = possibleString //此处我们不需要!来对possibleString 进行显示解包
很显然,隐式解包的写法会带来一个潜在的危险,如果尝试访问一个为空的隐式解包Optional, 就会遇到一个runtime error。那么Swift为什么要引入隐式解包Optional呢,王巍在他的Swift Tip解释了一下:
这一切都是历史的锅。因为Object-C中Cocoa的所有类型变量都是可以指向nil的,有一部分Cocoa的API中在参数或者返回时即使被声明为具体的类型,但是还是可能在某些特定的情况下是nil, 而同时也有另一部分API永远不会接受或者返回nil。在Objective-C时,这两种情况并没有加以区别,因为在OC中向nil发送消息是允许的,结果就是什么都不会发生,而在Cocoa API从OC转为Swift的module声明的自动化工具里,是无法判定是否存在nil的可能的,因此也无法决定哪些类型应该是实际的类型,而哪些类型应该声明为Optional。
在这种自动化转换中,最简单粗暴的应对方式是全部转为 Optional,然后让使用者通过 Optional Binding 来判断并使用。虽然这是最安全的方式,但对使用者来说是一件非常麻烦的事情,我猜不会有人喜欢每次用个 API 就在 Optional 和普通类型之间转来转去。这时候,隐式解包的 Optional 就作为一个妥协方案出现了。使用隐式解包 Optional 的最大好处是对于那些我们能确认的 API 来说,我们可直接进行属性访问和方法调用,会很方便。但是需要牢记在心的是,隐式解包不意味着 “这个变量不会是 nil,你可以放心使用” 这种暗示,只能说 Swift 通过这个特性给了我们一种简便但是危险的使用方式罢了。
我们可以在类初始化时使用隐式解包Optional,官方文档里这么描述的:Unowned References and Implicitly Unwrapped Optional Properties.
我们也可以像使用一个一般的Optional一样使用隐式解包Optional
比如判断是否为空:
if possibleString !=nil {
//some action
}
比如使用if let 来进行optional binding:
if let implicitString = possibleString {
print(implicitString)
}
当你知道变量可能为空的时候,不要使用隐式解包Optional。如果一个变量在它的声明周期里可能包含控制,那你总是需要使用一般的Optional
Optional Chaining###
Optional Chaining,如同名字一样,我们可以通过一个链来安全的访问一个Optional的属性或者方法。
可以参看一个例子:
if let isPNG = imagePaths["star"]?.hasSuffix(".png") {
print("The star image is in PNG format")
}
这里通过在imagePath["star"]后面添加?
来获取star
所对应的图片路径,如果不存在就直接是nil,如果存在则返回对应的path,然后紧接着调用path的hasSuffix方法。
使用Optional Chaining可以让我们摆脱很多不必要的判断和取值,从而精简代码。
??的使用###
当Optional解包后的值为nil时,我们可以通过使用??
来设置一个默认值。
let defaultImagePath = "/images/default.png"
let heartPath = imagePaths["heart"] ?? defaultImagePath
print(heartPath)
如果imagePaths中没有包含"heart"对应的value,则会把defaultImagePath赋给heartPath,也就会打印出/images/default.png
当然了,我们也可以将??
链接起来,设置多重默认值:
let shapePath = imagePaths["cir"] ?? imagePaths["squ"] ?? defaultImagePath
这样每一次解包值为nil时都会设置默认的值。