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()
相关推荐
HarderCoder14 小时前
Swift 中的不透明类型与装箱协议类型:概念、区别与实践
swift
HarderCoder14 小时前
Swift 泛型深度指南 ——从“交换两个值”到“通用容器”的代码复用之路
swift
东坡肘子15 小时前
惊险但幸运,两次!| 肘子的 Swift 周报 #0109
人工智能·swiftui·swift
胖虎115 小时前
Swift项目生成Framework流程以及与OC的区别
framework·swift·1024程序员节·swift framework
songgeb1 天前
What Auto Layout Doesn’t Allow
swift
YGGP1 天前
【Swift】LeetCode 240.搜索二维矩阵 II
swift
YGGP2 天前
【Swift】LeetCode 73. 矩阵置零
swift
非专业程序员Ping3 天前
HarfBuzz 实战:五大核心API 实例详解【附iOS/Swift实战示例】
android·ios·swift
Swift社区4 天前
LeetCode 409 - 最长回文串 | Swift 实战题解
算法·leetcode·swift
YGGP6 天前
【Swift】LeetCode 54. 螺旋矩阵
swift