Skip to content

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 name

guard 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 使用。

本站总访问