Swift-Optional

127 阅读5分钟

1、 认识可选值

之前我们在写代码的过程中早就接触过可选值,比如我们在代码这样定义:

class SSLTeacher {
    var age: Int?
}
复制代码

当前的 age 我们就称之为可选值,当然可选值的写法下面两者是等同的:

var age: Int?
var age: Optional<Int>
复制代码

那对于 Optional 的本质是什么?我们直接跳转到源码,打开 Optional.swift 文件:

@frozen
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
  case none
  case some(Wrapped)
}
复制代码

既然 Optional 的本质是枚举,那么我们也可以仿照系统的实现制作一个自己的 Optional

enum MyOptional<Value> {
    case some(Value)
    case none
}
复制代码

比如给定任意一个自然数,如果当前自然数是偶数返回,否则为 nil,我们应该怎么表达这个案例:

func getOddValue(_ value: Int) -> MyOptional<Int> {
    if value & 2 == 0 {
        return .some(value)
    } else {
        return .none
    }
} 
复制代码

这个时候给定一个数组,我们想删除数组中所有的偶数:

image.png

这个时候编译器就会检查我们当前的 value 会发现他的类型和系统编译器期望的类型不符,这个时候我们就能使用 MyOptional 来限制语法的安全性。

通过 enum 的模式匹配来取出对应的值:

var array = [1, 2, 3, 4, 5, 6]
for element in array {
    let value = getOddValue(element)
    switch value {
    case .some(let value):
        array.remove(at: array.firstIndex(of: value)!)
    default:
        print("value not exist")
    }
} 
复制代码

如果我们把上述的返回值更换一下,其实就和系统的 Optional 使用无疑

func getOddValue(_ value: Int) -> Int? {
    if value & 2 == 0 {
        return .some(value)
    } else {
        return .none
    }
}
复制代码

这样我们其实是利用当前编译器的类型检查来达到语法书写层面的安全性。

2、 if let

当然如果每一个可选值都用模式匹配的方式来获取值在代码书写上就比较繁琐,我们还可以使用 if let 的方式来进行可选值绑定:

if let value = value {
    array.remove(at: array.firstIndex(of: value)!)
}
复制代码

3、 gurad let

除了使用 if let 来处理可选值之外,我们还可以使用 gurad let 来简化我们的代码,

  • gurad let 和 if let 刚好相反
  • gurad let 守护一定有值。如果没有,直接返回
  • 通常判断如果有值之后,会做具体的逻辑实现,通常代码多

我们来看一个具体的案例:

let name: String? = "ssl"
let age: Int? = 18

guard let newName = name else {
    print("姓名 为空")
    return
}

guard let newAge = age else {
    print("年龄 为空")
    return
}
// 代码执行至此, nameNew 和 ageNew 一定有值
print(newName + String(newAge))
复制代码

4、 可选链**

我们知道在 OC 中给一个 nil 对象发送消息什么也不会发生, Swift 中我们是没有办法向一个 nil 对象直接发送消息,但是借助可选链可以达到类似的效果。

我们先来看下面代码

let str: String? = "abc"
let upperStr = str?.uppercased() 
print(upperStr)  // 输出:Optional<"ABC">

var str1: String?
let upperStr1 = str1?.uppercased() 
print(upperStr1) // 输出:nil
复制代码

我们再来看下面这段代码输出什么

let str: String? = "ssl"
let upperStr = str?.uppercased().lowercased()
print(upperStr) // 输出:Optional("ssl")
复制代码

同样的可选链对于下标和函数调用也适用

var closure: ((Int) -> ())?
closure?(1) // closure 为 nil 不执行

let dict: NSDictionary? = ["one": 1, "two": 2]
print(dict?["one"]) // 输出:Optional(1)
print(dict?["three"]) // 输出:nil
复制代码

5、 ?? 运算符 (空合并运算符)

( a ?? b ) 将对可选类型 a 进行空判断,如果 a 包含一个值就进行解包,否则就返回一个默认值 b 

  • 表达式 a 必须是 Optional 类型
  • 默认值 b 的类型必须要和 a 存储值的类型保持一致

看下面示例:

var age: Int?

var x = age ?? 10

print(x) // 输出 10
复制代码

6、 运算符重载

在源码中我们可以看到除了重载了 ?? 运算符, Optional 类型还重载了 == , ?= 等等运算符,实际开发中我们可以通过重载运算符简化我们的表达式。

比如在开发中我们定义了一个二维向量,这个时候我们想对两个向量进行基本的操作,那么我们就可以通过重载运算符来达到我们的目的

extension Vector {
    static func + (fistVector: Vector, secondVector: Vector) -> Vector {
        return Vector(x: fistVector.x + secondVector.x, y: fistVector.y + secondVector.y)
    }
    
    static prefix func - (vector: Vector) -> Vector {
        return Vector(x: -vector.x, y: -vector.y)
    }
    
    static func - (fistVector: Vector, secondVector: Vector) -> Vector {
        return fistVector + -secondVector
    }
}

var v1 = Vector(x: 10, y: 20)
var v2 = Vector(x: 20, y: 30)
var v3 = v1 + v2
print(v3)  // 输出 Vector(x: 30, y: 50)
var v4 = -v3 
print(v4)  // 输出 Vector(x: -30, y: -50)
复制代码

我们还可以自定义运算符,看下面示例:

infix operator --- : AdditionPrecedence
precedencegroup SSLPrecedence {
    lowerThan: AdditionPrecedence
    associativity: left
}

struct Vector {
    let x: Int
    let y: Int
}

extension Vector {
    static func --- (fistVector: Vector, secondVector: Vector) -> Vector {
        return Vector(x: fistVector.x * secondVector.x, y: fistVector.y * secondVector.y)
    }
}

var v1 = Vector(x: 10, y: 20)
var v2 = Vector(x: 20, y: 30)

var v5 = v1 --- v2
print(v5) // 输出 Vector(x: 200, y: 600)
复制代码

关于自定义运算符,可以查看 官方文档

7、 隐式解析可选类型

隐式解析可选类型是可选类型的一种,使用的过程中和非可选类型无异。它们之间唯一的区别是,隐式解析可选类型其实就是告诉 Swift 编译器,在运行时访问时,值不会为 nil。

看下面示例代码:

image.png

age1 为隐式解析可选类型,我们不需要再对它做解包操作了,编译器已经帮我们做了。

运行项目会发现,虽然编译期没有问题,但是运行的时候如果没有值依然会报错:

image.png