Swift编程代码规范指南
# Swift编程代码规范指南
本规范参考 Swift API Design Guidelines (opens new window) 及 SwiftLint Rules (opens new window),结合项目实践精简整理。
# 目录
- 01.规范概述
- 02.命名规范
- 03.代码格式规范
- 04.注释规范
- 05.值类型与引用类型设计
- 06.函数与闭包规范
- 07.可选值处理
- 08.错误处理
- 09.协议与扩展规范
- 10.属性与属性包装器
- 11.并发规范
- 12.内存管理
- 13.现代Swift特性
- 14.工具链与自动化
- 15.常见反模式
- 16.代码审查清单
- 17.常见陷阱速查
# 01.规范概述
# 1.1 为何需要代码规范
疑惑:Swift 语法优雅,代码能编译就行,为什么还要花时间制定规范?
答疑:Swift 是一门"有态度的语言"——它的 API Design Guidelines 本身就是一种规范哲学。但同一个功能仍有多种实现路径:闭包可以尾随也可以不尾随,可选值可以 guard 也可以 if let,struct 和 class 的选型影响深远。规范的本质是用 Swift 社区认同的最佳实践写代码,让团队协作像 Swift 自身一样流畅。
# 1.2 核心目标
| 目标 | 说明 |
|---|---|
| 清晰度 | 代码读起来像英文句子,看一眼就知道在做什么 |
| 一致性 | 整个项目像一个人写的 |
| 安全性 | 从类型层面避免空指针崩溃、数据竞争、循环引用 |
| 可维护性 | 半年后自己还能快速读懂 |
| 可审查性 | Code Review 聚焦逻辑而非格式 |
# 1.3 要求等级
- 必须(Mandatory):必须采用,违反将在 Code Review 中被驳回
- 推荐(Preferable):理应采用,特殊情况可不采用但需注释说明
- 可选(Optional):团队自行决定
# 1.4 Swift版本
代码应针对 Swift 5.9+,优先使用稳定特性。实验性特性(如 Macros)需团队评审后引入。
# 02.命名规范
# 2.1 命名总表
| 类型 | 规则 | 示例 |
|---|---|---|
| 文件名 | 大驼峰(同主类型名) | UserProfile.swift, String+Extensions.swift |
| 类/结构体/枚举/协议 | 大驼峰 | UserProfile, HTTPClient |
| 枚举case | 小驼峰 | case success, case invalidEmail |
| 方法/属性 | 小驼峰 | fetchUser(), userName |
| 布尔属性/方法 | is/has/should 前缀 | isEmpty, hasData, shouldRefresh |
| 协议 | 名词描述能力 / able/ible/ing | Codable, Equatable, Animating |
| 常量 | 小驼峰 | maxRetryCount, defaultTimeout |
| 类型参数 | 大驼峰或以 T 结尾 | Element, Value, RequestT |
| 缩写 | 全大写或全小写 | URL, JSON;userID, htmlString |
| 工厂方法 | 参数为核心名词 | UIView(frame:), Date(timeIntervalSinceNow:) |
# 2.2 API设计核心原则
Swift API Design Guidelines 的三个核心原则:
- Usage at the call site(调用方清晰):命名从调用方的角度出发,读起来像英文句子
- Clarity over brevity(清晰胜于简洁):可以有省略,但语义必须明确
- Write documentation comments(写文档注释):对每个声明写摘要注释
# 2.3 正确与错误示例
// ✅ 方法读起来像英文句子
list.insert(element, at: index)
list.move(from: source, to: destination)
// ✅ 工厂方法:参数即是创建的核心
let rect = CGRect(x: 0, y: 0, width: 100, height: 100)
let date = Date(timeIntervalSinceNow: 3600)
// ✅ 弱引用参数不带介词(其类型已说明角色)
func addObserver(_ observer: NSObject, forKeyPath path: String)
// ✅ 布尔属性读起来像断言
var isEmpty: Bool { count == 0 }
var isDescending: Bool
var hasChildren: Bool
// ❌ 错误示例
func doInsert(element: Element, at: Int) // "do" 冗余
func makeView(frame: CGRect) -> UIView // 应用 UIView(frame:) 工厂风格
var empty: Bool // 丢失 is 前缀,是"清空"还是"是否为空"?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2.4 命名反模式
| 反模式 | 示例 | 改进 |
|---|---|---|
| 方法名冗余介词 | func doFetch() | func fetch() |
| 首参数名与方法名重复 | func addEmployee(employee:) | func addEmployee(_:) |
| 返回布尔无前缀 | var valid: Bool | var isValid: Bool |
| 缩写含糊 | usrMgr, cfg | userManager, configuration |
| 拼音命名 | dianHua | phoneNumber |
| get 多余 | func getName() -> String | var name: String |
# 03.代码格式规范
# 3.1 缩进与空格 【必须】
// ✅ 缩进:4 个空格(不用 Tab)
class UserService {
func process(id: String) {
if isActive {
updateStatus()
}
}
}
// ✅ 运算符两侧加空格
let result = a + b * c
let ok = (x > 0) && (y < 10)
// ✅ 逗号后加空格、冒号左边无空格、类型标注时右边有空格
func greet(name: String, age: Int) -> String
let dict: [String: Any] = ["key": 42]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3.2 冒号与括号 【必须】
// ✅ 类型声明:冒号右边有空格
class User {
let name: String
let age: Int
}
// ✅ 字典与三元:冒号两边都有空格
let dict: [String: Int] = ["a": 1, "b": 2]
let value = condition ? "yes" : "no"
// ✅ 协议继承:冒号两边有空格
struct Dog: Animal, Codable { }
// ✅ 空集合用 () 和 [] 不用显式类型
let numbers = [Int]() // ✅
let dict = [String: Any]() // ✅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3.3 换行与对齐
// ✅ 参数过多:左括号换行,参数对齐
func fetch(
from url: URL,
method: HTTPMethod = .get,
headers: [String: String] = [:],
timeout: TimeInterval = 30
) async throws -> Data
// ✅ 链式调用:用 . 开头换行
let activeNames = users
.filter { $0.isActive }
.sorted { $0.lastLogin > $1.lastLogin }
.map { $0.name }
.prefix(10)
// ✅ 条件过长时换行
if isUserLoggedIn
&& hasValidToken
&& !isTokenExpired
&& hasNetworkPermission {
proceed()
}
// 单行长度建议不超过 120 字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 3.4 空行与 import 顺序
// ✅ import 按字母序,Apple 框架在前,第三方在后
import Foundation
import SwiftUI
import UIKit
import Alamofire
import Kingfisher
// ✅ 类型内不同逻辑组之间空一行
struct UserProfile {
// MARK: - Properties
let id: String
var name: String
var avatarURL: URL?
// MARK: - Computed Properties
var displayName: String {
name.isEmpty ? "Unknown" : name
}
// MARK: - Initialization
init(id: String, name: String) {
self.id = id
self.name = name
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 04.注释规范
# 4.1 核心原则
注释解释"为什么",代码说明"做了什么"。
// ❌ 差的注释:复述代码
count += 1 // count 加 1
// ✅ 好的注释:解释意图
count += 1 // 跳过 CSV 文件的标题行
// ✅ 记录决策原因
// 使用 O(n²) 双重循环而非哈希表,因为 n ≤ 20,哈希开销更大
func findDuplicates(in items: [Item]) -> [Item]
// ✅ 标注非显而易见的优化
// 用 lazy 过滤避免立即遍历全部数据(数据源可能 10w+)
let filtered = allItems.lazy.filter { $0.isActive }
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 4.2 文档注释/Markup
/**
用户管理服务,负责用户的 CRUD 和权限管理。
线程安全性:使用 `actor` 保证内部状态线程安全。
使用示例:
```swift
let service = UserService(repo: repo, cache: cache)
let user = await service.getUser(id: 123)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- Note: 该类为单例,所有方法在
MainActor上执行 - Warning:
deleteUser是不可逆操作 - Precondition:
userId必须 > 0 */ class UserService { // ... }
### 4.3 函数注释模板
```swift
/**
根据 ID 查询用户信息。
优先从缓存获取,未命中时查数据库并回填缓存。
- Parameter userId: 用户唯一标识,必须 > 0
- Returns: 用户对象;若不存在则返回 `nil`
- Throws: `NetworkError.timeout` 当网络超时
*/
func getUser(userId: Int64) async throws -> User?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 4.4 TODO 与 FIXME
// TODO(yc): 添加分页查询支持,预计 v2.0 实现
func getAllUsers() -> [User]
// FIXME(yc): userId 为 nil 时缺少降级策略,应展示空态页面
func loadUser(userId: String?)
// HACK(yc): 第三方 SDK 的 bug,等待官方修复后移除(#1234)
func workaroundForSdkBug()
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 05.值类型与引用类型设计
# 5.1 struct vs class 选型 【必须】
// ✅ 优先用 struct:值语义,线程安全,无继承需求
struct UserProfile {
let id: String
var name: String
var email: String?
}
// ✅ 需要引用语义 / 共享可变状态的用 class
class DataManager {
private var cache: [String: Any] = [:]
func store(_ value: Any, for key: String) {
cache[key] = value
}
func retrieve(_ key: String) -> Any? {
cache[key]
}
}
// ✅ 选型决策表
// struct → 数据模型、配置、API 响应、CGPoint/CGSize 等值类型
// class → 控制器、管理器、ViewModel(引用同一对象)、需 deinit 的场景
// enum → 有限集合:状态机、结果、HTTP 方法
// actor → 需并发安全隔离的可变状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 5.2 枚举规范 【推荐】
// ✅ 枚举 + 关联值:表达状态和结果
enum LoadingState<T> {
case idle
case loading
case loaded(T)
case failed(Error)
}
// ✅ 带 RawValue 的枚举(Int/String)
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
}
// ✅ 嵌套枚举限定作用域
struct API {
enum Endpoint {
case login
case userProfile(id: String)
case search(query: String, page: Int)
}
}
// ❌ 不要为枚举的每个 case 都写 rawValue → 用关联值更强大
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 5.3 访问控制 【必须】
// ✅ Swift 默认 internal(模块内可见),按需收紧
public class APIClient {
public func fetchUsers() async throws -> [User] // 公开 API
func buildURL() -> URL // 模块内部使用(internal 默认)
private func signRequest(_ req: URLRequest) -> URLRequest // 仅内部使用
}
// ✅ 属性的 setter 比 getter 更严格
public private(set) var currentUser: User? // 外部只读,内部可写
// ✅ 不导出内部类型 → internal 即可,没必要 public
// ❌ 不要把所有东西都标成 public(暴露内部实现)
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 5.4 初始化器规范 【推荐】
// ✅ struct:编译器自动合成 memberwise init,不需要自己写
struct Point {
var x: Double
var y: Double
}
let p = Point(x: 10, y: 20) // 自动获得 Point(x:y:)
// ✅ 需要时提供便利 init
extension Point {
init(origin: (Double, Double)) {
self.init(x: origin.0, y: origin.1)
}
}
// ✅ class:指定初始化器与便利初始化器
class UserViewController: UIViewController {
let userId: String
// 指定初始化器
init(userId: String) {
self.userId = userId
super.init(nibName: nil, bundle: nil)
}
// 便利初始化器
convenience init() {
self.init(userId: UUID().uuidString)
}
required init?(coder: NSCoder) {
fatalError("Use init(userId:)")
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 06.函数与闭包规范
# 6.1 函数设计原则
// ✅ 按需省略第一个参数标签(当参数角色已由函数名表达时)
list.insert(element, at: index) // element 省略标签
list.move(from: source, to: destination) // from/to 保留标签
// ✅ 弱引用参数不带介词
func addObserver(_ observer: NSObject, forKeyPath path: String)
// ✅ 默认参数替代重载
func connect(host: String, port: Int = 8080, useTLS: Bool = true)
// ✅ 参数顺序:必选在前,可选在后
func search(query: String, page: Int = 1, pageSize: Int = 20)
// ❌ 不要用 Boolean 参数做行为切换 —> 拆成两个函数
// func save(data: Data, sync: Bool) ❌
func saveLocal(_ data: Data) // ✅
func saveRemote(_ data: Data) async // ✅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 6.2 闭包写法选择 【推荐】
// ✅ 尾随闭包:最后一个参数是闭包且较长时
UIView.animate(withDuration: 0.3) {
view.alpha = 0
}
// ✅ 多个尾随闭包(Swift 5.3+)
UIView.animate(withDuration: 0.3) {
view.alpha = 0
} completion: { finished in
view.removeFromSuperview()
}
// ✅ 简单闭包用 $0、$1(单表达式、上下文明确)
let names = users.map { $0.name }
let sorted = users.sorted { $0.score > $1.score }
// ✅ 复杂闭包显式命名参数(>5 行或多层嵌套时)
users.filter { user in
let isValid = validate(user)
let hasPermission = checkPermission(for: user)
return isValid && hasPermission
}
// ✅ 闭包参数类型可推断时省略类型声明
let filtered = users.filter { $0.isActive } // 省略 (User) -> Bool
// ❌ 不要过度使用 $0, $1, $2... → 超过 $1 显式命名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 6.3 捕获列表与循环引用 【必须】
// ✅ weak self:避免闭包与 self 互相强引用
class ViewController: UIViewController {
var api: APIClient!
func loadData() {
api.fetch { [weak self] result in
guard let self else { return } // Swift 5.8+ 简写
self.updateUI(with: result)
}
}
}
// ✅ unowned self:当 self 的生命周期保证长于闭包时
class AnimationHandler {
func start() {
DispatchQueue.main.async { [unowned self] in
self.performStep() // self 一定存在
}
}
}
// ✅ 捕获多个变量时:weak 和 strong 可混合
api.fetch { [weak self, unowned dependency] result in
guard let self else { return }
self.process(result, with: dependency)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 6.4 自动闭包与逃逸闭包 【推荐】
// ✅ @autoclosure:延迟求值,调用方不用写花括号
func assert(_ condition: @autoclosure () -> Bool, _ message: String) {
if !condition() { fatalError(message) }
}
assert(x > 0, "x must be positive") // 不需要 { x > 0 }
// ✅ @escaping:闭包在函数返回后才执行
func performLater(_ work: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
work()
}
}
// ❌ 非逃逸闭包(默认行为)不要加 @escaping
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 07.可选值处理
# 7.1 guard let 提前返回 【推荐】
// ✅ guard let:提前返回,减少嵌套("快乐路径"在主逻辑中)
func process(user: User?) {
guard let user else { return }
guard let email = user.email else {
showError("Email required")
return
}
guard isValid(email) else {
showError("Invalid email")
return
}
// 主逻辑:所有前置条件已满足
sendWelcomeEmail(to: email, name: user.name)
}
// ❌ if let 嵌套过深
func badProcess(user: User?) {
if let user {
if let email = user.email {
if isValid(email) {
sendWelcomeEmail(to: email, name: user.name)
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 7.2 可选链与 nil 合并 【必须】
// ✅ 可选链:多层嵌套属性的安全取值
let city = user?.address?.city?.name
// ✅ nil 合并运算符 ??
let displayName = user?.name ?? "Unknown"
let timeout = config?.timeout ?? 30
// ✅ ?? + 提前返回
func validate(user: User?) {
let name = user?.name ?? "" // 提供空字符串默认值
guard !name.isEmpty else { return }
proceed(with: name)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 7.3 可选值变换
// ✅ map:对可选值做变换
let uppercased = user?.name.map { $0.uppercased() }
let url = path.flatMap { URL(string: $0) } // flatMap 展开双层 Optional
// ✅ 可选值比较
if let score, score > 60 { } // Swift 5.7+ 简写(等价于 if let score = score)
// ✅ 可选值条件:逗号分隔多条件
if let name = user?.name, let email = user?.email, isValid(email) {
sendEmail(to: email, name: name)
}
// ❌ 不要用 nil 做业务信号 → 用 Optional 或 Result
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 7.4 强制解包边界 【必须】
// ❌ 代码审查时,每个 ! 都必须有充分理由
// ✅ 唯一可接受的场景:
// 1. IBOutlet(Storyboard 保证非空)
@IBOutlet private var tableView: UITableView!
// 2. 测试代码中断言
XCTAssertNotNil(user)
XCTAssertEqual(user!.name, "Alice")
// 3. 程序逻辑已保证(极少数情况)
let url = URL(string: "https://api.example.com")! // 硬编码 URL 必定有效
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 7.5 隐式解包可选值 【必须】
// ✅ IUO(Implicitly Unwrapped Optional)仅用于无法在 init 时赋值
// 的依赖(如 IBOutlet、注入)
class MyViewController: UIViewController {
@IBOutlet private var nameLabel: UILabel! // ✅ Storyboard 初始化
var coordinator: AppCoordinator! // ❌ 改成普通 Optional + guard
}
// ✅ 改进:用构造注入
class ModernViewController: UIViewController {
private let viewModel: UserViewModel
private let nameLabel: UILabel // 代码创建的 UI
init(viewModel: UserViewModel) {
self.viewModel = viewModel
self.nameLabel = UILabel()
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("Use init(viewModel:)")
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 08.错误处理
# 8.1 throws vs Result 【推荐】
// ✅ throws:函数可能失败,调用方必须处理
func fetchUser(id: String) async throws -> User
// ✅ Result:错误需要跨异步边界传递 / 存储
func loadAll(ids: [String]) async -> [Result<User, Error>] {
await withTaskGroup(of: Result<User, Error>.self) { group in
for id in ids {
group.addTask {
do { return .success(try await fetchUser(id: id)) }
catch { return .failure(error) }
}
}
return await group.reduce(into: []) { $0.append($1) }
}
}
// ✅ 选型建议
// throws → 同步 / 单步异步操作
// Result → 批量操作、需要存储错误结果、Combine 管道中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 8.2 do-catch 规范 【必须】
// ✅ 按错误类型分支捕获
func loadProfile() {
do {
let user = try fetchUser()
updateUI(user)
} catch NetworkError.timeout {
showError("网络超时,请重试")
} catch NetworkError.unauthorized {
promptLogin()
} catch {
showError("未知错误: \(error.localizedDescription)")
logError(error)
}
}
// ✅ throws 函数内部 try:错误向上传播
func loadAndCache() async throws -> User {
let user = try await api.fetchUser() // 失败则向上 throw
cache.store(user)
return user
}
// ❌ 不要让 catch 块为空(吞掉错误)
// do { ... } catch { } // ❌ 错误被沉默丢弃
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 8.3 try? 与 try! 的边界 【必须】
// ✅ try?:不关心具体错误,只需成功/失败
let config = try? loadConfig() ?? .default
let cached = try? cache.retrieve(key)
// ✅ try? + guard:失败即退出
guard let data = try? Data(contentsOf: fileURL) else {
showError("无法读取文件")
return
}
// ❌ try!:除非 100% 确定不会抛错(与 ! 同理)
// let data = try! Data(contentsOf: bundledURL) // Bundle 中文件,可接受
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 8.4 自定义 Error 类型 【推荐】
// ✅ 用枚举实现 Error,清晰分类
enum NetworkError: LocalizedError {
case timeout(after: TimeInterval)
case unauthorized
case serverError(code: Int, message: String)
case noConnection
var errorDescription: String? {
switch self {
case .timeout(let seconds):
return "请求超时(\(seconds)秒)"
case .unauthorized:
return "未授权,请重新登录"
case .serverError(let code, let message):
return "服务器错误 \(code): \(message)"
case .noConnection:
return "网络连接不可用"
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 09.协议与扩展规范
# 9.1 协议设计原则
// ✅ 协议小而精:一个协议只做一件事
protocol Reusable {
static var reuseIdentifier: String { get }
}
protocol Configurable {
associatedtype Configuration
func configure(with config: Configuration)
}
// ✅ 扩展提供默认实现(减少实现方的负担)
extension Reusable {
static var reuseIdentifier: String {
String(describing: self)
}
}
// ✅ 协议组合:typealias 简化复杂类型
typealias CodableEquatable = Codable & Equatable
typealias ReusableCell = Reusable & Configurable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 9.2 协议组合与关联类型 【推荐】
// ✅ associatedtype:协议泛型
protocol Repository {
associatedtype Item
func fetch(id: String) async throws -> Item
func save(_ item: Item) async throws
}
// ✅ 实现时指定具体类型
class UserRepository: Repository {
typealias Item = User // 也可省略,编译器自动推断
func fetch(id: String) async throws -> User { ... }
func save(_ user: User) async throws { ... }
}
// ✅ 泛型约束 + where
func sync<T: Repository>(_ repo: T) async throws where T.Item: Codable {
let items = try await repo.fetch(id: "all")
let data = try JSONEncoder().encode(items) // Codable 保证可用
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 9.3 扩展组织方式 【推荐】
// ✅ 用 extension 分组:按协议实现、功能模块分组
class UserViewController: UIViewController {
// 核心属性 + 生命周期
}
// MARK: - UITableViewDataSource
extension UserViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
}
// MARK: - UITableViewDelegate
extension UserViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
}
// MARK: - Private Helpers
private extension UserViewController {
func setupUI()
func bindViewModel()
}
// ✅ 对系统类型的扩展放在独立文件(如 String+Extensions.swift)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 9.4 面向协议编程实践
// ✅ 协议 + 扩展 = 模板方法模式
protocol Validatable {
func validate() -> Bool
}
extension Validatable {
func validateAndExecute(_ action: () -> Void) {
if validate() { action() }
}
}
// ✅ SwiftUI 中广泛使用
protocol Shape {
func path(in rect: CGRect) -> Path
}
extension Shape {
func fill<S: ShapeStyle>(_ style: S) -> some View { ... }
}
// ❌ 不要用协议替代简单的组合:过度协议化反而增加复杂度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 10.属性与属性包装器
# 10.1 存储属性与计算属性 【推荐】
// ✅ let:不可变优先
struct UserProfile {
let id: String // 不可变
var name: String // 需要修改
var email: String? // 可选值
}
// ✅ 计算属性:无副作用,O(1)
var isEmpty: Bool { count == 0 }
var fullName: String { "\(firstName) \(lastName)" }
// ❌ 计算属性中不要有网络请求、数据库查询(应用方法)
// var remoteConfig: Config { ... } // ❌ 副作用隐藏在 getter 中
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 10.2 SwiftUI 属性包装器 【必须】
// ✅ @State:View 内部状态(值类型)
struct ContentView: View {
@State private var text: String = ""
var body: some View {
TextField("输入", text: $text) // $ 获取 Binding
}
}
// ✅ @StateObject:View 拥有并持有的 ObservableObject
struct ContentView: View {
@StateObject private var viewModel = ContentViewModel()
}
// ✅ @ObservedObject:外部传入的 ObservableObject
struct DetailView: View {
@ObservedObject var viewModel: DetailViewModel
}
// ✅ @EnvironmentObject:环境注入的 ObservableObject
struct SettingsView: View {
@EnvironmentObject var settings: AppSettings
}
// ✅ @Binding:双向绑定,不持有数据
struct ToggleView: View {
@Binding var isOn: Bool
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 10.3 自定义属性包装器 【可选】
// ✅ 属性包装器:封装重复逻辑
@propertyWrapper
struct Clamped<T: Comparable> {
private var value: T
let range: ClosedRange<T>
init(wrappedValue: T, _ range: ClosedRange<T>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: T {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
// 使用
@Clamped(0...100) var score = 50
score = 150 // 自动裁切为 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 10.4 lazy 与属性观察器 【推荐】
// ✅ lazy:首次访问时延迟初始化
private lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}()
// ✅ didSet:值变化后回调(常用于 UI 更新)
var isLoading: Bool = false {
didSet {
loadingIndicator.isHidden = !isLoading
}
}
// ✅ willSet:值变化前回调(需新旧值对比时)
var currentPage: Int = 0 {
willSet {
print("即将从 \(currentPage) 切换到 \(newValue)")
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 11.并发规范
# 11.1 async/await 结构化并发 【必须】
// ✅ 结构化并发:所有子任务都在作用域内
func loadAll() async throws -> (User, [Post], [Friend]) {
async let user = fetchUser()
async let posts = fetchPosts()
async let friends = fetchFriends()
return try await (user, posts, friends) // 三个请求并发执行
}
// ✅ Task:手动启动顶层任务(通常只在 SwiftUI / UIKit 入口)
struct ContentView: View {
@State private var user: User?
var body: some View {
List { ... }
.task { // SwiftUI 的 task modifier
user = try? await fetchUser() // View 消失时自动取消
}
}
}
// ❌ 不要在 async 上下文中用 Task.detached ——破坏结构化并发
// ❌ 不要用 Task { } 在 ViewModel 中替代 .task()(需手动管理取消)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 11.2 Task 与 TaskGroup 【推荐】
// ✅ TaskGroup:动态数量的并发任务
func fetchAllUsers(ids: [String]) async throws -> [User] {
try await withThrowingTaskGroup(of: User.self) { group in
for id in ids {
group.addTask { try await fetchUser(id: id) }
}
return try await group.reduce(into: []) { $0.append($1) }
}
}
// ✅ 限制并发数(用 TaskGroup + 信号量 / AsyncChannel)
// ✅ Task 取消检查
func longRunningWork() async throws {
for i in 0..<1000 {
try Task.checkCancellation() // 协作取消检查点
await doStep(i)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 11.3 Actor 与数据隔离 【推荐】
// ✅ actor:保护可变状态,防止数据竞争
actor UserCache {
private var storage: [String: User] = [:]
func get(_ key: String) -> User? {
storage[key] // actor 内部同步访问
}
func set(_ key: String, user: User) {
storage[key] = user
}
}
// 使用:所有访问都需要 await(编译器强制)
let cache = UserCache()
await cache.set("u1", user: User(...))
let cached = await cache.get("u1")
// ❌ 不要在 actor 内部长时间阻塞(actor 是可重入的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 11.4 MainActor 标注 【必须】
// ✅ 整个类标注 @MainActor(UI 相关代码)
@MainActor
final class UserViewModel: ObservableObject {
@Published var users: [User] = []
@Published var isLoading = false
func load() async {
isLoading = true
defer { isLoading = false }
// MainActor 保证了 @Published 属性的更新在主线程
users = (try? await api.fetchUsers()) ?? []
}
}
// ✅ 特定方法标注 @MainActor
func updateCache(_ user: User) {
cachedUser = user // 后台线程可调
}
@MainActor
func updateUI(_ user: User) {
nameLabel.text = user.name // 必须在主线程
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 11.5 Sendable 与线程安全 【推荐】
// ✅ 值类型自动满足 Sendable(线程安全传递)
struct UserProfile: Sendable { // Sendable 可省略,编译器自动推导
let id: String
var name: String
}
// ✅ 类手动声明 Sendable(必须满足条件)
final class ThreadSafeCounter: @unchecked Sendable {
private let lock = NSLock()
private var _count = 0
var count: Int {
lock.lock(); defer { lock.unlock() }
return _count
}
}
// ❌ 可变引用类型默认不满足 Sendable → 编译器警告
// 解决:改用 actor、final class + 锁、或 struct
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 12.内存管理
# 12.1 循环引用检测 【必须】
// ❌ 经典循环引用
class Parent {
var child: Child?
}
class Child {
var parent: Parent? // ← 强引用 Parent → 循环引用 → 泄漏
}
// ✅ 打破循环:一方用 weak
class Parent {
var child: Child?
}
class Child {
weak var parent: Parent? // ✅ 弱引用
}
// ✅ 闭包中的 [weak self]
class ViewController {
var api: API!
func load() {
api.fetch { [weak self] result in // self → api → closure → self
guard let self else { return }
self.updateUI(result)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 12.2 weak 与 unowned 选择 【必须】
// ✅ weak:被引用对象可能为 nil(最常见)
class DetailView {
weak var delegate: DetailViewDelegate? // delegate 通常用 weak
}
// ✅ unowned:被引用对象生命周期 ≥ 当前对象
class Customer {
let creditCard: CreditCard
}
class CreditCard {
unowned let customer: Customer // CreditCard 不会比 Customer 活得久
}
// ✅ 决策表
// weak → 对方可能先释放(delegate、observers)
// unowned → 对方生命周期一定更长或相同(父子关系、同步闭包)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 12.3 闭包捕获列表 【必须】
// ✅ 多个捕获:混合 weak 和 unowned
api.fetch { [weak self, unowned cache] result in
guard let self else { return }
cache.store(result)
self.updateUI(result)
}
// ✅ Swift 5.3+ 闭包中 self 可省略(当捕获 self 明确时)
class MyClass {
var name: String
func configure() {
button.setAction { [weak self] in
self?.name = "tapped" // 显式 self,提醒可能已释放
}
}
}
// ❌ 不要忘了在长生命周期闭包(Timer、NotificationCenter)中加捕获列表
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
self?.tick() // ✅ 加 [weak self]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 13.现代Swift特性
# 13.1 不透明类型(some)【推荐】
// ✅ some View:隐藏具体类型,编译器保证类型一致
func makeLabel() -> some View {
Text("Hello")
.font(.title)
.foregroundColor(.blue)
}
// ✅ some Protocol:返回实现了该协议的具体类型
func makeCollection() -> some Collection {
[1, 2, 3] // 返回 [Int],但对外隐藏
}
// ❌ 不要用 any View 替代 some View(any 有性能开销)
// any View → 存在类型(existential),有运行时开销
// some View → 不透明类型(opaque),编译期确定,零开销
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 13.2 结果构建器(@resultBuilder)【可选】
// ✅ SwiftUI 中的 DSL(声明式 UI 语法)
VStack {
Text("Title")
Text("Subtitle")
}
// ✅ 自定义 DSL(按需)
@resultBuilder
struct ArrayBuilder<T> {
static func buildBlock(_ components: T...) -> [T] {
components
}
}
func build(@ArrayBuilder<String> _ builder: () -> [String]) -> [String] {
builder()
}
let items = build {
"A"
"B"
"C"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 13.3 泛型与 where 子句 【推荐】
// ✅ where 子句:为特定类型提供特殊实现
extension Array where Element == String {
func joinedWithComma() -> String {
joined(separator: ", ")
}
}
// ✅ where 约束协议关联类型
func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T where T: Hashable {
let decoded = try JSONDecoder().decode(T.self, from: data)
return decoded
}
// ✅ 泛型 + where 组合约束
extension Collection where Element: Numeric {
func sum() -> Element {
reduce(0, +)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 14.工具链与自动化
# 14.1 SwiftLint
# .swiftlint.yml
opt_in_rules:
- force_unwrapping # 检测 ! 强制解包
- closure_spacing
- empty_count
- explicit_self
- operator_usage_whitespace
- prohibited_super_call
- redundant_nil_coalescing
- unowned_in_closure
disabled_rules:
- trailing_whitespace
- line_length
force_unwrapping:
severity: error # ! 强制解包直接报错
line_length: 120
type_body_length: 300
file_length: 500
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 14.2 SwiftFormat
// SwiftFormat:专注格式化(比 SwiftLint 更强大的格式化能力)
// 配置文件 .swiftformat
--indent 4
--maxwidth 120
--wraparguments before-first
--self insert // 强制插入 self
--importgrouping alphabetized
--stripunusedargs unnamed-only
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 14.3 CI 集成
# GitHub Actions 示例
- name: Swift Code Quality
run: |
swiftlint lint --strict # 格式 & 风格检查(警告也视为错误)
swiftformat --lint . # 格式检查
xcodebuild test \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 15'
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 14.4 pre-commit 钩子 【推荐】
# .pre-commit-config.yaml
repos:
- repo: https://github.com/nicklockwood/SwiftFormat
rev: 0.53.0
hooks:
- id: swiftformat
1
2
3
4
5
6
2
3
4
5
6
# 安装
brew install pre-commit
pre-commit install # 每次 commit 前自动运行
pre-commit run --all-files # 手动全量检查
1
2
3
4
2
3
4
# 15.常见反模式
| 反模式 | 问题 | 改进 |
|---|---|---|
强制解包 ! | 运行时崩溃 | guard let / ?? / try? |
try! 忽略错误 | 错误被掩盖,崩溃无上下文 | try? 或 do-catch |
| 闭包循环引用 | 内存泄漏 | [weak self] + guard let self |
| 巨型 ViewController | 难以维护、无法测试 | MVVM / MV / VIPER 拆分 |
var 过多 | 可变状态难跟踪 | 优先 let,确需可变用 @Published |
| Protocol 过于庞大 | 实现方负担重 | 拆分为多个小协议 |
NotificationCenter 未移除 observer | 野指针崩溃 | addObserver 的地方确保有对应 removeObserver |
在 viewDidLoad 中做网络请求 | 阻塞 UI | 用 async/await 或移到 viewWillAppear / .task |
# 16.代码审查清单
每次 Code Review 时,按以下清单逐项检查:
## 命名
- [ ] 类型大驼峰,方法/属性小驼峰
- [ ] 布尔属性 is/has/should 前缀
- [ ] 方法读起来像英文句子(调用方视角清晰)
- [ ] 无拼音、单字母、含义模糊的命名
## 格式
- [ ] 缩进统一(4 空格)
- [ ] import 按字母序
- [ ] extension 合理分组(MARK 注释)
- [ ] 单行 ≤ 120 字符
## 安全
- [ ] 无强制解包(!),无 try!
- [ ] 可选值用 guard let / if let / ?? 处理
- [ ] 闭包中 [weak self] 防止循环引用
- [ ] NotificationCenter / Timer 确保移除 observer
## 值类型与引用
- [ ] 优先 struct(值语义)而非 class
- [ ] 枚举替代魔法数字/字符串
- [ ] 访问控制合理(不暴露内部实现)
## 并发
- [ ] UI 操作标记 @MainActor
- [ ] async/await 结构化并发,无 Task.detached 滥用
- [ ] 可变共享状态用 actor 保护
- [ ] Sendable 类型正确标注
## 内存
- [ ] 无循环引用(parent-child、闭包、delegate)
- [ ] weak 与 unowned 选择正确
- [ ] 闭包捕获列表完整
## 测试
- [ ] 核心逻辑有单元测试
- [ ] 正常路径和异常路径都覆盖
- [ ] async 代码用 XCTestExpectation / async test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 17.常见陷阱速查
以下陷阱即使在有经验的开发者中也常见,值得定期回顾。
# 17.1 可选值与强制解包陷阱
| # | 陷阱 | 描述 | 正解 |
|---|---|---|---|
| 1 | ! 在 production 中 | let name = user!.name 崩溃 | guard let / ?? |
| 2 | IBOutlet 用弱引用 | @IBOutlet weak var label: UILabel? 每次访问解包 | @IBOutlet private var label: UILabel!(强引用) |
| 3 | 可选链 = nil 不报错 | user?.address?.city 返回 nil 静默,丢失上下文 | 关键路径加 guard 提前失败 |
| 4 | 可选值比较 | if optionalBool 在 Swift 中不允许 | if optionalBool == true |
# 17.2 值类型陷阱
| # | 陷阱 | 描述 | 正解 |
|---|---|---|---|
| 1 | struct 中的 class 属性 | struct 包含 class 属性 → 值语义被破坏 | 全用 struct 属性或明确文档 |
| 2 | 修改 struct 忘记 mutating | 编译错误 | 加 mutating 关键字 |
| 3 | Array 是值类型 | var arr = [1,2]; let copy = arr; arr.append(3) — copy 仍是 [1,2] | 理解 COW(写时复制) |
| 4 | enum 关联值不能直接比较 | case loaded(User) 无 Equatable 时报错 | 让 User 实现 Equatable |
# 17.3 并发陷阱
| # | 陷阱 | 描述 | 正解 |
|---|---|---|---|
| 1 | Task {} 忘记取消 | View 消失后 Task 仍在执行 | 用 .task { } modifier |
| 2 | 在 MainActor 中做 IO | 阻塞主线程 | await withTaskGroup 在后台执行 |
| 3 | actor 重入 | await 后 actor 状态可能已变化 | 检查前置条件是否仍然成立 |
| 4 | @Published 非主线程 | 后台线程修改 @Published 触发 UI 更新 → 紫色警告 | 标注 @MainActor / 用 await MainActor.run |
# 17.4 内存陷阱
| # | 陷阱 | 描述 | 正解 |
|---|---|---|---|
| 1 | delegate 用强引用 | 经典的循环引用场景 | weak var delegate |
| 2 | Timer 强引用 target | Timer.scheduledTimer 的 target 被持有 | 用 block-based API:scheduledTimer(withTimeInterval:repeats:block:) |
| 3 | DispatchQueue.main.asyncAfter 引用 self | self 被 block 持有直到执行 | [weak self] |
| 4 | lazy 闭包中的 self | 编译器可能不警告但形成强引用 | 显式 [unowned self](单次执行的 lazy) |
# 17.5 协议与泛型陷阱
| # | 陷阱 | 描述 | 正解 |
|---|---|---|---|
| 1 | 协议不能当类型用(有 Self/associatedtype) | var delegate: any Equatable 不允许 | 用 any Equatable(Swift 5.7+)或类型擦除 |
| 2 | 扩展中的 @objc | extension 中的方法加 @objc 不会生成 Objective-C 入口 | 在类声明中加,或用 @objc extension |
| 3 | where 子句顺序 | extension Array where Element == Int 顺序重要 | Element 在前,约束在后 |
| 4 | 关联类型歧义 | 编译器无法推断 associatedtype | 显式 typealias |
本文档将随 Swift 语言演进持续更新,欢迎通过 GitHub issues (opens new window) 反馈问题和建议。
上次更新: 2026/06/17, 11:39:29