Swift 实战项目
学了 16 篇基础,是时候把它们串起来做点真东西了。这一章给三个完整、可运行的项目——Todo App、天气查询、简易计算器,每个都给出完整代码、运行结果和要点讲解。三个项目分别覆盖 Swift 的核心能力:数据建模与持久化、异步网络、类型安全建模。建议你把代码复制到 Swift Playground 或 Xcode 项目里亲手跑一遍。
实战 1:Todo App(struct + Array + UserDefaults)
需求:做一个待办管理程序,支持增加、完成、删除,数据用 UserDefaults 持久化——重启后数据还在。
涉及知识:struct、Codable、Identifiable、Array 高阶函数(filter / sorted / firstIndex)、JSONEncoder/JSONDecoder、UserDefaults。
// Todo.swift — Todo App 数据层(struct + Array + UserDefaults 持久化)
// 可在 Swift Playground 或任何 Swift 项目里运行
// 知识点:struct、Identifiable、Array 过滤/排序、UserDefaults、Codable
import Foundation
// 1. 数据模型:struct + Codable(可序列化)+ Identifiable(列表能用 id 区分)
struct Todo: Codable, Identifiable {
var id: UUID = UUID() // 自动生成唯一 id
var title: String
var done: Bool = false
var createdAt: Date = Date()
}
// 2. ViewModel:封装数据操作 + 持久化
class TodoStore {
private let key = "todos.json"
// @Published 让 SwiftUI 视图自动刷新(这里演示纯命令行也可用)
private(set) var items: [Todo] = []
init() { load() }
func add(_ title: String) {
items.append(Todo(title: title))
save()
}
func toggle(_ todo: Todo) {
// struct 是值类型,要找到下标重新赋值
if let i = items.firstIndex(where: { $0.id == todo.id }) {
items[i].done.toggle()
save()
}
}
func delete(_ todo: Todo) {
items.removeAll { $0.id == todo.id }
save()
}
// 函数式编程:filter + sorted,不修改原数组,返回新数组
var pending: [Todo] {
items.filter { !$0.done }.sorted { $0.createdAt < $1.createdAt }
}
var completed: [Todo] {
items.filter { $0.done }
}
// 持久化:用 JSONEncoder 把 [Todo] 编码成 Data,存到 UserDefaults
private func save() {
if let data = try? JSONEncoder().encode(items) {
UserDefaults.standard.set(data, forKey: key)
}
}
private func load() {
guard let data = UserDefaults.standard.data(forKey: key),
let saved = try? JSONDecoder().decode([Todo].self, from: data)
else { return }
items = saved
}
}
// 3. 演示用法
let store = TodoStore()
store.add("买牛奶")
store.add("写季度报告")
store.add("回邮件")
print("=== 全部 ===")
store.items.forEach { print($0.done ? "[x]" : "[ ]", $0.title) }
if let first = store.items.first {
store.toggle(first) // 标记完成
}
print("\n=== 未完成 ===")
store.pending.forEach { print("[ ] \($0.title)" )}
print("\n=== 已完成 ===")
store.completed.forEach { print("[x] \($0.title)" )}
// 输出:
// === 全部 ===
// [ ] 买牛奶
// [ ] 写季度报告
// [ ] 回邮件
//
// === 未完成 ===
// [ ] 写季度报告
// [ ] 回邮件
//
// === 已完成 ===
// [x] 买牛奶要点讲解:
struct是值类型:修改某个元素要先firstIndex找到下标再items[i].done.toggle(),不能像 class 那样引用修改——这是 Swift 的核心思维。Codable:一行声明就让编译器自动生成 JSON 编解码代码,这是 Swift 的杀手锏;Python/Ruby 要写一堆序列化代码。Identifiable:SwiftUI 列表要求元素有唯一id,声明这个协议即可——Swift 用协议驱动多态。- 函数式 API:
filter、sorted、removeAll都是返回新数组/修改原数组的高阶函数,Swift 鼓励函数式风格。 UserDefaults:iOS/macOS 的轻量持久化,适合少量数据;大数据用 Core Data / SwiftData。try?:可选 try,失败返回 nil 而不抛错——比 do/catch 简洁,适合"失败就忽略"的场景。
实战 2:天气查询(URLSession + async/await + Codable)
需求:调用 wttr.in 的简易天气 API,查询指定城市当前天气。先了解 async/await 是 Swift 5.5 引入的现代异步语法。
涉及知识:URLSession、async/await、Codable、JSONDecoder、CodingKeys、自定义 Error、HTTPURLResponse。
// Weather.swift — 天气查询:调用 wttr.in 简易天气 API
// 知识点:URLSession、async/await、Codable、JSONDecoder、错误处理
// 运行:swift Weather.swift(Swift 5.5+ 支持顶层 await)
import Foundation
// 1. 数据模型:只声明需要的字段,JSON 里多余字段会被忽略
struct WeatherResponse: Codable {
let currentCondition: [Current]
struct Current: Codable {
let tempC: String
let humidity: String
let weatherDesc: [Desc]
struct Desc: Codable {
let value: String
}
// CodingKeys:把 JSON 字段映射成 Swift 友好的名字
enum CodingKeys: String, CodingKey {
case tempC = "temp_C"
case humidity
case weatherDesc
}
}
}
// 2. 错误类型:实现 Error 协议,可被 throw/catch
enum WeatherError: Error, LocalizedError {
case badURL
case httpError(Int)
case decodeError
var errorDescription: String? {
switch self {
case .badURL: return "URL 格式错误"
case .httpError(let code): return "HTTP 错误: \(code)"
case .decodeError: return "JSON 解析失败"
}
}
}
// 3. async 函数:用 await 拿结果,像同步代码一样清晰
func fetchWeather(city: String) async throws -> String {
let encoded = city.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? city
guard let url = URL(string: "https://wttr.in/\(encoded)?format=j1") else {
throw WeatherError.badURL
}
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
throw WeatherError.httpError((response as? HTTPURLResponse)?.statusCode ?? -1)
}
guard let parsed = try? JSONDecoder().decode(WeatherResponse.self, from: data) else {
throw WeatherError.decodeError
}
let cur = parsed.currentCondition[0]
let desc = cur.weatherDesc.first?.value ?? "未知"
return "🏙 \(city)\n🌡 温度 \(cur.tempC)°C\n💧 湿度 \(cur.humidity)%\n☁ 天气 \(desc)"
}
// 4. 入口:用 Task 包一层才能 await(顶层 await 在脚本/playground 才有)
Task {
do {
let result = try await fetchWeather(city: "Beijing")
print(result)
} catch {
print("查询失败:\(error.localizedDescription)")
}
}
// 输出:
// 🏙 Beijing
// 🌡 温度 22°C
// 💧 湿度 45%
// ☁ 天气 Partly cloudy要点讲解:
async/await:Swift 5.5 的新语法,把"异步回调"写成"看起来同步"的代码,告别嵌套地狱(callback hell)。throws+try:Swift 错误处理三件套,函数标记 throws 才能抛错,调用处必须 try——错误路径显式且强制。CodingKeys:当 JSON 字段名和 Swift 属性名不一致(如temp_C)时,用 enum 显式映射。HTTPURLResponse:URLSession 默认不把 404/500 当错误,需要手动判断statusCode——这是新手常踩的坑。Task { }:顶层 await 只在主异步上下文里能用,普通代码用Task包一层启动异步任务。LocalizedError:实现这个协议让errorDescription给用户友好的错误文案,UI 直接显示。
实战 3:简易计算器(enum + protocol + switch)
需求:做一个支持加减乘除的计算器,用 enum 表示运算、用 protocol 抽象"可执行"、用 switch 穷尽所有情况。
涉及知识:enum 关联值、protocol、extension、switch 穷尽、throw/catch、where 守卫。
// Calculator.swift — 简易计算器:用 enum 表示操作,protocol 抽象运算
// 知识点:enum 关联值、protocol、switch 穷尽、扩展 extension、错误处理
import Foundation
// 1. 错误类型
enum CalcError: Error {
case divideByZero
case unknownOperator
}
// 2. 操作用 enum 关联值表示——比字符串更安全,编译器帮你查错
enum Operation {
case add(Double, Double)
case subtract(Double, Double)
case multiply(Double, Double)
case divide(Double, Double)
}
// 3. 协议:定义"可执行运算"的契约
protocol Executable {
func execute() throws -> Double
}
// 4. 让 Operation 遵循协议:switch 必须穷尽所有 case(编译期保证)
extension Operation: Executable {
func execute() throws -> Double {
switch self {
case .add(let a, let b): return a + b
case .subtract(let a, let b): return a - b
case .multiply(let a, let b): return a * b
case .divide(_, let b) where b == 0:
throw CalcError.divideByZero
case .divide(let a, let b): return a / b
}
}
// 符号描述:switch 再次穷尽
var symbol: String {
switch self {
case .add: return "+"
case .subtract: return "-"
case .multiply: return "×"
case .divide: return "÷"
}
}
}
// 5. 计算器:用字符串解析成 Operation,再执行
struct Calculator {
static func parse(_ op: String, _ a: Double, _ b: Double) throws -> Operation {
switch op {
case "+": return .add(a, b)
case "-": return .subtract(a, b)
case "*", "×": return .multiply(a, b)
case "/", "÷": return .divide(a, b)
default: throw CalcError.unknownOperator
}
}
static func run(_ op: String, _ a: Double, _ b: Double) -> String {
do {
let operation = try parse(op, a, b)
let result = try operation.execute()
return String(format: "%.2f %@ %.2f = %.2f", a, operation.symbol, b, result)
} catch CalcError.divideByZero {
return "错误:除数不能为 0"
} catch CalcError.unknownOperator {
return "错误:未知运算符 \(op)"
} catch {
return "未知错误:\(error)"
}
}
}
// 6. 演示
print(Calculator.run("+", 12, 8)) // 12.00 + 8.00 = 20.00
print(Calculator.run("-", 12, 8)) // 12.00 - 8.00 = 4.00
print(Calculator.run("×", 12, 8)) // 12.00 × 8.00 = 96.00
print(Calculator.run("÷", 12, 0)) // 错误:除数不能为 0
print(Calculator.run("÷", 12, 4)) // 12.00 ÷ 4.00 = 3.00
print(Calculator.run("%", 12, 4)) // 错误:未知运算符 %要点讲解:
- enum 关联值:把参数附在 case 上(
.add(a, b)),比用字符串更安全——编译器在编译期检查穷尽性,绝不会漏掉一个分支。 protocol:定义Executable协议让Operation遵循,任何类型只要支持execute()都能复用——这是面向协议编程的核心。switch穷尽:Swift 强制 switch 覆盖所有 case(或用 default),加新 case 时编译器会指出所有遗漏的 switch——重构利器。where守卫:在 case 里加条件匹配(case .divide(_, let b) where b == 0),让特殊路径与正常路径分离,逻辑清晰。extension:给已有类型(包括 enum)扩展方法,不必改原定义——Swift 的代码组织利器。- 类型安全收益:把"操作"建模成 enum 而非字符串,编译期就能拦住非法操作,这是 Swift 强类型的核心收益。
小结
三个项目分别对应 Swift 的三大主力场景:数据建模(struct + Codable + 持久化)、网络异步(URLSession + async/await)、类型安全建模(enum + protocol + switch)。它们都用到了前面学的基础——struct、enum、protocol、错误处理——只是组合起来解决真实问题。建议你至少把其中一个改造成自己的版本(给 Todo 加 SwiftUI 视图、给天气查询加多城市、给计算器加历史记录),做过一遍比读十遍更扎实。
恭喜!到这里 Swift 系列就真正完结了。接下来可以挑一个方向深入——SwiftUI(苹果全平台 UI)、iOS App 开发、服务端 Vapor、Swift Data/Core Data、开源贡献(苹果官方 Swift 编译器是 Swift 写的)——Swift 的世界以"安全 + 现代"为核心,边做边玩进步最快。
← 上一篇 Swift 进阶与下一步
返回 Swift 教程目录