Optionals 特性
前言
- Optionals 最佳实践
- 掌握guards 和多个可选
- 正确处理可选字符串与空字符串
- 同时处理各种可选
- 合并运算符合并返回默认值为nil
- 简化可选枚举
- 以多种方式处理可选布尔值
- 可选链
- 强制解包指南
- 隐式解包
1. Optionals 用途
- Optionals 是一个有值或者无值的框,有助于防止值为空时崩溃
- 通过显式处理常量或者变量可能为零或空的每种情况实现,需要输入一个可选值,展开获取值,如果有值则运行指定代码,如果不存在值,则执行另外的逻辑
- 有了Optionals 在编译时总能知道值是否为空
2. 简洁的展开可选项
-
举个例子:顾客
- 其中 id | email | balance 是强制必输项,不能为空, firstName | lastName 是可选项,可为空
- ? 表示可选项Optionals
struct Customer { let id: String let email: String let firstName: String? let lastName: String? let balance: Int }
-
Optionals 是一个枚举
- Swift Optionals 底层是枚举 Enum
public enum Optionals
{ case none case some(Wrapped) } - Wrapped 类型是一种泛型类型,表示可选类型中的值。如果它有一个值,它被包装在some case中;如果不存在任何值,则该可选值的大小写为none。
- Swift在Optionals语法中添加了一些语法糖,因为如果没有这种糖,编写如下的顾客结构将会发生改变。
-
没有语法糖的Optionals
struct Customer { let id: String let email: String let firstName: Optional
let lastName: Optional let balance: Int } - 显式编写Optionals代码仍然是合法的Swift代码,编译也很好,但它不符合Swift编码风格
-
匹配Optionals
-
通常,可以使用if-let展开可选的属性,例如展开顾客结构体的firstName属性
let customer = Customer(id: "30", email: "[email protected]", firstName: "Jake", lastName: "Freemason", balance: 300) print(customer.firstName) /// 编译器会提示firstName是一个Optional 类型需要进行盘空处理 if let firstName = customer.firstName { /// 这里的firstName 是unwapped类型 print("First name is \(firstName)") }
-
假如使用 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)]
-
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") }
这两段代码大同小异,显然Swift optionals的本质是枚举。
-
-
解包Optionals
-
合并多个optionals解包数据
if let firstName = customer.firstName, let lastName = customer.lastName { print("Friend's full name is \(firstName) \(lastName)") }
-
合并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) }
-
将模式匹配与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 { }
-
-
Optionals 判空
-
当某个值为空时需要做一些逻辑处理,并不关心值的后续使用Swift 提供了通配符 “—”
if let _ = customer.firstName, let _ = customer.lastName { print("The customer entered his full name") }
-
Optionals不为空
if customer.firstName != nil, customer.lastName != nil { print("The customer entered his full name") }
-
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: "[email protected]", firstName: "eric", lastName: "yan", balance: 100) print("\(eric)") 账号:007, 邮箱: [email protected], 姓名: eric.yan, 余额: 100 let eric = Customer(id: "007", email: "[email protected]", firstName: "eric", lastName: nil, balance: 100) print("\(eric)") 账号:007, 邮箱: [email protected], 姓名: eric, 余额: 100
- 小结
- Customer 实现CustomStringConvertible 协议
- 解包Optional类型属性 firstName和lastName,定义同名常量接收其值
- 在解包时let作用域内firstName/lastName既是解包后的数据并非Optional类型
- Customer 展示了一个自定义信息描述
- 这个示例展示了如何使用 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)"
}
}
guard 语句绑定解包后的firstName、lastName 到同名的常量中
如果firstName、lastName 两个常量有一个或都为nil, guard 返回空 “”
-
guard 的作用是像守门员一样保证通过数据的合法性,下面的语句返回解包后的全名
let eric = Customer(id: "007", email: "[email protected]", firstName: "eric", lastName: "yan", balance: 100) print("\(eric.displayName)") /// eric yan let eric = Customer(id: "007", email: "[email protected]", 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: "[email protected]", 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: "[email protected]", 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语句的组合。
思考
- 如果函数中不允许任何选项有值,那么确保所有选项都已填充的好策略是什么?
- 如果函数根据其内部的选项采用不同的路径,那么处理所有这些路径的正确方法是什么?
7. Optional类型数据返回逻辑处理
上一节我们学习了如何使用 switch + tuple 对Customer对象的多个Optional 类型属性数据进行精确逻辑控制,当其中一个属性为nil时返回另外一个属性,当两个都为空时返回nil。但这样并没有解决dispalyName 为nil时邮件昵称为空的问题。接下来我们学习如何在Optional 数据为空时返回一个有效数据
当Optional 类型数据为nil时,Swift 推荐使用 “??” 操作符进行返回默认数据,也称作合并空运算符。
举个例子:displayName为nil,则name 为 ”customer“
let eric = Customer(id: "007", email: "[email protected]", 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类型,我们该如何处理,接下来我们学习如何处理可选枚举。
- 例子:在Customer 中有一些客户时金牌会员,有一些事银牌会员,也有一些客户不是会员,那么我们的Customer 结构体就会多一个optional 属性 membership。
enum Membership {
/// 10% discount
case gold
/// 5% discount
case silver
}
struct Customer {
...
let membership: Membership?
}
- 当客户进行消费时,我们需要获取客户的会员资格,这时我们会展开Optional类型的membership,进行匹配。
let eric = Customer(id: "007", email: "[email protected]", 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")
}
- 上面的展开可选项方式逻辑正确,但并非最优解,我们可以使用“?”操作符来展开可选项枚举,获取membership数据。
let eric = Customer(id: "007", email: "[email protected]", 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 呢?
- 嵌套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: "[email protected]", firstName: nil, lastName: nil, balance: 100, membership: .silver, favoriteProduct: favoriteProduct)
- 常规方法: if let 展开嵌套Optional属性image,判空逻辑
let imageView: UIImageView = UIImageView()
if let favoriteProductImg = eric.favoriteProduct?.image {
imageView.image = favoriteProductImg
} else {
imageView.image = UIImage(named: "missing_image")
}
- 高级方法:借用用操作符??展开嵌套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的逻辑处理,避免异常。
- 优化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
- 返回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")
}
- 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更精确
- 实现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. 使用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. 强制解包指南
-
Force Unwarpping 简介
[图片上传失败...(image-3010a2-1670849288514)]
强制解包指在不检查Optional值是否存在的情况下直接展开可选值,直接使用其值。如果有值则程序正常运转,如果为nil,则应用程序会崩溃。
例如,创建一个Optional URL,需要传一个Optional 字符串
-
强制解包成功
/// validate url
let url = URL(string: "https://ww.baidu.com")
/// 展开Optional url
let forceUnwarppingUrl = url!
print(forceUnwarppingUrl)
-
强制解包失败
/// invalidate url
let invalidUrl = URL(string: "ww .baidu.com")
let forceUnwarppingInvalidUrl = invalidUrl!
/// Fatal error: Unexpectedly found nil while unwrapping an Optional value
print(forceUnwarppingInvalidUrl)
-
不可避免的强制解包
-
延迟错误处理
理想情况下是不需要做强制解包的,因为这样程序会有未知风险,但实际开发正我们无法真正避免强制解包。这时我们需要对错误进行延迟处理,返回一个nil或者抛出异常,当开始进行错误处理时函数可能会抛出一样,调用方也需要处理异常的逻辑。
-
当我们比编译器更早确认可以强制解包
let url = URL(string:"http://www.themayonnaisedepot.com")! /// http://www.themayonnaisedepot.com
现在这个url 的参数是一个常量,假如参数变为服务器返回的变量,那么直接强制解包时程序将面临崩溃的风险
-
强制解包一个有效的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)") }
提示:例如,iOS应用程序,用户可能会在崩溃日志中看到app提供的敏感信息。您在fatalErrormessage中输入的内容必须根据具体情况做出决定。
-
12. 隐式解包
-
前言
隐式解包(IUO)比较危险,它是一种特殊的Optionals,自动解包并且依赖于上下文,一旦使用不当就会导致程序Crash。
这一章我们对IUO进行深入剖析,从而熟练掌握IUO。
-
隐式解包初识
IUO是一种unwraps,并非实例
/// 隐式解包 let lastName: String! = "Eric" /// Optional let name: String? = "Yan" /// 强制解包 let firstName = name! print(lastName as Any, firstName) /// Optional("Eric") Yan
- IUO 的标示符是类型+叹号(!),可以理解为预展开
- 和强制解包类似,隐式解包比较危险,容易导致程序Crash 闪退
-
隐式解包实践
当创建IUO时,须在初始化后赋值,否则会导致程序崩溃。我们继续以蛋糕店为例进行实践。后端添加一个聊天服务,以便客户订购产品。
当后端服务器启动时,服务器首先启动进程监视器。此进程监视器确保在初始化和启动其他服务之前系统已就绪,这意味着必须在进程监视器之后启动聊天服务器。流程监视器就绪后,聊天服务器被传递给流程监视器。
[图片上传失败...(image-8711bc-1670849288514)]
/// 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不需要知道依赖关系。
-
练习
IUOs 有哪些优点?
无须在初始化时赋值,使用时可直接展开
13. 结语
- Optionals是在上面撒上语法糖的枚举。
- 您可以模式匹配枚举。
- 通过将多个Optionals放入一个元组中,一次在多个选项上进行模式匹配。
- 可以使用nil合并返回默认值。
- 使用Optionals链接深入挖掘可选值。
- 可以使用nil合并将可选布尔值转换为常规布尔值。
- 可以将Optionals布尔值转换为三种显式状态的枚举。
- 当需要值时,返回Optionals字符串,而不是空字符串。
- 只有当程序无法从空值恢复时,才使用强制展开。
- 当您想要延迟错误处理时,例如在原型制作时,请使用强制展开。
- 如果比编译器更清除数据时,强制展开更安全。
- 对初始化后立即实例化的属性使用IUOs隐式解包选项。