Swift 可选类型Optional
[TOC]
前言
本将以Swift中的可选类型为入口,介绍:
- 可选类型的底层实现
- Swift中的nil
- Optional的模式匹配
- if语句以及强制解析
- 可选绑定
- 隐式解析可选类型等。
Swift中的Optional底层实现是enum,如果你对Swift中的enum不是很了解,可以先看看我的这篇文章Swift 枚举(enum)详解
1. Optional
1.1 简介
相对于apple开发的其他语言(C、Objective-C)中,Swift增加了可选(Optional)类型,用于处理值缺失的情况。简单来说,可选就是“那有一个值,并且它等于x”或者“那没有值”。可选有点像Objective-C中使用nil
,但是它可以使用在任何类型上,不仅仅是类。可选类型比Objective-C中的nil
指针更加安全也更具表现力,它是Swift许多强大特性的重要组成部分。
虽然说可选像Objective-C中使用nil
,但是C和Objective-C中并没有可选类型的概念。nil
也仅仅是针对没有一个合法的对象的时候使用,对于结构体,基本的C类型或者枚举类型不起作用。对于这些类型,Objective-C方法一般会返回一个特殊值(比如NSNotFound)来暗示缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift的可选类型可以让你暗示任意类型的值缺失,并不需要一个特殊值。
举个例子,当我们尝试将一个String
转换成Int
:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"
此时我们按住option
键单击convertedNumber
就可以看到它是Int?
类型
因为上面的构造器可能会失败,如果possibleNumber
的值为Hello World
,就不会构造处一个Int
,所以对于一个可选的Int
应该被写作Int?
。其中这里面的?
相当于语法糖,Int?
等价于optional<Int>
。
1.2 nil
我们可以给你可选变量赋值为nil
来表示它没有值:
var serverResponseCode: Int? = 404
// serverResponseCode 包含一个可选的 Int 值 404
serverResponseCode = nil
// serverResponseCode 现在不包含值
但是nil
不能用于非可选的常量和变量。如果我们的代码中有常量或者变量需要处理值缺失的情况,那就把它们声明成对应的可选类型。
如果声明一个可选常量或者变量但没有赋值,它们会自动设置为nil
:
var surveyAnswer: String?
// surveyAnswer 被自动设置为 nil\
print(surveyAnswer)
<!--打印结果-->
nil
Swift的nil
和Objective-C中的nil
并不一样。在Objective-C中,nil
是一个指向不存在对象的指针。在Swift中,nil
不是指针,它是一个确定的值,用来标识值缺失。任何类型的可选状态都可以被设置为nil
,不只是对象类型。
1.3 Optional源码
查看Swift源码,此处使用的是Swift5.3.1
,可以同command+p
全局搜索Optional.Swift
文件,来快速定位。由于同名文件很多,我们选择swift-source/swift/stdlib/public/core/Optional.swift
路径下的Optional.Swift
。
@frozen
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
// The compiler has special knowledge of Optional<Wrapped>, including the fact
// that it is an `enum` with cases named `none` and `some`.
/// The absence of a value.
///
/// In code, the absence of a value is typically written using the `nil`
/// literal rather than the explicit `.none` enumeration case.
case none
/// The presence of a value, stored as `Wrapped`.
case some(Wrapped)
.....
}
通过源码我们可以知道Optional
的本质使用一个具有关联值的enum
,关联值的类型来自Optional
所接收的一个泛型参数Wrapped
。
所以对于可选类型的写法,以下两种是等价的:
var a: Int? = 10
var a1: Optional<Int> = 10
1.4 Optional的模式匹配
既然Optional
的本质是枚举,那么他也就可以使用枚举的模式匹配来匹配对应的值:
var a: Int? = 10
//匹配有值和没值两种情况
switch a {
case .none:
print("nil")
case .some(let value):
print(value)
}
// 匹配没值,有特定值的情况
switch a {
case .none:
print("nil")
case .some(10):
print("value is 10")
default: print("other value")
}
1.5 解包
对于可选类型,我们要想使用的时候,其内部可能有值也可能没有值,所以我们需要对其进行解包。
1.5.1 if判断
我们可以使用if
语句和nil
比较来判断一个可选值是否包含值。可以使用==
或!=
来比较
var a: Int? = 10
if a != nil {
print("a has an integer value of \(a!).")
}
当我们确定可选包含一个非nil
的值,就可以使用!
来强制解析值了。
1.5.2 强制解析
最简单的写法就是强制解包了,写法简单,只需加一个感叹号!
,但是也有坏处,就是一旦解包的值是nil
,程序就会崩溃了:
就单从这一点来说,此方案能不用尽量别用,因为在Swift中每使用一个!
都需要慎重。如果使用!
进行解析,已读要去掉可选包含一个非nil
的值。
1.5.3 可选绑定
如果不确定可选类型是否有值,我们通常会使用可选绑定进行判断:
-
if let
:如果有值,则会进入if 流程 -
gurad let
:如果为nil,则会进入else
流程
var a: Int? = 10
if let value = a {
print("a has an integer value of \(value).")
} else {
print("a is nil")
}
func test() {
guard let value = a else {
print("a is nil")
return
}
print("a has an integer value of \(value).")
}
test()
- 一般我们使用
if let
的时候就是为了拿到可选类型的值进行业务逻辑处理,其值只在if
分支中可用。 - 我们使用
guard let
的时候是为了守护这个可选类型,当可选类型没有值,则优先进入else
分支进行容错处理,如果有值,则后续都可以使用这个重绑定的值,而不需要在进行解包。
1.6 隐式解析可选类型
有时候在程序架构中,第一次赋值之后,可以确定一个可选类型总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。此时我们就可以通过隐式解析来解决这个问题,通常隐式解析可选类型被用于Swift中类的构造过程。
class Country {
let name: String
var capitalCity: String!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = capitalName
}
}
let country = Country(name: "China", capitalName: "beijing")
let capitalName = country.capitalCity
此时当country
对象初始化后,其首都就已经确认了,在后续的使用的过程就不需要进行解包了。
但是由初始化的变量仍然是一个可选类型:
image当然如果在init
方法中没有给capitalCity
赋值,在后续的使用的过程中与可选值为nil
的时候使用!
是一样的,会触发应用程序的崩溃错误。所以还是那句话,使用!
需要谨慎。
我们可以把隐式解析可选类型当做普通可选类型类判断它是否包含值:
if country.capitalCity != nil {
print(country.capitalCity!)
}
if let capital = country.capitalCity {
print("The capital of \(country.name) is \(capital).")
} else {
print("The capital of \(country.name) is nil")
}
func test() {
guard let capital = country.capitalCity else {
print("The capital of \(country.name) is nil")
return
}
print("The capital of \(country.name) is \(capital).")
}
test()
注意:
如果一个变量之后可能变成nil
的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是nil
的话,请使用普通可选类型
1.7 unsafelyUnwrapped
unsafelyUnwrapped
即不安全的打开,是Swift Optional提供的一个计算属性,提供了与强制解包操作符!
相同的值:
var a: Int? = 10
print(a!)
print(a.unsafelyUnwrapped)
如果可选值为nil
,使用unsafelyUnwrapped
通用会引起程序崩溃:
下面我们来看看unsafelyUnwrapped
的源码:
@inlinable
public var unsafelyUnwrapped: Wrapped {
@inline(__always)
get {
if let x = self {
return x
}
_debugPreconditionFailure("unsafelyUnwrapped of nil optional")
}
}
在源码中我们可以看到是通过if let
进行可选绑定,然后返回值,如果绑定失败则会打印错误信息。
虽然看起来跟!
没什么区别,但是注释中有这么一句话:
在 -O 这个优化级别时,不会执行检测来确保当前实例实际上有一个值。
这里的-O
是指target -> Build Setting -> Optimization Level
设置成-O
时。
下面我们就按照文档上说的,将优化级别设置成Fastest, Smallest[-Os]
,并将运行模式调整为release
模式,重新运行:
PS: 首先Debug
没生效,release
也没生效,各种优化级别试了一遍也没生效。Fastest, Smallest[-Os]
+ release
重启Xcode
生效了。我用的是Xcode 12.2
所以在开发中如果需要强制解包就用!
吧,按照官方文档说的此属性以安全换取性能,使用时机与!
一致。
1.8 ?? 空合运算符
对于可选值来说,其值有可能为nil
,此时我们想要给其赋一个默认值来解决值为nil
的问题,如果通过解包等方法就显得很复杂了,此时使用??
空合运算符,就会很简单,也使得代码很简洁。
空合运算符(a ?? b
)将可选类型a
进行空判断,如果a
包含一个值就进行解包,否则就返回一个默认值b
。表达式a
必须是Optional
类型。默认值b
的类型必须要和a
存储值的类型保持一致。
空合运算符是对以下代码的简短表达方法:
a != nil ? a! : b
以上是一个三元运算符,当可选值a
不为空时,对其进行强制解包a!
以访问a
中的值;反之默认返回b
的值。如果b是一个函数,也就不会执行了。
实际应用可以是这样:
var a: Int? = nil
print(a ?? getNum())
var b: Int? = 11
print(b ?? getNum())
func getNum() -> Int {
print("get num")
return 20
}
<!--打印结果-->
get num
20
11
当b有值的时候,是不会执行getNum
函数的。
下面我们看看 ??
的实现,在Optional
源码中:
// 返回T
@_transparent
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
rethrows -> T {
switch optional {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}
// 返回T?
@_transparent
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?)
rethrows -> T? {
switch optional {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}
根据源码,我们可以知道,??
的返回值类型有两种,分别是T
和T?
,这点主要与??
后面的返回值有关,也就是??
是什么类型,返回的就是什么类型。因为(a ?? b
),b
也有可能是可选类型。
此时我们可以看到c
就是个Int?
类型
此时d
是Int
类型。
如果??
后面的类型与前面的类型不匹配,则会报编译错误。Cannot convert value of type 'String' to expected argument type 'Int'
无法将类型'String'的值转换为期望的参数类型'Int'。
1.9 可选链
可选链的意思就是允许在一个链上来访问当前属性/方法,示例:
class Person {
var name: String?
var subject: Subject?
}
class Subject {
var subjectName: String?
func test(){print("test")}
}
var s = Subject()
var p = Person()
if let sName = p.subject?.subjectName {
print("subjectName is \(sName)")
} else {
print("subjectName is nil")
}
p.subject?.test()
运行结果如下:
image
可选链当链上的任意一个值为nil
则不会继续执行后面的代码。
2. 总结
至此我们对Swift的Optional
的分析基本就到这里了,下面总结一下
-
Optional
是Swift增加的一种类型,用于处理值缺失的情况; - 可选表示“那有一个值,并且它等于x”或者“那儿没有值”;
- Swift中的
nil
不是指针,它是一个确定的值,用来标识值缺失; - Swift中任何类型的可选状态都可以被设置为nil,不只是对象类型;
- 在Objective-C中,nil是一个指向不存在对象的指针。
-
Optional
的本质是enum
,所以它具备enum
的特性,可以使用模式匹配来匹配 - 可选类型在使用的时候需要解包
- 可以使用最基本的
if
判断是否值为nil
- 可以使用
!
进行强制解包,但需要注意可能引起的崩溃问题 - 使用
if let
可选绑定,着重处理有值的情况 - 使用
guard let
可选绑定,守护值,着重处理没值的情况
- 可以使用最基本的
- 对于在构造方法中赋值后不在为
nil
的属性,我们也可以隐式声明可选类型,已解决后续总需要解包的问题。 -
Optional
提供了一个与!
功能一样的计算属性unsafelyUnwrapped
-
unsafelyUnwrapped
在release
环境Fastest, Smallest[-Os]
优化级别,不会执行检查来确保当前私立实际上有一个值 -
Optional
提供了??
空合运算符来便利的处理值为nil
时提供默认值 - 对于可选链上的属性或者方法的调用,只要链上任意为
nil
则不会继续执行后面的代码
网友评论