SwiftInDepth_04_可选项Optionals

Optionals 特性

前言

  • Optionals 最佳实践
  • 掌握guards 和多个可选
  • 正确处理可选字符串与空字符串
  • 同时处理各种可选
  • 合并运算符合并返回默认值为nil
  • 简化可选枚举
  • 以多种方式处理可选布尔值
  • 可选链
  • 强制解包指南
  • 隐式解包

1. Optionals 用途

  • Optionals 是一个有值或者无值的框,有助于防止值为空时崩溃
  • 通过显式处理常量或者变量可能为零或空的每种情况实现,需要输入一个可选值,展开获取值,如果有值则运行指定代码,如果不存在值,则执行另外的逻辑
  • 有了Optionals 在编译时总能知道值是否为空

2. 简洁的展开可选项

  1. 举个例子:顾客

    • 其中 id | email | balance 是强制必输项,不能为空, firstName | lastName 是可选项,可为空
    1. ? 表示可选项Optionals
    struct Customer {
        let id: String
        let email: String
        let firstName: String?
        let lastName: String?
        let balance: Int
    }
    
  2. Optionals 是一个枚举

    • Swift Optionals 底层是枚举 Enum
    public enum Optionals<Wrapped> {
         case none
       case some(Wrapped)
    }
    
    • Wrapped 类型是一种泛型类型,表示可选类型中的值。如果它有一个值,它被包装在some case中;如果不存在任何值,则该可选值的大小写为none。
    • Swift在Optionals语法中添加了一些语法糖,因为如果没有这种糖,编写如下的顾客结构将会发生改变。
  3. 没有语法糖的Optionals

    struct Customer {
        let id: String
        let email: String
        let firstName: Optional<String>
        let lastName: Optional<String>
       let balance: Int
    }
    
    • 显式编写Optionals代码仍然是合法的Swift代码,编译也很好,但它不符合Swift编码风格
  4. 匹配Optionals

    1. 通常,可以使用if-let展开可选的属性,例如展开顾客结构体的firstName属性

      let customer = Customer(id: "30", email: "eric@gmail.com", firstName: "Jake", lastName: "Freemason", balance: 300)
      
      print(customer.firstName) /// 编译器会提示firstName是一个Optional 类型需要进行盘空处理
      
      if let firstName = customer.firstName { /// 这里的firstName 是unwapped类型
          print("First name is \(firstName)")
      }
      
    2. 假如使用 switch 去匹配没有语法糖的 Optional Value,需要对Optional 的 none 和 some 都进行模式匹配

      switch customer.firstName {
          case .some(let name): print("First name is \(name)")
          case .none: print("Customer didn't enter a first name")
      }
      
      

      [图片上传失败...(image-c0acb4-1670849288514)]

    3. Swift 除了模式匹配之外还提供了另一种小语法糖,可以使用如下示例替换.some & .none

      switch customer.firstName {
            /// let 替换 some,如果firstName 有值,将其值绑定到name并打印输出
          case let name?: print("First name is \(name)")
          /// nil 替换 none 明确匹配firstName 为空
          case nil: print("Customer didn't enter a first name")
      }
      
    4. 这两段代码大同小异,显然Swift optionals的本质是枚举。

  5. 解包Optionals

    1. 合并多个optionals解包数据

      if let firstName = customer.firstName, let lastName = customer.lastName {
          print("Friend's full name is \(firstName) \(lastName)")
      }
      
    2. 合并optionals给Boolean

      if let firstName = customer.firstName, customer.balance > 0 {
          let welcomeMessage = "Dear \(firstName), you have money on your account, want to spend it on mayonnaise?"
          print(welcomeMessage)
      }
      
    3. 将模式匹配与optionals 解包相结合

      if let firstName = customer.firstName, 4500..<5000 ~= customer.balance {
          let notification = "Dear \(firstName), you are getting close to afford our $50 tub!"
          print(notification)
      }
      

      假如使用if分别检查firstName 和 balance 模式匹配 将会产生嵌套的if和多个esle,让代码又臭又长

      /// 💩💩💩
      if let firstName = customer.firstName {
            if let hasBalance = 4500..<5000 ~= customer.balance {
                let notification = "Dear \(firstName), you are getting close to afford our $50 tub!"
                print(notification)
            } else {
            
            }
      } else {
            
      }
      
  1. Optionals 判空

    1. 当某个值为空时需要做一些逻辑处理,并不关心值的后续使用Swift 提供了通配符 “—”

      if
          let _ = customer.firstName,
          let _ = customer.lastName {
          print("The customer entered his full name")
      }
      
    2. Optionals不为空

      if
          customer.firstName != nil,
          customer.lastName != nil {
          print("The customer entered his full name")
      }
      
    3. Optionals为空

      if customer.firstName == nil, customer.lastName == nil {
          print("The customer has supplied a name")
      }
      

