Swift可选类型详解

在OC中,我们通常会遇到这种写法

NSString *urlString = @"";
NSURL *url = [NSURL URLWithString:urlString];
// NSError 
NSError *error; 
// 这里的 String初始化方法可能会创建失败
NSString *result =  [[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];

只有result 等于 nil的时候 才应该去判断error是否为空

if (result != nil){
    // do something ...
}
//只有result 等于 nil的时候 才应该去判断error是否为空
// 检查 error ,非nil 的error 并不是一个有效的指针,直接使用可能造成异常
if (error) {
    // error do something...
}

实际上OC中 向nil发送消息是安全的,但是这种方式显得很繁琐

Swift中的可选值

Swift中的枚举 可以在它们的成员中包含另外的关联的值,Optional 也是通过枚举实现的

enum Optional {
    case none
    case some(Wrapped)
}

可选值的使用

完整的声明如下:

var num1:Optional //Optional
num1 = .some(10) // Optional(10)
num1 = .none // nil

由于可选类型在Swift中很常见,有更简洁的声明如下,推荐!!!

Optional 可以写成Int?,并且Optional遵守ExpressibleByNilLiteral协议可以用 nil 替代 .none

var num2:Int? // Int?
num2 = 10 //Optional(10)
num2 = nil //nil
num2? = 10 // nil

注意,这里的num2? = 10,当num2的真实值不为nil的时候,num2? = 10才会赋值成功,否则就是nil,这是可选链调用

包装一个非可选值,非可选值转换为可选

var ch:Character = "4" // Character
var optionalCh = Optional(ch) //Character?

获取枚举关联值的唯一方法是通过模式匹配,枚举的模式匹配

switch num1{
case .none: break //none 什么都不做
case .some(let value):print(value) 
}

这种通过模式匹配看着很有些复杂,Optional这个枚举还能简化一下:

switch num1{
case nil:break
case let v?:print(v)
}

可选值绑定
用枚举的模式匹配处理可选类型是有些复杂,可选类型作为Swift的一大亮点,系统当然提供了更加便利的方案:

if - let
if - let 语句会检查可选值是否为 nil,如果不是 nil,便会解包可选值。并且它可以是多个表达式的组合,可选值的解包结果可以作为后续表达式的值,解包后的作用域介于{}之中

if let num = num1 , num > 0{
    // do num operator ...
}

当然if - let 语句也支持多个可选值的解包

if let lnum = num1 , rnum = num2{
    // do lnum and rnum operator ...
}

if 后面可以是 var , if - var 。var对应的解包值可以在作用域内修改,但这是对原解包结果的拷贝,实际并不会修改原始值

let num1:Int? = 10
if var num = num1 {
    num += 1
    print(num) // 11
}
print(num1) // Optional(10)

while - let
while let 语句和 if let 非常相似,同样 let可以是var,可以是多个表达式的组合,唯一不同的是它表示当表达式的结果返回 nil 时便终止循环

//readLine()从“readLine 函数从标准输入中读取内容,并返回一个可选字符串。当到达输入末尾
//时,这个函数将返回 nil” 
while let n = readLine(){
    print(n)  
}

guard - let
guard let 更像是 if not let 的反义,可选解包不为nil的作用域从当前guard语句开始一直到离开作用域前。在 guard 的 else 代码块中,你可以执行任意代码,但是必须提供一个离开当前作用域的方式,例如:return,异常抛出fatalError()

func doSomething(_ value:Int?){
    guard let v = value else{
        return
    }
    
    //v的作用域一直到函数结束
    print(v)
    //...
}

可选链
在OC中对nil对象发送消息是安全的,Swift中可选链也可以做到

delegate?.handle() //delegate 如果为nil handle将不会被执行

OC中的Block在调用之前需要判断是否为null,否则也会crash,Swift中的可选链在调用闭包也是没问题的

let block:((String) -> Int)? = nil
block?("123")

调用可选链得到的返回值会自动包装成可选类型

let result = block?("123") //Int?

多个可选链的结果,编译器会帮我们展开,

extension Int{
    var opt:Self?{
        Optional(self)
    }
}
//以下调用结果不是 Int??? 而是Int?
var op = 5.opt?.opt?.opt // Optional(5)

可选链链赋值,可选变量赋值,如果加上?会判断当前变量是否为nil,如果为nil就会赋值失败

var num2:Int? = nil // 不初始化 默认是也nil
num2? = 10 // nil
num2 = 20 // 无条件赋值
num2? = 30 // Optional(30)

nil合并运算符

刚开始在使用可选值的时候,编译器会有这样的提示:Provide a default value to avoid this warning这句话意思是需要提供一个如下形式的默认值:nil合并运算符 ??

num1 ?? default value // 需要提供一个默认值

注意??两侧的类型必须一致,左侧必须是可选类型。比如左侧是Optional(Int),右侧必须是Int类型

看一个例子:这行代码,如果不加 ??是不能编译通过,因为字符串转int有可能失败,Int.init()返回值是个可选值,如果Int("123")初始化成功,返回解包后的值,失败返回??后面的值。

var number:Int =  Int("123") ?? 0

其中的??的行为类似于三目运算符:

var intValue = Int("123") //Optional(Int)
number =   (intValue!) != nil ? intValue! : 0

多个可选值通过??链接,表达式的结果是选择第一个不为nil的结果

var a:Int? = nil
var b:Int? = 2
var c:Int? = 3
var result = a ?? b ?? c ?? 0 //2

强制解包

上文提到的解包方式都是可选解包,只有当结果不为nil的时候,才会进行下一步。而强制解包是通过在可选值后面加上!,这种方式看似简洁,但是会忽略编译器警告,而且当结果为nil时会直接闪退

var number = Int("123")! // 123
var number1 = Int("123a")! // Fatal error: Unexpectedly found nil while unwrapping an Optional value

强制解包时机

当你能确定你的某个值不可能是 nil 时可以使用叹号,你应当会希望如果它意外是 nil 的话,程序应当直接挂掉

隐式解包

隐式解包,是在声明类型的时候,加上!。注意区别强制解包

这种方式声明的变量,在使用的时候系统会强制解包,因此看上去不像是一个可选类型。但是一旦值为nil,使用时候依然会异常(使用的时候系统会强制解包)。

let number3:Int! = Int("123") // Int!仍然是可选类型
number3 + 1 // 124 系统强制解包number3,如果number3为nil,闪退。否则正常运行

隐式解包,仍然是可选类型,可以对它进行可选解包

if let number4 = number3{
    number4 // int
}

你可能感兴趣的:(Swift可选类型详解)