Swift 基础语法全景(三):元组、错误处理与断言

元组:轻量级"匿名结构体"

  1. 快速组装
swift 复制代码
// 1. 不命名元素
let http404 = (404, "Not Found")
print(http404.0)   // 404

// 2. 命名元素(推荐,可读性≈struct)
let http200 = (code: 200, description: "OK")
print(http200.code)
  1. 解构赋值
swift 复制代码
let (statusCode, statusText) = http404
// 只想要其中一个
let (code, _) = http404
  1. 函数多返回值------官方最推崇场景
swift 复制代码
/// 返回"商"和"余"
func divide(_ a: Int, by b: Int) -> (quotient: Int, remainder: Int)? {
    guard b != 0 else { return nil }   // 除 0 返回 nil
    return (a / b, a % b)
}

if let result = divide(10, by: 3) {
    print("商:\(result.quotient),余:\(result.remainder)")
}

对比单独建 struct:

  • 一次性接口,无需额外类型污染命名空间;
  • 超过 3 个字段或需要方法 → 果断建 struct/class。
  1. 与 switch 搭配
swift 复制代码
let point = (0, 0)
switch point {
case (0, 0):
    print("原点")
case (_, 0):
    print("在 x 轴")
case (0, _):
    print("在 y 轴")
default:
    print("象限内")
}

错误处理:比 Optional 更丰富的失败信息

  1. 错误类型必须遵 Error 协议
swift 复制代码
enum SandwichError: Error, LocalizedError {
    case outOfCleanDishes
    case missingIngredients([String])
    
    var errorDescription: String? {
        switch self {
        case .outOfCleanDishes:
            return "没有干净的盘子"
        case .missingIngredients(let list):
            return "缺少食材:\(list.joined(separator: ", "))"
        }
    }
}
  1. 函数标记 throws
swift 复制代码
class Sandwich {}

func makeSandwich() throws -> Sandwich {
    let dishes = arc4random()
    let pantry = Set<String>(["Bread", "Any", "has"])
    guard dishes > 0 else { throw SandwichError.outOfCleanDishes }
    guard pantry.contains("Bread") && pantry.contains("Ham") else {
        throw SandwichError.missingIngredients(["Bread", "Ham"])
    }
    return Sandwich()
}
  1. 调用方三种写法
写法 场景 备注
try? 失败就变 nil 丢弃错误细节
try! 100% 不会错
do-catch 要细分错误 最常用
swift 复制代码
// 1) try? ------ 快速转 Optional
let sandwich = try? makeSandwich()   // Sandwich?

// 2) do-catch ------ 细粒度处理
func eat(_ wich: Sandwich) {
    
}
func washDishes() {
    
}
func buyGroceries(_ list: [String]) {
    
}

do {
    let sandwich = try makeSandwich()
    eat(sandwich)
} catch SandwichError.outOfCleanDishes {
    washDishes()
} catch SandwichError.missingIngredients(let list) {
    buyGroceries(list)
} catch {
    // 兜底,必须写,否则新增错误 case 会漏
    print("未知错误:\(error)")
}
  1. rethrows------高阶函数也能"转发"错误
swift 复制代码
func retry(_ times: Int, _ body: () throws -> Void) rethrows {
    for _ in 0..<times {
        do {
            try body()
        } catch {
            throw error
        }
    }
}
  1. 错误传递链------跨层穿透
swift 复制代码
// DAO → Service → UI
func loadUser() throws -> User   // DAO
func presentProfile() throws     // UI
// 任意一层不 catch,编译器自动加 throw,无需手工 return

Assertions vs Preconditions------何时让程序"死"

方法 生效构建 失败行为 用途 性能影响
assert Debug 终止 + 日志 开发期抓 Bug Release 构建中被移除
precondition Debug + Release 终止 + 日志 生产期防脏数据 Release 构建中仍会检查

示例:

swift 复制代码
let depth = arc4random() % 10
// 开发期:算法入参必须 ≥ 0
assert(depth >= 0, "递归深度不能为负")