3. variable shadowing(变量替身)

  • 在解包Optional值时,定义一个unwrapped常量对原变量进行接收时,通常会这样命名常量unwrapped+rawName。在Swift中有一种技术叫做 variable shadowing,可以使用相同的常量名称进行接收。接下来我们看下示例。

    1. 实现 CustomStringConvertible

    我们来创建一个函数使用Customer的optional properties来演示variable shadowing,Customer需要遵守CustomStringConvertible协议。

    struct Customer {
        let id: String
        let email: String
        let firstName: String?
        let lastName: String?
        let balance: Int
    }
    
    /// customer 遵守CustomStringConvertible协议之后自定义实现description方法
    extension Customer: CustomStringConvertible {
        var description: String {
            var customDescription: String = "账号:\(id)" + ", 邮箱: \(email)"
             /// 定义同名常量接收optional变量
            if let firstName = firstName {
                customDescription += ", 姓名: \(firstName)"
            }
            /// 定义同名常量接收optional变量
            if let lastName = lastName {
                customDescription += ".\(lastName)"
            }
            customDescription += ", 余额: \(balance)"
            return customDescription
        }
    }
    

    接下来验证下variable shadowing,可以看出,当lastName 传nil时只打印出firstName:eric

    let eric = Customer(id: "007", email: "eric.yan@foxmail.com", firstName: "eric", lastName: "yan", balance: 100)
    print("\(eric)")
    账号:007, 邮箱: eric.yan@foxmail.com, 姓名: eric.yan, 余额: 100
    
    let eric = Customer(id: "007", email: "eric.yan@foxmail.com", firstName: "eric", lastName: nil, balance: 100)
    print("\(eric)")
    账号:007, 邮箱: eric.yan@foxmail.com, 姓名: eric, 余额: 100
    
    • 小结
      1. Customer 实现CustomStringConvertible 协议
      2. 解包Optional类型属性 firstName和lastName,定义同名常量接收其值
      3. 在解包时let作用域内firstName/lastName既是解包后的数据并非Optional类型
      4. Customer 展示了一个自定义信息描述
      5. 这个示例展示了如何使用 variable shadowing技术解包firstName/lastName,variable shadowing看起来易混淆,一旦掌握,确实提高了可读性

4. Optionals guard

   让我们看看当多个Optional类型都为nil时如何处理。当Mayonnaise Depot网络商店收到订单时,客户会收到通过电子邮件发送的确认消息,该消息需要由后端创建。此消息如下所示。

确认信息

/// 1. create func  swift 使用′′′ ′′′ 创建多行字符串
func createConfirmationMessage(name: String, product: String) -> String {
    return """
    Dear \(name),
    Thank you for ordering the \(product)!
    Your order will be delivered tomorrow.
    
    Kind regards,
    The Mayonnaise depot.
    """
}

/// 2. call 需要传入Customer‘s name & order product
let confirmationMsg = eric.createConfirmationMessage(name: "Jeff", product: "economy size party tub")

/// 3. print
print(confirmationMsg)
/*
Dear Jeff,
Thank you for ordering the economy size party tub!
Your order will be delivered tomorrow.

Kind regards,
The Mayonnaise depot.
*/

如果传入Customer.firstName | lastName,就必须确保其不为空,因为都是Optional类型

4.41 添加一个计算属性

刚才我们讨论到传入的name 值不能为空,因此我们需要新增一个计算属性确保传入的name不为空,暂且称其为displayName。

Swift提供一个关键字guard用来确保所有属性有值。让我们尝试使用guard来解决多个Optional属性为空的问题。

