Swift语言中的常量变量的声明和使用方式借鉴了像Ruby这样的动态语言,也保留了静态语言的一些特性。
- 使用let定义常量
- 使用var定义变量
- 普通值类型的变量声明之后不会赋默认值,必须初始化才能使用
- 除普通值类型外,还可以声明Optional值类型的变量,也就是本文所讲的内容。
Swift的常量变量使用中,Optional值理解起来是比较困难的,尤其是?和!这样的语法糖,代码写起来赏心悦目,但是学习起来就需要下点功夫。
?和!只是Optional值的语法糖,而!可以看做是?的进一步的特殊形式,所以后面说明的顺序是:Optional值 -> ?语法糖 -> !语法糖 -> 使用注意事项
什么是Optinal值(?语法糖)
首先,我们来声明一个普通值的变量。
var str : String
不给str赋初值,直接使用的话编译器会报错:
// error: Variable 'str' used before being initialized
print(str)
如果我们这样定义,就没有问题了。
var str2 : Optional
// 通常我们使用?语法糖来实现上面的语法:
// var str2 : String?
print(str2) // "nil\n"
"Optional<String>"就是一个Optional值,通过这个格式可以看出:Optional本身是一个枚举,<String>是这个枚举里面定义的一个泛型。
Optional在Swift2.0中的定义如下:
public enum Optional
case None
case Some(Wrapped)
/// Construct a nil
instance.
public init()
/// Construct a non-nil
instance that stores some
.
public init( some: Wrapped)
/// If self == nil
, returns nil
. Otherwise, returns f(self!)
.
@warn_unused_result
@rethrows public func map(@noescape f: (Wrapped) throws -> U) rethrows -> U?
/// Returns nil
if self
is nil, f(self!)
otherwise.
@warn_unused_result
@rethrows public func flatMap(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
/// Create an instance initialized with nil
.
public init(nilLiteral: ())
}
通过上面的定义我们可以看到两个枚举类型:.None和.Some(Wrapped)。这样就很好理解了,声明的时候如果使用了?或者!,这个变量的类型就不简简单单是String类型,而是一个Optinal.Some
封装和拆包
其实,Swift里面的nil就是Optional.None;非nil就是Optional.Some(Wrapped),即变量里面存储的不是普通值(原始值),而是对原始值的封装(Wrap),这也是Optional值类型变量在使用时需要先**拆包(Unwrap)**的原因。
因此 var str2 : Optional
// if (str2 != nil)
if (str2 != Optional.None) { // 与上面意思相等
...
}
这里强调一点是Swift2.0里面Optional值不能直接用作判断条件,必须和nil比较,如下的语句编译器会报错:
// error: Optinal type cannot be used as a boolean.
// if str2 {
Optinal值还可以直接打印:
print(str2) // "nil\n"
Optinal值的使用(拆包)
如果要作为普通值使用,例如方法调用、属性访问、下标访问、打印原始值,则需要先拆包,再使用。由于Swift里面没有显示的拆包方法,因此需要通过?和!两个语法糖来实现。
- 属性访问,?的作用可以理解为不是简单的拆包,而是先判断当前变量是否为nil,如果为nil,则忽略后续操作,如果不为nil,则执行后面的操作。
str2?.capitalizedString // nil
// 此处由于在整个上下文中我还没有对str2赋值,所以编译器会报错
// error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION
// str2!.capitalizedString
str2 = "test"
str2!.capitalizedString // "Test" - 方法调用,在方法调用的时候?的作用类似于OC中的isResponseToSelector:,如果是nil则忽略后面的方法调用,如果非nil则执行方法调用。
var str3 : String?
str3 = "hello"
print(str3) // "Optional("hello")\n"
str3?.appendContentsOf(" swift")
print(str3) // "Optional("hello swift")\n" - 下标访问
var ary1 : Array?
// 未经拆包访问下标编译器会报错
// error: Cannot subscript a value from type 'Array?'
// ary1[0]
ary1?[0] // nil
ary1 = [1, 2, 3]
ary1?[1] // 2 - 打印原始值,打印时不能用?号,Swift中?号只能用来访问属性、调用方法、访问下标元素。
var str4 : String?
// error: '?' must be followed by a call, member lookup, or subscript
// print(str4?)
只能用!打印Optional的原始值,当然,如果什么都不加,则打印的是原始值本身。
str4 = "make a cup of tea"
print(str4!) // "make a cup of tea\n"
print(str4) // "Optional("make a cup of tea")\n"
打印未主动初始化的Optinal值同样会报错。
var str5 : String?
// error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION
// print(str5!)
以上就是?语法糖(即Optinal值)在变量声明和变量使用两个方面的用法,有了上面的基础,!语法糖可以简单总结为两句话:
!语法糖
- 声明时指定!,则表示这个变量不仅是一个Optinal值,并被初始化为nil,而且这个Optinal值在使用的时候都会被编译器隐式的加上!,即强制拆包。
var str6 : String!
str6 = "str6"
print(str6) // "str6\n"
注意到上面打印出来的是拆包后的内容,而不是"Optinal("str6")\n"。
隐式拆包的Optinal值(Implicitly Unwrapped Optionals)的调用方式可以有如下三种,当然通常我们使用的是第一种:
str6.capitalizedString // "Str6"
str6?.capitalizedString // "Str6"
str6!.capitalizedString // "Str6"
前面提到!只不过是一种语法糖,真正对应的其实是ImplicitlyUnwrappedOptional这种枚举类型,如下两种方式声明得到的结果是一样的:
// var str7 : String!
var str7 : ImplicitlyUnwrappedOptional - 使用时加上!号表示强制拆包,由于对隐式拆包的Optinal值强制拆包是没有意义的(虽然编译上没有报错),因此这里的强制拆包通常用于使用?声明的Optinal值。
var str8 : String?
str8 = "Swift"
if (str8 != nil) {
// if判断从逻辑上保证str8不为空,可以强制拆包
str8!.appendContentsOf(" is fun")
}
值得注意的是:对于nil强制拆包会导致Crash。因此**!这个东西是把双刃剑**,有过大型程序经验的同学都了解:今天的逻辑保证x不为空,不代表明天的逻辑还能保证x不为空。
?和!用于类型转换
?和!还有另一种用法就是用于类型转换,看下下面的例子。
var anybody : AnyObject
anybody = "still a String"
如果要把AnyObject赋值给一个Optinal.Some
var justTry : String?
justTry = anybody as? String // "still a String"
如果要把AnyObject赋值给一个普通String,则需要as后面跟上!来强制类型转换,!的作用是强制转换并赋值给secondTry。
var secondTry : String
secondTry = anybody as! String // "still a String"
如果我们这里重新给anybody赋一个Int值,那么发现!转换编译器会报错,而?转换的结果为nil
anybody = 1
// error: Execution was interrupted, reason: singal SIGABRT.
// let anotherTry = anybody as! String
let tryAgain = anybody as? String // nil
// 不带?和!的转换同样会报错
//let failedTry = anybody as String
小贴士
- 在思考Optinal值的nil比较时突然想到既然普通值不能与nil比较,那么普通值如何判断是否为nil呢?再细一想是自己犯二了,普通值在声明的时候compile-time就保证了不会为空,自然也不需要判断;
- 上面已经提到在Swift中,nil其实是一个枚举值Optional.None,还有一个有意思的值:Void,它其实是(),一个空元组;
- 强制拆包(!)不要轻易使用,倒也还没想到必须使用的场景,不知道后续会不会出现关于这一点的开发规范;
- 可以考虑Optinal值在定义的时候增加一个特殊的命名规范和普通值加以区分,虽然现在编译器给的错误提示挺全面,但是代码的可阅读性也需要考虑。
什么时候需要Optional值
- 属性的初始化操作不在声明时和init中进行时;
- 对已定义的Optinal变量使用时;
- 对变量做类型转换时;
- 当然,任何想用的时候只要得到编译器的同意都可以用。