let number = "1252412342390847"
// 生产期:银行卡号长度必须 16-19
precondition(number.count >= 16 && number.count <= 19,
             "卡号非法,立即终止流程")

进阶:

  • assertionFailure / preconditionFailure ------ 条件已隐含,直接报失败
  • 单元测试可用 XCTAssert 家族,与 assert 不冲突

综合实战

目标:写个 CLI 小工具,扫描指定目录下的 .swift 文件,统计

① 代码行数 ② 空行数 ③ 注释行数,结果通过元组返回;

若目录不存在则抛错;用断言保证"行数 ≥ 0"这一不变式。

swift 复制代码
#!/usr/bin/env swift

import Foundation

enum CLIError: Error {
    case directoryNotFound
}

typealias Stat = (code: Int, blank: Int, comment: Int)   // 类型别名

/// 统计单个文件
func analyze(_ path: String) throws -> Stat {
    let content = try String(contentsOfFile: path)
    var code = 0, blank = 0, comment = 0
    for line in content.split(separator: "\n", omittingEmptySubsequences: false) {
        let trim = line.trimmingCharacters(in: .whitespaces)
        if trim.isEmpty {
            blank += 1
        } else if trim.hasPrefix("//") {
            comment += 1
        } else {
            code += 1
        }
    }
    assert(code >= 0 && blank >= 0 && comment >= 0, "行数不能为负")
    return (code, blank, comment)
}

/// 递归目录
func analyzeDirectory(_ path: String) throws -> Stat {
    guard FileManager.default.fileExists(atPath: path) else {
        throw CLIError.directoryNotFound
    }
    var total: Stat = (0, 0, 0)
    let files = FileManager.default.enumerator(atPath: path)!
    for case let file as String in files {
        guard file.hasSuffix(".swift") else { continue }
        let sub = try analyze("\(path)/\(file)")
        total = (total.code + sub.code,
                 total.blank + sub.blank,
                 total.comment + sub.comment)
    }
    return total
}

// CLI 入口
do {
    let result = try analyzeDirectory(FileManager.default.currentDirectoryPath)
    print("Swift 文件统计")
    print("代码行:\(result.code)")
    print("空行:\(result.blank)")
    print("注释行:\(result.comment)")
} catch CLIError.directoryNotFound {
    print("错误:目录不存在")
} catch {
    print("未知错误:\(error)")
}

运行:

bash 复制代码
❯ swift stat.swift
Swift 文件统计
代码行:544157
空行:105382
注释行:101237

再谈"什么时候用哪个特性"------一张思维导图

vbnet 复制代码
需要表达"没有值"
├─ 仅关心"有没有" → Optional
├─ 关心"为什么失败" → Error
├─ 开发期断言 → assert
└─ 生产期哨兵 → precondition

需要多值返回
├─ 临时一次性 → Tuple
├─ 需要方法/协议 → Struct/Class
└─ 需要引用语义 → Class

需要类型别名
├─ 底层数据不变,语义更清晰 → typealias
└─ 跨平台兼容 → typealias + 条件编译 #if arch()
相关推荐
HarderCoder3 小时前
Swift 基础语法全景(一):从变量到类型安全
swiftui·swift
怪力左手17 小时前
地图下载工具
开发语言·ios·swift
YGGP21 小时前
【Swift】LeetCode 15. 三数之和
swift
HarderCoder1 天前
Swift 6.2 类型安全 NotificationCenter:告别字符串撞车
swift
HarderCoder1 天前
Swift 控制流深度解析(一):循环、条件与分支
swift
HarderCoder1 天前
Swift 控制流深度解析(二):模式匹配、并发与真实项目套路
swift
QWQ___qwq2 天前
SwiftUI 的状态管理包装器(Property Wrapper)
ios·swiftui·swift
大熊猫侯佩3 天前
AI 开发回魂夜:捉鬼大师阿星的 Foundation Models 流式秘籍
llm·ai编程·swift