struct Customer {
    let id: String
    let email: String
    let firstName: String?
    let lastName: String?
    let balance: Int
    var displayName: String {
        guard let firstName = firstName, let lastName = lastName else {
            return ""
        }
        return "\(firstName) \(lastName)"
    }
}
  1. guard 语句绑定解包后的firstName、lastName 到同名的常量中

  2. 如果firstName、lastName 两个常量有一个或都为nil, guard 返回空 “”

  3. guard 的作用是像守门员一样保证通过数据的合法性,下面的语句返回解包后的全名

    let eric = Customer(id: "007", email: "eric.yan@foxmail.com", firstName: "eric", lastName: "yan", balance: 100)
    print("\(eric.displayName)")
    /// eric yan
    
    let eric = Customer(id: "007", email: "eric.yan@foxmail.com", firstName: "eric", lastName: nil, balance: 100) 
    print("\(eric.displayName)")
    ///   
    

5. 返回可选字符串

实际在app开发中可能会返回空字符串“”,由于Optional类型数据有可能为nil, 返回空数字符串通常是有意义的,但有些case返回空字符串并不适用,比如发给Customer邮件的昵称,如果Customer 的name 为空字符串,邮件昵称为dear + “”, 收件人的名字不见了,这样其实并不那么友好,这时就需要返回一个默认值

Optional属性展开为空值,返回默认值

let eric = Customer(id: "007", email: "eric.yan@foxmail.com", firstName: nil, lastName: "yan", balance: 100)
print("\(eric.displayName)")
/// firstName 为空 因此displayName 是空字符串“”
var msg = ""
let displayName = eric.displayName
if displayName.count > 0 {
    msg = eric.createConfirmationMessage(name: displayName, product: "Economy size partytub")
} else {
        /// displayName 是空字符串“” 会执行name:customer 
    msg = eric.createConfirmationMessage(name: "customer", product: "Economy size partytub")
}
print(msg)
/// log
Dear customer,
Thank you for ordering the Economy size partytub!
Your order will be delivered tomorrow.

Optional属性展开有值

let eric = Customer(id: "007", email: "eric.yan@foxmail.com", firstName: "eric", lastName: "yan", balance: 100)
print("\(eric.displayName)")
/// eric yan
var msg = ""
let displayName = eric.displayName
if displayName.count > 0 {
    /// displayName 有值 
    msg = eric.createConfirmationMessage(name: displayName, product: "Economy size partytub")
} else {
    msg = eric.createConfirmationMessage(name: "customer", product: "Economy size partytub")
}
print(msg)
/// log
Dear eric yan,
Thank you for ordering the Economy size partytub!
Your order will be delivered tomorrow.

6. Optional精确控制

上一节我们学习了使用guard 关键字进行多个Optional类型数据的进行判空,其中一旦有一个为空则进行拦截返回默认值或者空字符串,但无法对指定的某个数据为空进行逻辑处理,如:对 firstName、lastName 只有一个为空时进执行指定逻辑。这一节让我们将对多个Optional数据进行精确逻辑控制。

还是以Customer的displayName为例:

var displayName: String? {
    /// Cusotmer的成员属性 firstName & lastName 都为Optional,作为元组进行模式匹配
    switch (firstName, lastName) {
    /// 解包 firstName & lastName 绑定到fast & last 都非空
    case let (first?, last?): return first + "" + last
    /// 解包 firstName & lastName 绑定到 fast非空 & last 为空
    case let (first?, nil): return first + "" + ""
    /// 解包 firstName & lastName 绑定到 fast为空 & last 非空
    case let (nil, last?): return "" + "" + last
    /// 解包 firstName & lastName 绑定到 fast & last 都为空
    default: return ""
    }
}

这里我们使用操作符 “?"来展开Optional 类型数据firstName/lastName,并绑定到一个非Optional属性字符串常量first/last中。

当Customer的Optional属性firstName或lastName其中之一为空时,仍可使用另外一个作为displayName。

Note:在元组中添加三个或更多可选项时,需要使用不同的抽象以提高可读性,例如使用if-let语句的组合。

思考

  1. 如果函数中不允许任何选项有值,那么确保所有选项都已填充的好策略是什么?
  2. 如果函数根据其内部的选项采用不同的路径,那么处理所有这些路径的正确方法是什么?

7. Optional类型数据返回逻辑处理

上一节我们学习了如何使用 switch + tuple 对Customer对象的多个Optional 类型属性数据进行精确逻辑控制,当其中一个属性为nil时返回另外一个属性,当两个都为空时返回nil。但这样并没有解决dispalyName 为nil时邮件昵称为空的问题。接下来我们学习如何在Optional 数据为空时返回一个有效数据

