Swift-奇奇怪怪的符号
相信大家在学习和使用Swift的时候,肯定会被各种各样的符号弄得摸不清头脑,不知道实际的原理是什么,这里简单介绍一下swift中常用的一些符号定义:
?- 可选类型
?
在Swift语言中对一种可选类型(Optional)操作的语法糖,可以用 ?
来表示“有一个值,值是什么”或者“没有值,值是nil”。
var A_nil: Int? = nil
var A: Int? = 10
var A_other: Int = nil // 'nil' cannot initialize specified type 'Int'
这里我们可以看到可选类型的变量既可以设置为有值类型,也可以设置为nil类型,但是A_other设置为nil,程序就会报错,nil不能初始化指定类型Int。
那可选类型的本质是什么呢?
var A: Int? = 10
var A_optional: Optional<Int> = 10
这里我们可以发现,类型(Optional)的定义就是通过在类型声明后加一个 ?
操作符完成的,它们的作用是等价的,我们可以看看Optional底层的源码是什么样的,这里是swift5的源码:
@frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {
/// 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)
...
/// The wrapped value of this instance, unwrapped without checking whether
/// the instance is `nil`.
///
/// The `unsafelyUnwrapped` property provides the same value as the forced
/// unwrap operator (postfix `!`). However, in optimized builds (`-O`), no
/// check is performed to ensure that the current instance actually has a
/// value. Accessing this property in the case of a `nil` value is a serious
/// programming error and could lead to undefined behavior or a runtime
/// error.
///
/// In debug builds (`-Onone`), the `unsafelyUnwrapped` property has the same
/// behavior as using the postfix `!` operator and triggers a runtime error
/// if the instance is `nil`.
///
/// The `unsafelyUnwrapped` property is recommended over calling the
/// `unsafeBitCast(_:)` function because the property is more restrictive
/// and because accessing the property still performs checking in debug
/// builds.
///
/// - Warning: This property trades safety for performance. Use
/// `unsafelyUnwrapped` only when you are confident that this instance
/// will never be equal to `nil` and only after you've tried using the
/// postfix `!` operator.
@inlinable public var unsafelyUnwrapped: Wrapped { get }
...
}
这里截取了一部分,我们可以看到:
- Optional其实是一个标准库的一个enum类型
- <Wrapped>泛型,也就是包装实际类型的数据类型
- 遵守ExpressibleByNilLiteral,允许nil来初始化一个类型
- none 和some两种类型,Optional.none就是nil , Optional.some就是实际值
- 泛型属性unsafelyUnwrapped,理论上就是调用unsafelyUnwrapped获取可选项的实际的值,这种值不安全的
这里我们用枚举来匹配对应的值:
var A: Int? = 10
// 匹配有值和没值两种情况
switch A {
case .none:
print("nil")
case .some(let value):
print(value)
}
! - 强制解析
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号!
来获取值,这个感叹号表示"可选值有值"。我们称之为强制解析(forced unwrapping)
var A: Int? = 10
print(A!)
print(A.unsafelyUnwrapped)
这里我们看到A的值都能打印出10,unsafelyUnwrapped和强制解包操作符 !
具有相同的值,但是unsafelyUnwrapped是不安全的
var A: Int? = nil
print(A!)
print(A.unsafelyUnwrapped)
这里都会出现崩溃,崩溃的信息分别是
Fatal error: Unexpectedly found nil while unwrapping an Optional value
// 致命错误:解包装可选值时意外发现nil
Fatal error: unsafelyUnwrapped of nil optional
// 致命错误:无可选的不安全解包
根据报错的信息我们可以看到unsafelyUnwrapped是不安全的解包,这里我们看一下官方文档的解释:
unsafelyUnwrapped官方解释
这里我们来验证一下unsafelyUnwrapped的不安全性,首先我们先设置一下 -O 这个优化级别,这里的-O是指target -> Build Setting -> Optimization Level设置成-O时。release模式默认是Fastest, Smallest[-Os],将运行模式调整为release模式,按照下面代码重新运行:
let A: Int? = nil
print(A!)
// print(A.unsafelyUnwrapped)
print("----")
此时出现崩溃,我们通过打印,可以看到相关的错误信息,说明强制解析可以检查确保当前的实际的值,错误信息也会正常打印检测:
截屏
相反我们使用unsafelyUnwrapped:
截屏
这里我们可以看到并没有出现崩溃信息,没有检查确保当前实际的值,这种是很不安全的。
??- 空合运算符
空合运算符(a ?? b)将可选类型a进行空判断,如果a包含一个值就进行解包,否则就返回一个默认值b。表达式a必须是Optional类型。默认值b的类型必须要和a存储值的类型保持一致。
我们可以用三元运算符表达:
a != nil ? a! : b
以上是一个三元运算符,当可选值a不为空时,对其进行强制解包a!以访问a中的值;反之默认返回b的值。如果b是一个函数,也就不会执行了。
我们点开源码分析一下:
...
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
...
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?
通过源码,我们可以看到:
- <T>泛型
- 第一个变量optional,是一个T?类型
- 第二个变量defaultValue,默认值,通过@autoclosure修饰,自动转换成了一个闭包。比如"X",就会被封装成{return "X"},这里defaultValue不是一个值,而是一个函数,这样一来,我们可以在需要的时候再执行这个函数,而不需要的话,就不执行。
如果我们这里设置defaultValue是T类型呢?我们自定义自己的符号运算符???
来验证这个问题:
func A() -> String{
print("A is called!!!")
return "A"
}
func B() -> String{
print("B is called!!!")
return "B"
}
infix operator ???
func ???<T>(optional: T? , defaultValue: T) -> T{
if let value = optional{ return value }
return defaultValue
}
let AorB = A() ??? B()
结果是:
A is called!!!
B is called!!!
很显然A和B都执行了,不符合空值运算符的特性,然后我们将defaultValue设置为闭包类型,用@autoclosure修饰:
func A() -> String{
print("A is called!!!")
return "A"
}
func B() -> String{
print("B is called!!!")
return "B"
}
infix operator ???
func ??? <T>(optional: T?, defaultValue: @autoclosure () -> T) -> T {
if let value = optional{ return value}
return defaultValue()
}
let AorB = A() ??? B()
let AorX = A() ??? "X"
这里的结果如下:
A is called!!!
A is called!!!
可以看到,optional执行后,defaultValue就不会执行,如果你本身就是闭包(或者函数),非常好,你将享受这种延迟调用带来的优势;但如果你不是闭包,你将被自动封装成闭包,同时也享受了这种延迟调用带来的性能提升!,这里"X"就会自动被封装成{return "X"},如果这里的X不是string类型,而是其他类型,空合运算符前后数据类型不一致,就会导致崩溃。
$0- 闭包简化参数名
Swift 自动对行内闭包提供简写实际参数名,可以通过
$0
,$1
,$2
等名字来引用闭包的实际参数值。
let numbers = [1,2,3,4,5]
let sortNum = numbers.sorted(by: {(a, b) -> Bool in
return a > b
})
print(sortNum)
// 尾随闭包写法
let sortNum2 = numbers.sorted { (a, b) -> Bool in
return a > b
}
print(sortNum2)
let sortNum3 = numbers.sorted(by: { $0 > $1 })
print(sortNum3)
// 尾随闭包写法
let sortNum4 = numbers.sorted { $0 > $1 }
print(sortNum4)
简写实际参数名的数字和类型将会从期望的函数类型中推断出来。 in 关键字也能被省略,因为闭包表达式完全由它的函数体组成
var myDict: [String: Int] = ["Li": 22,"Zhang": 28]
myDict.sorted {
let key1 = $0.0
let value1 = $0.1
let key2 = $1.0
let value2 = $1.1
print(key1,value1,key2,value2)
return true
}
$0
是传递给closure的第一个参数的简写名称,在这种情况下,当您映射一个Dictionary时,该参数是一个元组,因此$0.0
是第一个一个键,$0.1
是第一个值
这里的输出结果是:
Li 22 Zhang 28