Optional 简介
可选类型伴随着Swift诞生,在原有的Objective-C语言中不存在,究其原因,是因为Swift是类型安全的语言,而OC则是弱类型语言,OC中 str字符串既可以是nil,也可以是字符串,而Swift中,这两种状态是不能同时存在的。
首先我们先看下Objective-C与Swift语言对于可选nil的不同理解:
- Objective-C中的nil:表示缺少一个合法的对象,是指向不存在对象的指针,对结构体、枚举等类型不起作用(会返回NSNotFound)
- Swift中的nil:表示任意类型的值缺失,是一个确定的值,要么是该类型的一个值要么什么都没有(即为nil)
- 可选项,一般也叫做可选类型,它允许将值设置为nil
- 在类型名称后面加个问号?来定义一个可选项
var name: String = "Jack"
name = nil
var age: Int?
age = 10
age = nil
- 在类型和 ?之间没有空格
- 在Swift中Optional(可选类型)是一个含有两种情况的枚举,None 和 Some(T),用来表示可能有或可
- 当你声明一个可选变量或者可选属性的时候没有提供初始值,它的值会默认为 nil。
- 可选项遵照 LogicValue 协议,因此可以出现在布尔环境中。在这种情况下,如果可选类型T?包含类型为T的任何值(也就是说它的值是 Optional.Some(T) ),这个可选类型等于 true,反之为 false。
- 使用"!"强制解析获取可选类型的值(不建议直接使用)
一旦确定可选值包含值,可以通过在可选值名称的末尾添加感叹号(!)来访问其内部值。这被称为强制解包一个可选的值
class Person {
var name:String
var age:Int
init(name:String,age:Int) {
self.name = name
self.age = age
}
}
let status:Int? = 1
var defaultAddress:String? = "Apple"
var student:Person?
var tempAddress:String = defaultAddress!
print(tempAddress)
- 使用操作符!去获取值为nil的可选变量会有运行时错误。
- 你可以用可选链接和可选绑定选择性执行可选表达式上的操作。如果值为nil,任何操作都不会执行,也不会有运行报错。
- 叹号(!)表示"我知道一定有值,请使用它",但是当你判断错误,可选值为nil时使用(!)进行强制解析,会有运行错误。所以在使用强制解包之前,一定要确保一个可选值不为
nil。
class Person {
var name:String
var age:Int
init(name:String,age:Int) {
self.name = name
self.age = age
}
}
let status:Int? = 1
var defaultAddress:String? = "Apple"
if defaultAddress != nil {
print(defaultAddress!);
} else {
print("值为nil,请做异常处理")
}
var student:Person?
可选类型类似于Objective-C中指针的nil值,但是nil只对类(class)有用,而可选类型对所有的类型都可用,并且更安全。
Optional使用
强制解析
class Person {
var name:String
var age:Int
init(name:String,age:Int) {
self.name = name
self.age = age
}
}
let status:Int? = 1
var defaultAddress:String? = "Apple"
if defaultAddress != nil {
print(defaultAddress!);
} else {
print("值为nil,请做异常处理")
}
var student:Person?
注意: 使用!来获取一个不存在的可选值会导致运行时错误。使用!来强制解析值之前,一定要确定可选包含一个非nil的值。
自动解析
你可以在声明可选变量时使用感叹号(!)替换问号(?)。这样可选变量在使用时就不需要再加一个感叹号(!)来获取值,它会自动解析。
class Person {
var name:String
var age:Int
init(name:String,age:Int) {
self.name = name
self.age = age
}
}
let status:Int! = 1
var defaultAddress:String! = "Apple"
if defaultAddress != nil {
print(defaultAddress);
} else {
print("值为nil,请做异常处理")
}
var student:Person!
可选绑定(推荐使用)
使用可选绑定(optional binding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在if和while语句中来对可选类型的值进行判断并把值赋给一个常量或者变量。
class Person {
var name:String
var age:Int
init(name:String,age:Int) {
self.name = name
self.age = age
}
}
let status:Int? = 1
var defaultAddress:String? = "Apple"
if let tempAddress = defaultAddress {
print(tempAddress)
} else {
print("字符串为nil")
}
var student:Person?
- 如果转换成功,那么 tempAddress 常量可以在 if 语句的第一个分支中使用。它已经被初始化为包含在非可选的值中,所以没有必要使用
! 后缀来访问它的值。 - 你可以使用可选绑定的常量和变量。如果你想在 if 语句的第一个分支内操作 tempAddress 的值,你可以写 if var
tempAddress,使得可选值作为一个变量而非常量。
你可以根据需要在单个 if 语句中包含尽可能多的可选绑定和布尔条件,并用逗号分隔。如果可选绑定中的任何值为 nil,或者任何布尔条件的计算结果为 false,则整个 if 语句的条件被认为是错误的。以下 if 语句是等价的:
class Person {
var name:String
var age:Int
init(name:String,age:Int) {
self.name = name
self.age = age
}
}
let status:Int? = 1
var defaultAddress:String? = "Apple"
if let tempAddress = defaultAddress, let tempStatus = status {
print(tempAddress)
print(tempStatus)
} else {
print("字符串为nil")
}
var student:Person?
隐式解包
- 在某些情况下,可选项一旦被设定值之后,就会一直拥有值
- 在这种情况下,可以去掉检查,也不必每次访问的时候都进行解包,因为它能确定每次访问的时候都有值
- 可以在类型后面加个感叹号!定义一个隐式解包的可选项
let num1: Int! = 10
let num2: Int = num1
if num1 != nil {
print(num1 + 6)
}
if let num3 = num1 {
print(num3)
}
如果给隐式解包类型的optional变量赋值nil,当该变量被赋值给其他变量/常量式,会出现 运行时报错
let num4: Int! = nil
let num5: Int = num4 -->注意,程序执行完这一句才会报错,在执行这一句的时候,会对num4进行隐式解包,结果发现它内部是nil,因此把nil取出来赋值给 num5: Int 的时候就报错了,因为 num5 不能为空
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
上述的案例,让人感觉使用带!的optional存在很多风险,其实用下面的方式莫不是更保险点吗,
var num5: Int = 10
let num6: int = num5
这样至少在程序运行之前,就能看到错误,如果有的话。
实际上,我们大部分场景下,也都是推荐使用带?的optional,那用!声明的optional存在的意义是什么呢?
- 如果你提供一套api给外界使用,并且期望使用者严格遵守你的要求不要传nil过来,并且认为使用者在错误使用的时候而导致程序直接报错崩溃就是你期待的,那么你可以使用这种用法。除此之外,还是不用为妙。
Optional 源码分析
Optional 的定义
- Optional.swift 源码中,Optional定义如下
- Optional是通过enum实现的,Optional本质是枚举。
- 两个case,nil和some。
- 关联值就是传进来的值。
- 本质上?是语法糖,下面的写法等价:
var age: Int? = 10
var age1:Optional<Int> = Optional(10)
switch age {
case .none:
print("nil")
case .some(10):
print("\(10)")
default:
print("unKnown")
}
print(age == age1)
10
true
var age: Int? = nil
print(age!)
如果age没有值!会直接crash。Fatal error: Unexpectedly found nil while unwrapping an Optional value:
if let
var age: Int? = nil
if let temp = age {
print("\(temp)")
} else {
print("nil")
}
guard let
var age: Int? = 10
func test() -> Any {
guard let temp = age else {
return "error"
}
print(temp)
return temp
}
test()
Equatable 协议(Optional 遵循了Equatable)
Optional 遵循了Equatable 协议,重写了==方法
extension Optional: Equatable where Wrapped: Equatable {
@inlinable
public static func ==(lhs: Wrapped?, rhs: Wrapped?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l == r
case (nil, nil):
return true
default:
return false
}
}
}
var age: Int? = 10
var age1:Optional<Int> = Optional(10)
print(age == age1)
前面age和age1能够比较就是因为Optional遵循了Equatable协议重载了==运算符。
自定义数据类型要进行==需要遵循Equatable协议
struct HotpotCat {
var age: Int
var name: String
}
extension HotpotCat: Equatable {}
var hp = HotpotCat(age: 18, name: "hotpot")
var hp1 = HotpotCat(age: 18, name: "hotpot")
print(hp == hp1)
true
- 在HotpotCat中我们并没有实现==方法,编译器帮我们默认实现了
- 将上述代码生成SIL文件如下
struct HotpotCat {
@_hasStorage var age: Int { get set }
@_hasStorage var name: String { get set }
init(age: Int, name: String)
}
extension HotpotCat : Equatable {
@_implements(Equatable, ==(_:_:)) static func __derived_struct_equals(_ a: HotpotCat, _ b: HotpotCat) -> Bool
}
__derived_struct_equals实现:
sil hidden @$s4main9HotpotCatV23__derived_struct_equalsySbAC_ACtFZ : $@convention(method) (@guaranteed HotpotCat, @guaranteed HotpotCat, @thin HotpotCat.Type) -> Bool {
bb0(%0 : $HotpotCat, %1 : $HotpotCat, %2 : $@thin HotpotCat.Type):
debug_value %0 : $HotpotCat, let, name "a", argno 1
debug_value %1 : $HotpotCat, let, name "b", argno 2
debug_value %2 : $@thin HotpotCat.Type, let, name "self", argno 3
%6 = struct_extract %0 : $HotpotCat, #HotpotCat.age
%7 = struct_extract %1 : $HotpotCat, #HotpotCat.age
%8 = struct_extract %6 : $Int, #Int._value
%9 = struct_extract %7 : $Int, #Int._value
%10 = builtin "cmp_eq_Int64"(%8 : $Builtin.Int64, %9 : $Builtin.Int64) : $Builtin.Int1
cond_br %10, bb1, bb4
bb1:
%12 = metatype $@thin String.Type
%13 = struct_extract %0 : $HotpotCat, #HotpotCat.name
retain_value %13 : $String
%15 = struct_extract %1 : $HotpotCat, #HotpotCat.name
retain_value %15 : $String
%17 = function_ref @$sSS2eeoiySbSS_SStFZ : $@convention(method) (@guaranteed String, @guaranteed String, @thin String.Type) -> Bool
%18 = apply %17(%13, %15, %12) : $@convention(method) (@guaranteed String, @guaranteed String, @thin String.Type) -> Bool
release_value %15 : $String
release_value %13 : $String
%21 = struct_extract %18 : $Bool, #Bool._value
cond_br %21, bb2, bb3
bb2:
%23 = integer_literal $Builtin.Int1, -1
%24 = struct $Bool (%23 : $Builtin.Int1)
br bb5(%24 : $Bool)
bb3:
%26 = integer_literal $Builtin.Int1, 0
%27 = struct $Bool (%26 : $Builtin.Int1)
br bb5(%27 : $Bool)
bb4:
%29 = integer_literal $Builtin.Int1, 0
%30 = struct $Bool (%29 : $Builtin.Int1)
br bb5(%30 : $Bool)
bb5(%32 : $Bool):
return %32 : $Bool
}
- 1.取两个结构体a、b
- 2.比较a.age 与 b.age
- 3.取a.name与b.name,并且调用String自己的判等方法
- 4.构造Int1类型的数据,相等-1,不相等0。
底层通过Int1类型的数据生成Bool类型:
%23 = integer_literal $Builtin.Int1, -1
%24 = struct $Bool (%23 : $Builtin.Int1)
br bb5(%24 : $Bool)
所以也可以简单说Swift中的Bool是-1(true)和0(false)。
Bool.Swift源码:
- 如果结构体里面嵌套结构体,内嵌结构体也要遵循Equatable协议
只有MakeCatBar与HotpotCat 都遵循Equatable协议 才能使用==运算符
- 上面我们分析的是结构体,那么如果HotpotCat是class呢?
class HotpotCat {
var age: Int
var name: String
init(age:Int,name:String) {
self.age = age
self.name = name
}
}
extension HotpotCat: Equatable {
static func == (lhs: HotpotCat, rhs: HotpotCat) -> Bool {
return lhs.age == rhs.age && lhs.name == rhs.name
}
}
var hp = HotpotCat.init(age: 18, name: "hotpot")
var hp1 = HotpotCat.init(age: 18, name: "hotpot")
var hp2 = hp
print(hp == hp1)
print(hp === hp1)
print(hp === hp2)
true
false
true
如果判断两个对象是否是同一个实例对象需要使用===
==相等于equal to,用于判断两个值是否相等 === 用于判断两个是否是同一个实例对象(内存地址是否一致)
Comparable 协议
Comparable 协议遵循了Equatable协议支持更多的比较方式
public protocol Comparable : Equatable {
static func < (lhs: Self, rhs: Self) -> Bool
static func <= (lhs: Self, rhs: Self) -> Bool
static func >= (lhs: Self, rhs: Self) -> Bool
static func > (lhs: Self, rhs: Self) -> Bool
}
Comparable自动实现了Equatable
struct HotpotCat {
var age: Int
var name: String
}
extension HotpotCat: Comparable {
static func < (lhs: HotpotCat, rhs: HotpotCat) -> Bool {
return lhs.age < rhs.age && lhs.name < lhs.name
}
}
var hp = HotpotCat(age: 21, name: "hotpot1")
var hp1 = HotpotCat(age: 20, name: "hotpot")
print(hp > hp1)
在这里结构体必须实现<,编译器会通过<自动实现其它运算符。当然实现>,不实现<不行,由于源码中其它运算法是通过<来实现的:
public protocol Comparable: Equatable {
static func < (lhs: Self, rhs: Self) -> Bool
static func <= (lhs: Self, rhs: Self) -> Bool
static func >= (lhs: Self, rhs: Self) -> Bool
static func > (lhs: Self, rhs: Self) -> Bool
}
extension Comparable {
@inlinable
public static func > (lhs: Self, rhs: Self) -> Bool {
return rhs < lhs
}
@inlinable
public static func <= (lhs: Self, rhs: Self) -> Bool {
return !(rhs < lhs)
}
@inlinable
public static func >= (lhs: Self, rhs: Self) -> Bool {
return !(lhs < rhs)
}
}
空合运算符(??)
@_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()
}
}
@_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?,这里为什么有两个?看个例子就明白了
var age: Int? = nil
var age2: Int? = 10
var temp = age ?? age2
print(temp)
- 可以看到??的返回值是和age2相关的。age2是什么类型就返回什么类型,age2是可选类型就返回T?,否则返回T。
- 空合运算符后面返回值的类型,还要与当前类型保持一致,当前是Int类型,就不能返回字符串
可选链
可选链是一个调用和查询可选属性、方法和下标的过程,它可能为 nil 。如果可选项包含值,属性、方法或者下标的调用成功;如果可选项是 nil ,属性、方法或者下标的调用会返回 nil 。多个查询可以链接在一起,如果链中任何一个节点是 nil ,那么整个链就会得体地失败。
class Hotpot {
var name: String?
var cat: Cat?
func test() {
print("test")
}
}
class Cat {
var catName: String?
func ctTest() {
print("ctTest")
}
}
var cat: Cat? = nil
var hp: Hotpot? = Hotpot()
let temp = hp?.cat?.catName
hp?.test()
hp?.cat?.ctTest()
unsafelyUnwrapped
在optional源码中还有一个unsafelyUnwrapped,实现如下:
@inlinable
public var unsafelyUnwrapped: Wrapped {
@inline(__always)
get {
if let x = self {
return x
}
_debugPreconditionFailure("unsafelyUnwrapped of nil optional")
}
}
unsafelyUnwrapped 和!强制解包一样。
var age: Int? = 20
print(age.unsafelyUnwrapped)
区别在于:
这里的-O,是指target -> Build Setting -> Optimization Level设置成-O时,如果使用的是age.unsafelyUnwrapped,则不检查这个变量是否为nil,
1、设置Optimization Level 为Fastest, Smallest[-Os]
2、edit Scheme -> Run -> Info -> Build Configuration改为release模式,然后再次运行发现,没有崩溃,与官方所说是一致的
as as? as!
var age: Int = 10
var age1 = age as Any
print(age1)
var age2 = age as AnyObject
print(age2)
<!--打印结果-->
10
10
var age: Int = 10
var age3 = age as? Double
print(age3)
<!--打印结果-->
nil
此时的age3的类型是Double?
var age: Int = 10
var age4 = age as! Double
print(age4)
运行结果如下,会崩溃
查看以下代码的SIL文件
var age: Int = 10
var age3 = age as? Double
var age4 = age as! Double
总结
- Optional的本质是enum,所以可以使用模式匹配来匹配Optional的值
- Optional的解包方式有两种:
1、强制解包:一旦为nil,程序会崩溃 2、可选值绑定:if let (只能在if流程的作用域内访问)、guard let
- 对于swift标准库中的绝大部分类型都默认实现了Equatable协议
- 对于自定义Struct类型,仅需要遵守Equatable协议
- 对于自定义class类型,除了需要遵守Equatable协议,还需要自己实现Equatable协议的方法
== 相当于 equal to,用于判断两个值是否相等 === 是用来判断 两个对象是否是同一个实例对象(即内存地址指向是否一致)
对于自定义类型,需要遵循Comparable协议,并重写运算符 ??空运算符:??只有两种类型,一种是T,一种是T?,主要是与 ?? 后面的返回值有关(即简单来说,就是??后是什么类型,??返回的就是什么类型)
- 可选链:允许在一个链上来访问当前的属性/方法,如果为nil,则不会执行?后的属性/方法
- unsafelyUnwrapped:与强制解包类似,但是如果项目中设置target -> Build Setting -> Optimization Level设置成-O时,如果使用的是age.unsafelyUnwrapped,则不检查这个变量是否为nil
- 区分 as、as?、 as!
as 将类型转换为其他类型 as? 将类型转换为 其他可选类型 as! 强制转换为其他类型 使用建议:能确定使用as!,不能确定使用as?
|