当Optional 类型数据为nil时,Swift 推荐使用 “??” 操作符进行返回默认数据,也称作合并空运算符。

举个例子:displayName为nil,则name 为 ”customer“

let eric = Customer(id: "007", email: "eric.yan@foxmail.com", firstName: "", lastName: "", balance: 100)
print("displayName : \(eric.displayName)")
/// displayName : nil

var msg = ""
let name = eric.displayName ?? "customer"
msg = eric.createConfirmationMessage(name: name, product: "Economy size partytub")
print(msg)
/*
Dear customer,
Thank you for ordering the Economy size partytub!
Your order will be delivered tomorrow.

Kind regards,
The Mayonnaise depot.
*/

8. 简化可选枚举

之前已经了解了optionals是如何作为枚举进行模式匹配,例如创建Customer 的 dispalyName。现在有一个Enum也是Optional类型,我们该如何处理,接下来我们学习如何处理可选枚举。

  1. 例子:在Customer 中有一些客户时金牌会员,有一些事银牌会员,也有一些客户不是会员,那么我们的Customer 结构体就会多一个optional 属性 membership。
enum Membership {
    /// 10% discount
    case gold
    /// 5% discount
    case silver
}

struct Customer {
    ...
    let membership: Membership?
}
  1. 当客户进行消费时,我们需要获取客户的会员资格,这时我们会展开Optional类型的membership,进行匹配。
let eric = Customer(id: "007", email: "eric.yan@foxmail.com", firstName: nil, lastName: nil, balance: 100, membership: nil)
/// 展开可选项membership
if let membership = eric.membership {
    /// 对membership 枚举进行模式匹配
    switch membership {
    case .gold:
        print("10% discount")
    case .silver:
        print("5% discount")
    }
} else {
    /// membership为nil
    print("0% discount")
}
  1. 上面的展开可选项方式逻辑正确,但并非最优解,我们可以使用“?”操作符来展开可选项枚举,获取membership数据。
let eric = Customer(id: "007", email: "eric.yan@foxmail.com", firstName: nil, lastName: nil, balance: 100, membership: nil)
switch eric.membership {
case .gold?:
    print("10% discount")
case .silver?:
    print("5% discount")
case nil:
    print("0% discount")
}

在Switch中使用?操作符展开Optional 类型的枚举, 减少的if let 等冗余逻辑

9. 嵌套Optional

有时我们会遇到一种特殊情况,在展开Optional类型数据时该数据内部还有一个Optional类型数据,例如:在Customer 结构体中有一个Optional属性favoriteProduct,而Product 结构体内部也有一个Optional属性image, 我们怎样获取到Customer.favoriteProduct.image 呢?

  1. 嵌套Optional属性 image
struct Product {
    let id: String
    let name: String
    let image: UIImage?
}

struct Customer {
    ...
    let favoriteProduct: Product?
}

let favoriteProduct: Product? = Product(id: "0001", name: "Cappuccino", image: UIImage(named: "favorite_image"))

let eric: Customer = Customer(id: "007", email: "eric.yan@foxmail.com", firstName: nil, lastName: nil, balance: 100, membership: .silver, favoriteProduct: favoriteProduct)
  1. 常规方法: if let 展开嵌套Optional属性image,判空逻辑
let imageView: UIImageView = UIImageView()
if let favoriteProductImg = eric.favoriteProduct?.image {
    imageView.image = favoriteProductImg
} else {
    imageView.image = UIImage(named: "missing_image")
}
  1. 高级方法:借用用操作符??展开嵌套Optional属性image
let imageView: UIImageView = UIImageView()
let favoriteProductImage: UIImage = eric.favoriteProduct?.image ?? UIImage(named: "missing_image")!

10. 可选布尔值

众所周知,Booleans类型有两种状态:true 和 false,但Swift中的Boolean有三种状态:true/false/nil,Boolean出现三种状态导致代码逻辑变得复杂,这样nil就不能直接代表true,根据上下文不同有三种含义,有时表示true,有时表示false,有时表示其他状态。无论哪种都是为了进行nil的逻辑处理,避免异常。

  1. 优化Boolean为两种状态

