元组:轻量级"匿名结构体"
- 快速组装
swift
// 1. 不命名元素
let http404 = (404, "Not Found")
print(http404.0) // 404
// 2. 命名元素(推荐,可读性≈struct)
let http200 = (code: 200, description: "OK")
print(http200.code)
- 解构赋值
swift
let (statusCode, statusText) = http404
// 只想要其中一个
let (code, _) = http404
- 函数多返回值------官方最推崇场景
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。
- 与 switch 搭配
swift
let point = (0, 0)
switch point {
case (0, 0):
print("原点")
case (_, 0):
print("在 x 轴")
case (0, _):
print("在 y 轴")
default:
print("象限内")
}
错误处理:比 Optional 更丰富的失败信息
- 错误类型必须遵 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: ", "))"
}
}
}
- 函数标记 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()
}
- 调用方三种写法
写法 | 场景 | 备注 |
---|---|---|
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)")
}
- rethrows------高阶函数也能"转发"错误
swift
func retry(_ times: Int, _ body: () throws -> Void) rethrows {
for _ in 0..<times {
do {
try body()
} catch {
throw error
}
}
}
- 错误传递链------跨层穿透
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()