在Swift代码会经常看到定义属性或方法参数时类型后面会紧跟一个感叹号( ! )或问号( ? ), 刚开始接触Swift的童鞋就可能不太明白之代表什么意思,一头雾水,开始凌乱了。
本文将带你了解感叹号( ! )与问号( ? )之谜,首先问号( ? )是可选类型,是用来处理值可能缺失的情况,也就是没有值的情况(也就是OC中NULL); 感叹号( ! )就是与之相反的, 就是一定有值(非可选类型),不存在空值的情况(OC中NULL)。
可选类型(Optional Type)
常量或变量声明的时候类型后面带有?或!为可选类型
- 声明为Optional常量时,如果没有赋初始值,次常量没有默认值,需要在构造函数中给常量设置初始值
- 声明为Optional的变量,如果不显示的赋值就会有个默认值nil
- 声明为Optional的变量或常量不能直接参与运算,必须解包后才能参与运算
- 解包后的Optional变量或常量如果没有值(值为nil)参与运算会发生Crash
- 可选类型类似于Objective-C中的nil,但是nil只针对类有用,而Swift中可选类型对所有的类型都可使用,并且更安全
下面根据不同使用场景来进一步了解感叹号( ! )与问号( ? )之谜
强制展开
如果一个可选有值,也就是它不等于nil, 可以通过比较nil来判断一个可选中是否包含值
一旦确定可选中包含值,可以使用后缀操作符!来强制拆包访问这个值,感叹号的意思就是说"我知道这个可选项里面有值,放心使用吧"这就是所谓的可选值的强制展开
var myString:String?
myString = "Hello, Swift!"
if myString != nil {
print(myString) //prints:Optional("Hello, Swift!")
// 强制展开
print(myString!) //prints:Hello, Swift!
}else{
print("myString 值为 nil")
}
如果使用!来获取一个不存在的可选值会导致Crash,因此在使用!强制展开之前必须确保可选项中包含一个非nil的值。
可选项绑定
可以使用可选项绑定来判断可选类型是否包含值,如果包含就把值赋给一个临时的常量或变量。可选绑定可以与if和while的语句使用来检查可选项内部的值,并赋值给一个变量或常量。
var myString:String?
myString = "Hello, Swift!"
if let yourString = myString {
print("你的字符串值为 - \(yourString)")
}else{
print("你的字符串没有值")
}
//prints:你的字符串值为 - Hello, Swift!
隐式展开
隐式展开可选项:通过在类型后面添加一个叹号(!)而不是问号(?)
你可以把隐式展开可选项当作每次访问它的时候被给予了自动进行展开的权限(编译器自动在操作前补上一个!进行拆包,然后在执行后面的操作,当然如果改值为nil,也一样会报错发生Crash),这样就可以在声明可选项的时候添加一个叹号,而不是每次调用的时候在可选项后边添加一个叹号。
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // requires an exclamation mark
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // no need for an exclamation mark
你可以像对待普通可选一样对待隐式展开可选项来检查里边是否包含一个值,也可以通过可选绑定来检查和展开隐式展开可选项:
//检查可选项是否包含一个值
if assumedString != nil {
print(assumedString)
}
//通过可选绑定来检查和展开隐式展开可选项
if let definiteString = assumedString {
print(definiteString)
}
什么时候使用隐式解析可选类型
- 初始化过程中不能确定有正确的值,但是又可以确保在被访问前一定会有值。
- 使用可选变量不会有问题,因为在可选变量在初始化时会自动赋予nil,并且在初始化结束时被赋予正确的值。但是使用时的强制解包会很痛苦,满屏都是!
以下展示了适合使用隐式解析可选选项的例子:
class MyController: UIViewController {
@IBOutlet var button: UIButton!
var buttonOriginalWidth: CGFloat!
override func viewDidLoad() {
self.buttonOriginalWidth = self.button.frame.size.width
}
}
在这里变量button不是在初始化时赋值的,而是在加载视图的时候。你可以把它设置为普通可选类型,但是如果这个视图加载正确,它是不会为空的。
在视图加载完成之前,我们无法知道button的初始化宽度,但是我们知道viewDidLoad方法会在任何方法之前被调用(除了初始化函数)。与其在所有地方强制解包,不如定义成隐式解析可选类型。
合并空值运算符
上面对可选类型的三种处理不是需要繁琐的判断就是存在Crash风险不够安全,那么??的出现很好的解决了这个问题。
合并空值运算符 (a ?? b )如果可选项 a 有值则展开,如果没有值,是 nil ,则返回默认值 b 。表达式 a 必须是一个可选类型。表达式 b 必须与 a 的储存类型相同。
合并空值运算符是下边代码的缩写:a != nil ? a! : b
let defaultColorName = "red"
var userDefinedColorName: String? // defaults to nil
var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName is nil, so colorNameToUse is set to the default of "red"
异常处理 try? 和 try!
try?: 如果不想出处理异常,那么可以使用这个关键字,使用这个关键字会返回一个可选类型,如果有异常出现,返回nil,否则返回可选值。例如:
enum MyError : Error {
case one
case two
case three
}
func testFunc(str: String) throws -> String {
if str == "one" {
throw MyError.one
}else if str == "two" {
throw MyError.two
}else if str == "three" {
throw MyError.three
}
return "ok"
}
var str = try? testFunc(str: "three")
print(str)
控制台输出:
nil
try!: 有点类似NSAssert(),使用try!后,一旦可能抛出异常的方法中抛出了异常,那么程序会立刻停止