当我们在解析API中的数据时尝试读取布尔值,或者从字典中获取key对应的value时,可以使用可选的布尔值。例如,服务器返回用户在app的一些设置项,是否自动登录,或者是否使用Apple的Face ID登录

/// 服务端返回用户的设置数据
let preferences = ["autoLogin": true, "faceIdEnabled": true]
/// 获取用户是否开启Face ID
let isFaceIdEnabled = preferences["faceIdEnabled"]
print(isFaceIdEnabled as Any)
/// Optional(true)

返回的isFaceIdEnabled 是一个Optional类型的数据有可能时nil或者false/true

如果需要返回一个确定的true/false可以使用操作符“??”对nil进行处理

let preferences = ["autoLogin": true, "faceIdEnabled": true]
let isFaceIdEnabled = preferences["faceIdEnabled"] ?? false
print(isFaceIdEnabled)
/// true
  1. 返回true

使用Optional Boolean时Swift不建议返回false,根据业务场景不同返回true更准确,例如:判断用户是否开启Face ID 以便引导用户进行下一步操作,用户已开启或者未设置则引导用户去设置,用户已关闭则无需引导设置


let preferences = ["autoLogin": true, "faceIdEnabled": true]
/// 这里如果为ni(未设置)或者已开启,则需要引导用户进行设置
if preferences["faceIdEnabled"] ?? true {
    print(preferences["faceIdEnabled"] as Any)
} else {
    /// 这里如果已关闭则无需要引导
    print("FaceId set false")
}
/// Optional(true)

let preferencesNotSet = ["autoLogin": true]
/// 这里如果为ni(未设置)或者已开启,则需要引导用户进行设置
if preferencesNotSet["faceIdEnabled"] ?? true {
    print(preferencesNotSet["faceIdEnabled"] as Any)
    /// nil
} else {
    /// 这里如果已关闭则无需要引导
    print("FaceId set false")
}
  1. Boolean 的三种状态

当Boolean需要表示三种状态时可以考虑Optional Boolean,也可以使用枚举来显式的表达三种状态。

faceId is enabled

let preferences = ["autoLogin": true, "faceIdEnabled": true]
/// 获取Optional类型的Face ID状态
let faceIdEnabled: Bool? = preferences["faceIdEnabled"]
/// 根据Optional 类型Boolean 初始化一个UserPreference 枚举
let faceIdEnabledEnum: UserPreference = UserPreference(rawValue: faceIdEnabled)!
/// 匹配枚举获取Face ID三种状态
switch faceIdEnabledEnum {
case .open:
    print("faceId is enabled")
case .close:
    print("faceId is disenabled")
case .notSet:
    print("faceId is notset")
}
/// faceId is enabled

faceId is disenabled

let preferences = ["autoLogin": true, "faceIdEnabled": false]
/// 获取Optional类型的Face ID状态
let faceIdEnabled: Bool? = preferences["faceIdEnabled"]
/// 根据Optional 类型Boolean 初始化一个UserPreference 枚举
let faceIdEnabledEnum: UserPreference = UserPreference(rawValue: faceIdEnabled)!
/// 匹配枚举获取Face ID三种状态
switch faceIdEnabledEnum {
case .open:
    print("faceId is enabled")
case .close:
    print("faceId is disenabled")
case .notSet:
    print("faceId is notset")
}
/// faceId is disenabled

faceId is notset

let preferences = ["autoLogin": true, "faceIdEnabled": nil]
/// 获取Optional类型的Face ID状态
let faceIdEnabled: Bool? = preferences["faceIdEnabled"]!
/// 根据Optional 类型Boolean 初始化一个UserPreference 枚举
let faceIdEnabledEnum: UserPreference = UserPreference(rawValue: faceIdEnabled)!
/// 匹配枚举获取Face ID三种状态
switch faceIdEnabledEnum {
case .open:
    print("faceId is enabled")
case .close:
    print("faceId is disenabled")
case .notSet:
    print("faceId is notset")
}
/// faceId is notset

使用枚举显式的匹配三种case,接收者需处理三种case,这样比Boolean更精确

  1. 实现RawRepresentable

RawRepresentable Protocol

遵循RawRepresentable是将类型转换为原始值并再次返回的惯用方法。遵守此协议可以简化到Objective-C的流程,并简化与其他协议的一致性,如Equalable、Hashable和Compariable。

