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

猜你喜欢

转载自blog.csdn.net/ios_xumin/article/details/123653117