Appearance
Swift 入门(6):可选类型与空安全
Swift 语言最突出的特性之一就是 空安全(null safety),它通过 可选类型(Optional) 来避免常见的空指针错误。
什么是可选类型?
在 Swift 中,变量默认必须有值。如果可能为空,就要用 ? 声明为 可选类型:
swift
var name: String? = "Alice"
name = nil // 合法,因为是可选类型普通变量不能为 nil:
swift
var age: Int = 18
// age = nil ❌ 错误解包(Unwrapping)
1. 强制解包(!)
如果确定有值,可以用 ! 取出:
swift
let name: String? = "Bob"
print(name!) // Bob⚠️ 如果为 nil 会崩溃:
swift
let n: String? = nil
print(n!) // ❌ 崩溃2. 可选绑定(if let)
更安全的写法:
swift
var name: String? = "Charlie"
if let n = name {
print("Hello, \(n)") // Hello, Charlie
} else {
print("No name")
}3. 可选绑定(guard let)
常用于函数中提前退出:
swift
func greet(_ name: String?) {
guard let n = name else {
print("No name")
return
}
print("Hello, \(n)")
}
greet("David") // Hello, David
greet(nil) // No nameguard let 的实战应用:Base64URL 编码
swift
// 实际项目中的 guard let 使用模式
private func b64url(_ data: Data?) -> String {
guard let d = data else {
return ""
}
// 继续处理 d(已确保不为 nil)
return d.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}
// 使用示例
let someData: Data? = "Hello".data(using: .utf8)
let result = b64url(someData) // 返回 Base64URL 编码字符串
let nilResult = b64url(nil) // 返回空字符串 ""多重 guard let 与 as? 组合
swift
// 复杂的 JSON 数据解析与类型转换
func parsePasskeyOptions(_ data: Data, _ cb: @escaping (String) -> Void) {
guard
let pub = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let rp = pub["rp"] as? [String: Any],
let rpId = rp["id"] as? String,
let challengeB64 = pub["challenge"] as? String,
let user = pub["user"] as? [String: Any],
let userIdB64 = user["id"] as? String,
let userName = user["name"] as? String
else {
cb(toJSON(["ok": false, "errMsg": "bad options"]))
return
}
// 所有解析都成功,继续处理
processPasskeyRegistration(rpId: rpId, challenge: challengeB64,
userId: userIdB64, userName: userName)
}as? String else 详解
语法结构:
swift
let userName = user["name"] as? String
// ↑ ↑ ↑
// 字典取值 安全转换 目标类型执行流程:
swift
// 1. user["name"] 返回 Any?(字典值可能不存在)
// 2. as? String 尝试将 Any? 转换为 String?
// 3. 如果转换失败(nil 或类型不匹配),整个 guard 失败
// 4. 执行 else 块,提前退出函数可能失败的情况:
swift
let testCases = [
// 情况1:字段不存在
["user": ["age": 25]], // user["name"] 返回 nil
// 情况2:类型不匹配
["user": ["name": 123]], // 123 as? String 返回 nil
// 情况3:嵌套字段缺失
["user": "not a dict"], // user 不是字典类型
// 情况4:成功情况
["user": ["name": "Alice"]] // 成功转换为 "Alice"
]为什么用 guard let 而不是 if let?
swift
// ❌ 使用 if let 会让代码嵌套过深
private func b64urlBad(_ data: Data?) -> String {
if let d = data {
return d.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
} else {
return ""
}
}
// ✅ guard let 让主逻辑保持在最外层,更清晰
private func b64urlGood(_ data: Data?) -> String {
guard let d = data else {
return "" // 提前退出,处理异常情况
}
// 主要逻辑在最外层,易读易维护
return d.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}多重 guard 的优势
swift
// ❌ 嵌套 if let(可读性差)
func badParseOptions(_ data: Data, _ cb: @escaping (String) -> Void) {
if let pub = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
if let rp = pub["rp"] as? [String: Any] {
if let rpId = rp["id"] as? String {
if let user = pub["user"] as? [String: Any] {
if let userName = user["name"] as? String {
// 主逻辑深度嵌套,难以阅读
processData(rpId: rpId, userName: userName)
} else { cb(errorJSON) }
} else { cb(errorJSON) }
} else { cb(errorJSON) }
} else { cb(errorJSON) }
} else { cb(errorJSON) }
}
// ✅ 多重 guard let(清晰简洁)
func goodParseOptions(_ data: Data, _ cb: @escaping (String) -> Void) {
guard
let pub = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let rp = pub["rp"] as? [String: Any],
let rpId = rp["id"] as? String,
let user = pub["user"] as? [String: Any],
let userName = user["name"] as? String
else {
cb(toJSON(["ok": false, "errMsg": "bad options"]))
return
}
// 主逻辑在最外层,清晰易读
processData(rpId: rpId, userName: userName)
}实际应用:WebAuthn 数据解析
swift
// 真实的 WebAuthn 选项解析
func parseWebAuthnOptions(_ optionsJson: String, _ callback: @escaping (String) -> Void) {
guard let data = optionsJson.data(using: .utf8) else {
callback(errorResponse("Invalid JSON string"))
return
}
guard
// 第一层:JSON 解析
let options = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
// 第二层:必需字段提取
let rp = options["rp"] as? [String: Any],
let rpId = rp["id"] as? String,
let rpName = rp["name"] as? String,
// 第三层:用户信息提取
let user = options["user"] as? [String: Any],
let userIdB64 = user["id"] as? String,
let userName = user["name"] as? String,
let userDisplayName = user["displayName"] as? String,
// 第四层:挑战码和其他参数
let challengeB64 = options["challenge"] as? String,
let pubKeyCredParams = options["pubKeyCredParams"] as? [[String: Any]]
else {
callback(errorResponse("Missing required fields"))
return
}
// 所有必需数据都已成功提取,继续处理
createPasskey(rpId: rpId, rpName: rpName,
userId: userIdB64, userName: userName,
challenge: challengeB64, params: pubKeyCredParams)
}4. 默认值(?? 运算符)
swift
let nickname: String? = nil
print(nickname ?? "Guest") // Guest隐式解包(!)
如果一个变量在生命周期内 一定会有值,可以用 String!:
swift
var username: String! = "Eve"
print(username) // 自动解包 → Eve⚠️ 一般少用,容易出错。
可选链(Optional Chaining)
访问可能为空的属性/方法时,可以用 ?.:
swift
class Person {
var pet: Pet?
}
class Pet {
var name: String = "Dog"
}
let p = Person()
print(p.pet?.name) // nil可选调用操作符(?.)的实战应用
swift
// 可选方法调用
class AuthManager {
private var callback: ((String) -> Void)?
func setCallback(_ cb: @escaping (String) -> Void) {
self.callback = cb
}
func triggerSuccess() {
// 使用 ?. 安全调用可选的闭包
self.callback?("认证成功")
// 等价于:
// if let callback = self.callback {
// callback("认证成功")
// }
}
func handleError(_ error: Error) {
defer {
self.callback = nil // 清理回调
}
// 可选调用:如果 callback 不为 nil 才执行
self.callback?(toJSON(["ok": false, "errMsg": error.localizedDescription]))
}
}?. 与 ! 的对比
swift
let manager = AuthManager()
// ❌ 强制调用 - 如果 callback 为 nil 会崩溃
// manager.callback!("消息") // 危险!
// ✅ 可选调用 - 安全,callback 为 nil 时什么都不做
manager.callback?("消息") // 安全复杂的可选链调用
swift
class User {
var profile: Profile?
}
class Profile {
var settings: Settings?
}
class Settings {
func updateTheme(_ theme: String) {
print("主题更新为: \(theme)")
}
}
let user = User()
// 多层可选链调用
user.profile?.settings?.updateTheme("暗黑模式")
// 只有当 user.profile 和 settings 都不为 nil 时才会执行
// 等价的完整写法:
if let profile = user.profile,
let settings = profile.settings {
settings.updateTheme("暗黑模式")
}实际应用场景
swift
// 1. UI 更新
class ViewController {
weak var delegate: ViewControllerDelegate?
func viewDidAppear() {
// 可选调用 delegate 方法
delegate?.viewControllerDidAppear(self)
}
}
// 2. 网络回调
class NetworkManager {
var onComplete: ((Data?) -> Void)?
var onError: ((Error) -> Void)?
func performRequest() {
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
self.onError?(error) // 可选调用错误回调
} else {
self.onComplete?(data) // 可选调用成功回调
}
}.resume()
}
}
// 3. 系统 API 回调(你的代码应用)
public func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
defer {
self.callback = nil
}
// 可选调用:只有当 callback 存在时才执行
self.callback?(self.toJSON(["ok": false, "errMsg": error.localizedDescription]))
// 如果不用 ?.,需要这样写:
// if let callback = self.callback {
// callback(self.toJSON(["ok": false, "errMsg": error.localizedDescription]))
// }
}实战示例:JSON 处理中的空安全
try? 与可选绑定的组合
swift
// 将字典转换为 JSON 字符串的安全写法
func dictToJSON(_ dict: [String: Any]) -> String {
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []) {
return String(data: data, encoding: .utf8) ?? "{}"
}
return "{}"
}
// 使用示例
let userInfo = ["name": "Alice", "age": 25]
let jsonString = dictToJSON(userInfo)
print(jsonString) // {"name":"Alice","age":25}try? 与 as? 的双重安全
swift
// JSON 反序列化:Data → 字典
func parseJSON(_ data: Data) -> [String: Any]? {
let pub = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
return pub
}
// 完整示例
let jsonData = """
{
"name": "Alice",
"age": 25,
"active": true
}
""".data(using: .utf8)!
if let dict = parseJSON(jsonData) {
print("姓名: \(dict["name"] ?? "未知")")
print("年龄: \(dict["age"] ?? 0)")
}语法分解:try? as?
swift
let pub = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
// ↑ ↑
// try? as?
// 错误处理 类型转换第一层安全:try?
swift
// JSONSerialization.jsonObject 可能抛出错误
// try? 将错误转换为 nil
// 等价于:
let jsonObject: Any?
do {
jsonObject = try JSONSerialization.jsonObject(with: data)
} catch {
jsonObject = nil
}第二层安全:as?
swift
// jsonObject 的类型是 Any,需要转换为 [String: Any]
// as? 安全类型转换,失败返回 nil
// 等价于:
let dict: [String: Any]?
if jsonObject is [String: Any] {
dict = jsonObject as! [String: Any]
} else {
dict = nil
}组合使用的优势
swift
// ❌ 不安全的写法(可能崩溃)
func badParseJSON(_ data: Data) -> [String: Any] {
let obj = try! JSONSerialization.jsonObject(with: data) // 可能抛出异常
return obj as! [String: Any] // 可能类型转换失败
}
// ✅ 安全的写法(双重保护)
func safeParseJSON(_ data: Data) -> [String: Any]? {
return try? JSONSerialization.jsonObject(with: data) as? [String: Any]
// 1. try? 处理 JSON 解析错误
// 2. as? 处理类型转换失败
}
// ✅ 更详细的错误处理
func detailedParseJSON(_ data: Data) -> [String: Any]? {
guard let jsonObject = try? JSONSerialization.jsonObject(with: data) else {
print("JSON 解析失败")
return nil
}
guard let dict = jsonObject as? [String: Any] else {
print("类型转换失败,期望字典类型")
return nil
}
return dict
}实际应用场景
swift
// 网络请求中的 JSON 处理
func handleAPIResponse(_ data: Data) {
if let response = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
// 成功解析为字典
let status = response["status"] as? String ?? "unknown"
let message = response["message"] as? String ?? ""
print("状态: \(status), 消息: \(message)")
} else {
print("响应格式错误")
}
}
// 配置文件解析
func loadConfig(_ data: Data) -> AppConfig? {
guard let configDict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
return nil
}
return AppConfig(
apiURL: configDict["apiURL"] as? String ?? "",
timeout: configDict["timeout"] as? Double ?? 30.0,
enableLogging: configDict["enableLogging"] as? Bool ?? false
)
}
// WebAuthn 数据解析
func parsePasskeyOptions(_ data: Data) -> PasskeyOptions? {
guard let optionsDict = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let rp = optionsDict["rp"] as? [String: Any],
let user = optionsDict["user"] as? [String: Any] else {
return nil
}
return PasskeyOptions(
rpId: rp["id"] as? String ?? "",
userName: user["name"] as? String ?? "",
challenge: optionsDict["challenge"] as? String ?? ""
)
}让我们分解这段代码的每个部分:
1. try? 的作用
swift
// JSONSerialization.data 可能抛出错误
// try? 将错误转换为 nil,返回 Optional<Data>
let data = try? JSONSerialization.data(withJSONObject: dict, options: [])
// data 的类型是 Data?(可选类型)2. if let 安全解包
swift
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []) {
// 只有 data 不为 nil 时才执行这里
// data 在这个作用域内是 Data 类型(已解包)
}3. 双重保险的默认值
swift
// String(data:encoding:) 也可能返回 nil
// 第一层保险:?? "{}"
return String(data: data, encoding: .utf8) ?? "{}"
// 第二层保险:整个 if let 失败时
return "{}"4. 完整的错误处理对比
swift
// ❌ 不安全的写法
func badDictToJSON(_ dict: [String: Any]) -> String {
let data = try! JSONSerialization.data(withJSONObject: dict, options: [])
return String(data: data, encoding: .utf8)! // 可能崩溃
}
// ✅ 安全的写法
func safeDictToJSON(_ dict: [String: Any]) -> String {
guard let data = try? JSONSerialization.data(withJSONObject: dict, options: []),
let jsonString = String(data: data, encoding: .utf8) else {
return "{}" // 任何步骤失败都返回默认值
}
return jsonString
}
// ✅ 更简洁的写法(你的代码)
func conciseDictToJSON(_ dict: [String: Any]) -> String {
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []) {
return String(data: data, encoding: .utf8) ?? "{}"
}
return "{}"
}5. 实际使用场景
swift
// 网络请求中的 JSON 处理
class APIClient {
func sendRequest(_ params: [String: Any]) {
let jsonString = dictToJSON(params)
// 即使 params 有问题,也不会崩溃,最多发送空对象 "{}"
print("发送数据: \(jsonString)")
}
}
// 日志记录
func logUserAction(_ action: String, data: [String: Any]) {
let jsonData = dictToJSON(data)
print("[\(action)] \(jsonData)")
// 保证日志一定能记录,不会因为 JSON 转换失败而中断
}小结
?→ 表示可能为空。!→ 强制解包,危险。if let/guard let→ 安全解包。??→ 提供默认值。?.→ 可选链,优雅避免空指针。try?→ 将错误转换为可选类型,配合if let使用。