一旦实现了RawRepresentable协议,类型就必须实现rawValue initializer和rawValue属性,才能将类型转换为枚举并返回

/// 1. 遵守协议
enum UserPreference: RawRepresentable {
    case open
    case close
    case notSet
    /// 2. 实现rawValue initializer
    init?(rawValue: Bool?) {
        switch rawValue {
        case true:
            self = .open
        case false:
            self = .close
        default:
            self = .notSet
        }
    }
    /// 3. 实现rawValue属性
    var rawValue: Bool? {
        switch self {
        case .open:
            return true
        case .close:
            return false
        case .notSet:
            return nil        
        }
    }   
}

  1. 练习题
/// 1. 使用Optional Boolean 创建枚举 AudioSetting,匹配三种case
let configuration = ["audioEnabled": true]
let audioSetting: AudioSetting? = AudioSetting(rawValue: configuration["audioEnabled"])

switch audioSetting {
case .enabled: print("Turn up the jam!")
case .disabled: print("sshh")
case .unknown: print("Ask the user for the audio setting")
default: print("unknown")
}
/// Turn up the jam!

let isEnabled = audioSetting?.rawValue
print(isEnabled as Any)
/// Optional(true)  

AudioSetting

enum AudioSetting: RawRepresentable {
    case enabled
    case disabled
    case unknown
    
    init?(rawValue: Bool?) {
        switch rawValue {
        case true:
            self = .enabled
        case false:
            self = .disabled
        default:
            self = .unknown
        }
    }
    
    var rawValue: Bool? {
        switch self {
        case .enabled:
            return true
        case .disabled:
            return false
        case .unknown:
            return nil
        }
    }
}

Validate

/// enabled
let configuration = ["audioEnabled": true]
let audioSetting: AudioSetting? = AudioSetting(rawValue: configuration["audioEnabled"])

switch audioSetting {
case .enabled: print("Turn up the jam!")
case .disabled: print("sshh")
case .unknown: print("Ask the user for the audio setting")
default: print("unknown")
}
/// Turn up the jam!

let isEnabled = audioSetting?.rawValue
print(isEnabled as Any)
/// Optional(true)

/// disabled
let configuration = ["audioEnabled": false]
let configValue: Bool? = configuration["audioEnabled"]
let audioSetting: AudioSetting? = AudioSetting(rawValue: configValue)
switch audioSetting {
case .enabled: print("Turn up the jam!")
case .disabled: print("sshh")
case .unknown: print("Ask the user for the audio setting")
default: print("unknown")
}
/// sshh
let isEnabled = audioSetting?.rawValue
print(isEnabled as Any)
/// Optional(false)

/// unknown
let configuration: [String : Any] = ["audioDisabled": true]
let configValue: Bool? = configuration["audioEnabled"] as? Bool
let audioSetting: AudioSetting? = AudioSetting(rawValue: configValue)

switch audioSetting {
case .enabled: print("Turn up the jam!")
case .disabled: print("sshh")
case .unknown: print("Ask the user for the audio setting")
default: print("unknown")
}
/// Ask the user for the audio setting

let isEnabled = audioSetting?.rawValue
print(isEnabled as Any)
/// nil

11. 强制解包指南

  1. Force Unwarpping 简介

[图片上传失败...(image-3010a2-1670849288514)]

强制解包指在不检查Optional值是否存在的情况下直接展开可选值,直接使用其值。如果有值则程序正常运转,如果为nil,则应用程序会崩溃。

例如,创建一个Optional URL,需要传一个Optional 字符串

  1. 强制解包成功
/// validate url
let url = URL(string: "https://ww.baidu.com")
/// 展开Optional url
let forceUnwarppingUrl = url!
print(forceUnwarppingUrl)
  1. 强制解包失败
