可选值
哨岗值
-
C代码
int ch; while ((ch = getchar()) != EOF) { printf("Read character %c\n", ch); } printf("Reached end-of-file\n"); 复制代码
-
C++ 代码
auto vec = {1,2,3}; auto iterator = std::find(vec.begin(),vec.end(),someValue); if (iterator != vec.end()) { std::cout << "vec contains" << *iterator << std:endl; } 复制代码
-
Java代码
int i = Integer.getInteger("123") 复制代码
-
Objective-C
[[NSString alloc] initWithContentsOfURL: url encodig: NSUTF8StringEncoding error: &error]; 复制代码
在上面的所有例子中,这些函数都返回了一个“魔法”数来表示并没有返回真实的值。这样的值被称为“哨岗值 (sentinel values)”
通过枚举解决魔法数的问题
-
大多数语言都支持某种形式的枚举类型,用它表达某个类型可能包含的所有值是一种更为安全的做法。Optional 也是通过枚举实现的
enum Optional<Wrapped> { case none case some(wrapped) } 复制代码
可选值概览
- 可选值在 Swift 中有很多来自语言内建的支持
if let
- **if let ** 语句会检查可选值是否为 nil,如果不是nil,便会解包可选值。
while let
- while let 语句和 if let 非常相似,它表示当一个条件返回 nil 时便终止循环。
双重可选值
-
一个可选值的包装类型是也是一个可选值的情况,这会导致可选值的嵌套。
-
基于 case 的模式匹配可以让我们把在 switch 的匹配中用到的规则同样地用到 if,for 和 while上去。最有用的场景是结合可选值。
if var and while var
-
除了 let 以外,你还可以使用 var 来搭配 if、while 和 for。这让你可以在语句块中改变变量:
let number = "1" if var i = Int(number) { i += 1 print(i) } // 2 复制代码
解包后可选值的作用域
-
guard let
-
Never 又被叫做无人类型 (uninhabited type)。这种类型没有有效值,因此也不能够被构建。在泛型环境里,把 Never 和 Result 结合在一起是非常有用的。例如,对于一个接受Result<A, E> (A 和 E 都是泛型参数) 的泛型 API,你可以传入一个 Result<…, Never> 表示这个结果中永远都不会包含失败的情况,因为这种情况是构建不出来的。
-
在 Swift 中,无人类型是通过一个不包含任意成员的 enum 实现的:
public enum Never {} 复制代码
-
Swift 在区分各种“无”类型上非常严密。除了 nil 和 Never,还有 Void ,Void 是空元祖 (tuple) 的另一种写法:
public typealias Void = () 复制代码
Swift 对“东西不存在”(nil),“存在且为空”(Void) 以及“不可能发生”(Never) 这几个概念进行了仔细的区分。
-
guard 是一个明确的型号,它暗示我们“只在条件成立的情况下继续”。Swift 编译器还会检查你是否在 guard 块中退出了当前作用域,如果没有的话,你会得到一个编译错误。因为可以得到编译器帮助,所以尽量选择使用 guard,即便 if 也可以正常工作。
可选链
-
在
Objective-C
中,对 nil 发消息什么都不会发生。Swift
里,我们可以通过“可选链 (optional chaining)” 来达到同样的效果:delegate?.callback() 复制代码
nil 合并运算符
- 和 || 操作符一样, ?? 操作符使用短路求值 (short circuiting)。当我们用
l ?? r
时,只有当l
为 nil 时,r
的部分才会被求值。这是因为在操作符的函数声明中,对第二个参数使用了 @autoclosure
在字符串插值中使用可选值
-
Swift 的
??
运算符不支持两侧的类型不匹配的操作。只是为了在字符串插值中使用可选值这一特殊目的的话,添加一个???
运算符也很简单infix operator ???: NilCoalescingPrecedence public func ???<T>(optional: T?, defaultValue: @autoclouse () -> String) -> String { switch optional { case let value?:return String(describing: value) case nil: return defaultValue() } } 复制代码
可选值map
Optional.map
和数组以及其他序列里的map方法非常类似。但是与序列中操作一系列值所不同的是,可选值的 map 只会操作一个值,那就是该可选值中的那个可能存在的值。你可以把可选值当做一个包含零个或者一个值的集合,这样 map 要么在零个值的情况下不做处理,要么在有值的时候会对其进行转换。
可选值flatmap
let stringNumbers = ["1","2","3","foo"]
let x = stringNumbers.first.map { Int($0) } // Optional(Optional(1))
复制代码
flatMap 可以把结果展平为单个可选值。这样以来,y 的类型将会是 Int? :
let y = stringNumbers.first.flatMap { Int($0) } // Optional(1)
复制代码
使用 compactMap 过滤 nil
- compactMap —— 将那些 nil 过滤出去并将非 nil 值进行解包的 map 。
可选值判等
- 当我们在使用一个非可选值的时候,如果需要匹配成可选值类型,Swift 总是会将它“升级”为一个可选值。
可选值比较
- 想要在可选值之间进行除了相等之外的关系比较的话,现在你需要先对它们进行解包,然后明确地指出 nil 要如何处理。
强制解包的时机
当你能确定你的某个值不可能是 nil 时可以使用叹号,你当会希望如果它意外是 nil 的话,程序应当直接挂掉。
改进强制解包的错误信息
-
加一个 !! 操作符,它将强制解包和一个更具有描述性质的错误信息结合在一起,当程序意外退出时,这个信息也会被打印出来:
infix operator !! func !!<T>(wrapped: T?,failureText: @autoclouse () -> String) -> T { if let x = wrapped { return x } fatalError(failureText()) } 复制代码
let s = "foo" let i = Int(s) !! "Expecting integer, got \"\(s)\"" 复制代码
在调试版本中进行断言
-
实现一个疑问感叹号 !? 操作符——将这个操作符定义为对失败的解包进行断言,并且在断言不触发的发布版本中将值替换为默认值:
infix operator !? func !?<T: ExpressibleByIntegerLiteral> (warpped: T?, failureText: @autoclouse () -> String) -> T { assert(wrapped != nil, failureText()) return wrapped ?? 0 } 复制代码
现在,下面的代码将在调试时触发断言,但在发布版本中打印0:
let s = "20" let i = Int(s) !? "Expecting integer, got \"\(s)\"" 复制代码
-
想要挂起一个操作我们有三种方式。首先,fatalError 将接受一条消息,并且无条件地停止操作。第二种选择,使用 assert 来检查条件,当条件结果为 false 时,停止执行并输出信息。在发布版本中,assert 会被移除掉,也就是说条件不会被检测,操作也永远不会挂起。第三种方式是使用 precondition,它和 assert 有一样的接口,但是在发布版本中不会被移除,也就是说,只要条件被判定为 false,执行就会被停止。
隐式解包可选值
为什么会要用到隐式可选值呢?
原因1:暂时来说,我们可能还需要到 Objective-C
里去调用那些没有检查返回是否存在的代码;或者你会调用一个没有针对 Swift
做注解的 C
语言的库。
原因2:因为一个值只是很短暂地成为nil
,在一段时间后,它就再也不会是nil
。最常见的情况就是两阶段初始化 (two-phase initialization)。
隐式可选值行为
虽然隐式解包的可选值在行为上就好像是非可选值一样,不过你依然可以对它们使用可选链,nil 合并,if let,map 或者将它们与 nil 比较,所有的这些操作都是一样的:
var s: String! = "Hello"
s?.isEmpty // Optional(false)
if let s = s { print(s) } // Hello
s = nil
s ?? "Goodbye" // Goodbye
复制代码