/// invalidate url
let invalidUrl = URL(string: "ww .baidu.com")
let forceUnwarppingInvalidUrl = invalidUrl!
/// Fatal error: Unexpectedly found nil while unwrapping an Optional value
print(forceUnwarppingInvalidUrl)
  1. 不可避免的强制解包
    1. 延迟错误处理

      理想情况下是不需要做强制解包的,因为这样程序会有未知风险,但实际开发正我们无法真正避免强制解包。这时我们需要对错误进行延迟处理,返回一个nil或者抛出异常,当开始进行错误处理时函数可能会抛出一样,调用方也需要处理异常的逻辑。

    2. 当我们比编译器更早确认可以强制解包

      let url = URL(string:"http://www.themayonnaisedepot.com")! /// http://www.themayonnaisedepot.com
      

      现在这个url 的参数是一个常量,假如参数变为服务器返回的变量,那么直接强制解包时程序将面临崩溃的风险

    3. 强制解包一个有效的url

      当程序无法避免Crash时,我们可以提供更多的Crash信息及原因,而不是直接强制解包。

      例如:解析一个url时,如果此url 无效时,由于某种原因程序无法继续,url将返回nil,可以手动添加fatalError提供精准信息 #line 和 #function,这些信息可以辅助我们在开发时更近准的定位问题。

      guard let url = URL(string: path) else {
        fatalError("Could not create url on \(#line) in \   (#function)")
      }
      
    4. 提示:例如,iOS应用程序,用户可能会在崩溃日志中看到app提供的敏感信息。您在fatalErrormessage中输入的内容必须根据具体情况做出决定。

12. 隐式解包

  1. 前言

    隐式解包(IUO)比较危险,它是一种特殊的Optionals,自动解包并且依赖于上下文,一旦使用不当就会导致程序Crash。

    这一章我们对IUO进行深入剖析,从而熟练掌握IUO。

  2. 隐式解包初识

    IUO是一种unwraps,并非实例

    /// 隐式解包
    let lastName: String! = "Eric"
    /// Optional
    let name: String? = "Yan"
    /// 强制解包
    let firstName = name!
    print(lastName as Any, firstName)
    /// Optional("Eric") Yan
    
    1. IUO 的标示符是类型+叹号(!),可以理解为预展开
    2. 和强制解包类似,隐式解包比较危险,容易导致程序Crash 闪退
  3. 隐式解包实践

    当创建IUO时,须在初始化后赋值,否则会导致程序崩溃。我们继续以蛋糕店为例进行实践。后端添加一个聊天服务,以便客户订购产品。

    当后端服务器启动时,服务器首先启动进程监视器。此进程监视器确保在初始化和启动其他服务之前系统已就绪,这意味着必须在进程监视器之后启动聊天服务器。流程监视器就绪后,聊天服务器被传递给流程监视器。

    [图片上传失败...(image-8711bc-1670849288514)]

    /// IUO错误示例 <A crash from an IUO>
    let processMonitor: ProcessMonitor = ProcessMonitor.start()
    print(processMonitor.status())
    ///  Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
    
    /// IUO 正确示例
    let processMonitor: ProcessMonitor = ProcessMonitor.start()
    let chatService: ChatService = ChatService()
    processMonitor.chatService = chatService
    print("---",processMonitor.status())
    /// --- Everything is up and running
    

    app 启动时创建程序监控器ProcessMonitor,在程序监控器初始化完毕须创建聊天服务器,聊天服务器是程序监控器的IUO属性,必须在初始化程序监控器完成立刻创建,否则在程序监控器获取状态时无法获取聊天服务导致闪退。

    通过将chatService设置为IUO,无须在初始值时赋值,每取值时都也无须解包,直接获取即可,既方便又危险。IUO是一把双刃剑,尽量避免使用。

    例如,可以将ChatServiceFactory传递给ProcessMonitor,它可以为ProcessMonitor生成聊天服务器,而ProcessMonitor不需要知道依赖关系。

  4. 练习

    IUOs 有哪些优点?

    无须在初始化时赋值,使用时可直接展开

13. 结语

  • Optionals是在上面撒上语法糖的枚举。
  • 您可以模式匹配枚举。
  • 通过将多个Optionals放入一个元组中,一次在多个选项上进行模式匹配。
  • 可以使用nil合并返回默认值。
  • 使用Optionals链接深入挖掘可选值。
  • 可以使用nil合并将可选布尔值转换为常规布尔值。
  • 可以将Optionals布尔值转换为三种显式状态的枚举。
  • 当需要值时,返回Optionals字符串,而不是空字符串。
  • 只有当程序无法从空值恢复时,才使用强制展开。
  • 当您想要延迟错误处理时,例如在原型制作时,请使用强制展开。
  • 如果比编译器更清除数据时,强制展开更安全。
  • 对初始化后立即实例化的属性使用IUOs隐式解包选项。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容