一键导入
xxf-aaa-coding-style
xxf_ios 项目的 iOS/Swift 编码规范(强制约束)——文件组织与拆分、命名、注释、访问控制。写 Swift 代码时必须遵守。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
xxf_ios 项目的 iOS/Swift 编码规范(强制约束)——文件组织与拆分、命名、注释、访问控制。写 Swift 代码时必须遵守。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
创建 XXF iOS 列表页模板(DiffableDataSource + BaseCollectionViewCell + StatefulView + 分页 ViewModel)
规范 ViewController 与 ViewModel 的分区组织方式。用于治理成员变量和方法过多、顺序混乱、阅读成本高的问题;通过 MARK 分区和职责分层保持代码导航清晰。
处理 XXF iOS 项目中的通用编码任务交付流程。用于 bugfix、功能开发、重构、回归修复等未显式指明测试或 review 的日常 coding 请求;负责自动串起模块 skill、补测、验证、代码审查与风险门禁。
iOS 性能门禁与主动鉴别。针对常规 coding 改动自动识别性能风险(主线程阻塞、列表卡顿、内存抖动、启动耗时、无效并发、过度渲染),并执行最小可行验证与门禁结论,不依赖用户额外提示。
Vibe Coding 通用治理闭环。用于把模糊需求转成可验证交付:先定义问题与边界,再走拼好码优先、最小改动、硬门禁验证、证据化交付和风险结论。适用于需求澄清、实现前规划、AI 协作治理、质量门禁落地。
XXFViewModel MVVM 的 VM 基类与生命周期。当用户要写 ViewModel、绑定 View、处理输入输出流,或询问"XXF 的 MVVM 怎么用"时使用。若出现 ViewModel 成员或方法膨胀、顺序混乱,应联动 `xxf-aaa-class-declaration-guidelines` 做分区治理。
| name | xxf-aaa-coding-style |
| description | xxf_ios 项目的 iOS/Swift 编码规范(强制约束)——文件组织与拆分、命名、注释、访问控制。写 Swift 代码时必须遵守。 |
适用范围:本规范为强制约束。在本项目编写、修改任何 Swift 代码时,必须遵守以下规则。违反时应修正,不要沉默通过。
所有内容都尽可能按职责拆分到独立文件,不要揉到一个 Swift 文件里。 每个文件职责单一、边界清晰,是本项目文件组织的第一原则。
// ✅ UserProfileViewController.swift —— 只承载 UserProfileViewController
// ✅ OrderModel.swift —— 只承载 OrderModel(及其私有嵌套类型)
// ✅ OrderListSection.swift —— 单独一个枚举也要独立成文件
// ❌ Helpers.swift —— 堆放各种无关工具(禁止)
// ❌ OrderModels.swift —— 一个文件塞 OrderModel + CartModel + PaymentModel(禁止)
强制要求:主类的扩展(extension)必须按功能 / 协议边界抽离为独立文件。 禁止把多个 extension 堆在主类文件内 —— 主文件快速膨胀、阅读与导航成本剧增,review diff 也会失焦。
理由:
命名约定:主类型名+职责.swift,职责名用 UpperCamelCase:
UserProfileViewController.swift // 属性、init、生命周期
UserProfileViewController+UI.swift // UI 搭建与布局
UserProfileViewController+TableView.swift // UITableView DataSource / Delegate
UserProfileViewController+Network.swift // 数据请求
UserProfileViewController+Actions.swift // @objc / IBAction 事件响应
UserProfileViewController+Event.swift // 通知 / 业务事件处理
UserProfileViewController+Prefetch.swift // 列表预取 / 预加载
// ✅ UserProfileViewController.swift —— 只保留核心
final class UserProfileViewController: UIViewController {
// MARK: - Properties
private let viewModel = UserProfileViewModel()
private lazy var tableView = UITableView()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
fetchData()
}
}
// ✅ UserProfileViewController+TableView.swift —— 协议实现独立
extension UserProfileViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { ... }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { ... }
}
// ❌ 禁止:所有 extension 堆在主文件里
// UserProfileViewController.swift(1200+ 行)
class UserProfileViewController: UIViewController { ... }
extension UserProfileViewController { /* UI */ }
extension UserProfileViewController: UITableViewDataSource { ... }
extension UserProfileViewController: UITableViewDelegate { ... }
extension UserProfileViewController { /* Network */ }
extension UserProfileViewController { /* Actions */ }
唯一豁免情形(需同时满足):
private 成员,拆出会被迫放宽为 fileprivate(此时优先保持封装性可留本文件,但需 MARK 分区)按以下维度拆分,避免过细(每个文件只装一两个方法)也避免过粗(一个文件包含多种无关职责):
+TableView.swift、+CollectionView.swift、+ScrollViewDelegate.swift+UI.swift(布局)、+Network.swift(数据请求)、+Actions.swift(交互响应)UIView+Snap.swift、String+Validation.swiftOrderModel+Codable.swift、OrderModel+Hashable.swift访问权限注意:private 成员在 Swift 中跨文件 extension 不可见。如果 extension 需要访问主类的私有属性,要么将该属性改为 fileprivate(仅限本类型真正需要的内部共享),要么在 extension 中定义独立的辅助方法。优先保持 private,只有在拆分后的 extension 确实需要访问时才放宽。
长文件必须用 // MARK: - 分区,让大纲清晰:
class OrderListViewController: UIViewController {
// MARK: - Properties
private let viewModel = OrderListViewModel()
private var dataSource: UITableViewDiffableDataSource<Section, Item>!
// MARK: - Lifecycle
override func viewDidLoad() { ... }
override func viewWillAppear(_ animated: Bool) { ... }
// MARK: - UI Setup
private func setupUI() { ... }
private func setupNavigationBar() { ... }
// MARK: - Data
private func fetchOrders() { ... }
// MARK: - Actions
@objc private func refreshTapped() { ... }
}
// MARK: - UITableViewDelegate
extension OrderListViewController: UITableViewDelegate { ... }
MARK 分区推荐顺序:Properties → Lifecycle → UI Setup → Data → Actions → Helpers → 协议实现 extension。
得益于"分而治之"原则,单文件通常应保持较小:
| 类别 | 规则 | 示例 |
|---|---|---|
| 类 / 结构体 / 枚举 / 协议 | UpperCamelCase | OrderListViewController、UserProfile、PaymentType |
| 方法 / 属性 / 变量 / 枚举 case | lowerCamelCase | fetchOrders()、isLoading、.success |
| 常量(全局/静态) | lowerCamelCase(不要用 k 前缀、不要全大写) | static let defaultTimeout = 15.0 |
| 泛型参数 | 单字母或 UpperCamelCase | T、Element、Response |
| 布尔变量 / 方法 | is / has / should / can 开头 | isHidden、hasLoaded、shouldRefresh |
文件名必须与其承载的主类型保持一致(区分大小写):
✅ OrderListViewController.swift → class OrderListViewController
✅ UIView+Layout.swift → extension UIView
❌ orderList.swift / OrderVC.swift(禁止缩写、禁止小写开头)
// ❌ 禁止
let usrInfo: UsrInfo
func calcAmt() -> Double
var vc: UIViewController
// ✅ 推荐
let userInfo: UserInfo
func calculateAmount() -> Double
var presentedViewController: UIViewController
常见可接受缩写(行业通用):URL、ID、JSON、HTTP、API、UI。作为标识符出现时整体大小写一致:
var userID: String // ✅
var userId: String // ❌
let apiURL: URL // ✅
let apiUrl: URL // ❌
// ✅
func insert(_ element: Element, at index: Int)
func remove(at index: Int) -> Element
array.insert(newItem, at: 0)
array.remove(at: 3)
// ❌
func insertElementAtIndex(element: Element, index: Int)
// ❌ OC 风格遗留,禁止
private var _viewModel: ViewModel
private var m_userName: String
// ✅ 直接命名,靠 private 修饰符表达可见性
private var viewModel: ViewModel
private var userName: String
Delegate 方法第一个参数必须是发送者本身:
// ✅
protocol OrderListViewDelegate: AnyObject {
func orderListView(_ view: OrderListView, didSelectOrder order: Order)
func orderListViewDidRefresh(_ view: OrderListView)
}
强制要求:所有类型、方法、方法参数、成员字段都必须用 /// 加文档注释。 本项目内部类型也不例外。
/// 描述大致意图,让读者一眼看出"这个类存在是为了做什么 / 解决什么问题"/// 覆盖以下维度(按需,不相关的省略,相关的一条不能少):
start()"、"仅在登录态可用"、"单次实例化后不可重入")@MainActor / 特定 queue / "任意线程"需显式注明;涉及 async 的要说明 suspension point 与 actor hop- Parameter xxx: 或 - Parameters: 列出每个参数:含义、单位、取值范围 / 允许 nil / 边界、是否被闭包捕获(@escaping)、回调所在线程- Returns: 说明含义与特殊情况(nil / 空数组 / 负数 的语义差别,成功与失败的返回形态)- Throws: 列出可能抛出的错误类型,以及什么条件下会抛出(不只是类型名)/// 说明字段的作用;对外可见字段要明确语义边界(如"未加载前为 nil"、"修改需在主线程")理由:
// ✅ 类型:描述意图 + 使用场景
/// 订单列表分页加载器。
///
/// 内部维护 `pageIndex` 与 `hasMore`,对外只暴露「下一页」语义,避免调用方感知分页细节。
/// **线程限制**:所有 public 方法仅允许在主线程调用;内部会切换到后台执行网络请求,
/// completion 固定回到主线程。
final class OrderListPager {
// MARK: - Properties
/// 当前已加载的订单,按业务字段排序后的结果。
///
/// 下拉刷新(`loadNextPage(forceRefresh: true)`)会整体替换;
/// 追加加载只会 append 尾部。只允许在主线程读写。
private(set) var orders: [Order] = []
/// 是否还有下一页。为 false 时上拉不再触发请求。
///
/// 由最近一次服务端返回驱动;下拉刷新会被重置为 true。
private(set) var hasMore = true
/// 单页条数。与后端约定为 20,超过可能触发限流。
private let pageSize: Int = 20
// MARK: - Public
/// 加载下一页订单。
///
/// **功能**:按当前 `pageIndex` 向后端拉取一页数据,追加到 `orders` 尾部。
///
/// **使用限制**:
/// - 必须先调用 `start(userID:)` 建立上下文,否则直接回调 `.failure(.notStarted)`
/// - 调用方应处于登录态,未登录时直接 `.failure(.notLoggedIn)`
///
/// **线程**:必须在主线程调用;`completion` 也在主线程回调。
///
/// **边界**:
/// - 幂等:正在加载时重复调用会被忽略,`completion` 不会被多次触发
/// - `hasMore == false` 时直接返回 `.success([])`,不发请求
/// - `forceRefresh == true` 会取消在途请求,重置 `pageIndex = 0`,`orders` 会整体替换
///
/// **副作用**:成功时 `orders` / `hasMore` / `pageIndex` 会被更新;
/// 失败不修改状态。不发送任何通知。
///
/// **性能**:网络耗时主导;无本地耗时操作。
///
/// - Parameters:
/// - forceRefresh: true 时忽略本地缓存、重置 `pageIndex` 到 0;默认 false
/// - completion: 主线程回调;`@escaping`,成功返回本次新增的订单数组(下拉刷新场景返回首页全部)
/// - Returns: 正在进行的请求 task,调用方可持有用于外部取消;重复调用被忽略时返回 nil
@discardableResult
func loadNextPage(
forceRefresh: Bool = false,
completion: @escaping (Result<[Order], Error>) -> Void
) -> Task<Void, Never>? { ... }
}
// ❌ 禁止:类型/方法/字段无注释
final class OrderListPager {
private(set) var orders: [Order] = []
private(set) var hasMore = true
private let pageSize: Int = 20
func loadNextPage(
forceRefresh: Bool = false,
completion: @escaping (Result<[Order], Error>) -> Void
) -> Task<Void, Never> { ... }
}
豁免情形(仍需保持克制,不要滥用):
override 方法重写父类语义、父类已有文档 —— 可省略,但若行为改变必须补说明override var description: String)且语义与父类一致文档注释(///)以外的行内注释(//)默认不写。只在以下情况添加:
// ❌ 禁止:行内注释解释 WHAT(代码自己已经说了)
// 设置名字为 name
self.name = name
// 循环数组
for item in items { ... }
// ✅ 推荐:解释 WHY(非显然原因)
// 服务端返回 amount 单位是分,这里除以 100 转为元展示
displayAmount = amount / 100.0
// 延迟一帧是为了规避 iOS 16 上 UICollectionView 初次 layout 的布局抖动
DispatchQueue.main.async { [weak self] in ... }
// MARK: - Section Name // 分区(必须带 -)
// MARK: Subsection // 子分区(不带 -)
// TODO: 分页加载待后端接口就绪后实现(@xxf 2026-05)
// FIXME: iOS 17 beta 上 scrollToItem 偶发崩溃,待验证修复方案
// HACK: 临时规避 xxx 库 v1.2 的 bug,升级后移除
TODO / FIXME 必须写清楚:要做什么 + 负责人或日期,否则会变成永久死债。
提交前必须删除注释掉的废代码。需要找回历史代码用 git,不要让仓库变垃圾场。
// ❌ 禁止
func doSomething() {
newLogic()
// oldLogic()
// if legacy { ... }
}
默认使用最严的访问级别,逐级放开:
优先级:private > fileprivate > internal(默认)> public > open
// ✅ 内部状态一律 private
class OrderListViewController: UIViewController {
private let viewModel = OrderListViewModel()
private var isLoading = false
private func setupUI() { ... }
private func handleRefresh() { ... }
}
// ❌ 禁止:无脑默认 internal 暴露内部细节
class OrderListViewController: UIViewController {
let viewModel = OrderListViewModel() // 应该 private
var isLoading = false // 应该 private
func setupUI() { ... } // 应该 private
}
private vs fileprivateprivate:只在当前声明作用域内可见(Swift 4+ 同文件 extension 也能访问同一类型的 private 成员)fileprivate:同文件其他类型也可见默认用 private,只有需要让同文件的其他类型访问时才用 fileprivate。
final不打算被继承的类,必须标记 final:
// ✅ 推荐:业务层的 VC/View/Service 默认 final
final class OrderListViewController: UIViewController { ... }
final class PaymentService { ... }
// ✅ 明确设计为可继承的基类,才不加 final
class BaseViewController: UIViewController { ... }
实例持有的闭包、Combine 订阅、异步回调中,引用 self 必须用 [weak self] 或 [unowned self]:
// ❌ 强引用 self 导致循环引用
viewModel.onDataUpdate = {
self.tableView.reloadData()
}
// ✅ 默认 weak
viewModel.onDataUpdate = { [weak self] in
guard let self else { return }
self.tableView.reloadData()
}
// ✅ 明确生命周期不短于闭包时,可用 unowned(谨慎)
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [unowned self] _ in
self.tick()
}
weak vs unowned 选择:
weakunownedweak,unowned 访问已释放对象会直接崩溃Delegate 属性必须 weak:
weak var delegate: OrderListViewDelegate?
| 修饰符 | 使用场景 |
|---|---|
let | 默认首选,只在明确需要可变时用 var |
lazy | 初始化开销大且未必使用;不要用于引用 self 方法的复杂初始化(隐式循环引用风险) |
@Published / Combine | ViewModel 对外发布状态,配合 private(set) 限制外部写入 |
private(set) var | 外部可读不可写 |
@MainActor | UI 相关类型 / 方法,确保主线程调用 |
// ✅ 外部只读,内部可写
final class OrderListViewModel {
@Published private(set) var orders: [Order] = []
@Published private(set) var isLoading = false
}
!除 @IBOutlet 和极少数初始化后才可用的场景,禁止使用隐式解包 Type!:
// ❌ 禁止
var viewModel: ViewModel!
let user = userInfo!
// ✅ 用普通可选 + guard / if let
var viewModel: ViewModel?
guard let user = userInfo else { return }
仅以下场景允许隐式解包:
@IBOutlet weak var tableView: UITableView!try! 的使用// ❌ 禁止:任意强制解包
let url = URL(string: userInput)!
let data = try! JSONEncoder().encode(obj)
// ✅ 只在编译期/启动期常量可保证绝不失败时接受
let localURL = URL(string: "https://api.example.com")! // 字面量 URL,启动期失败会立即暴露
let data = try! JSONEncoder().encode(staticConfig) // 静态配置,失败即编程错误
原则:运行时数据一律用 guard let / try? / do-catch;只有"失败即程序 bug"的场景才用 ! / try!。
本项目代码必须尽量满足以下六大设计原则,配合"分而治之"的文件组织,形成可维护、可扩展的代码结构。
一个类型只应有一个变化的理由。 如果一个类同时承担多个职责,其中任何一个职责的变化都会迫使这个类修改。
// ❌ 一个 Manager 同时做网络、缓存、UI 通知
final class OrderManager {
func fetchFromServer() { ... }
func saveToDisk() { ... }
func showToast() { ... }
}
// ✅ 拆分为三个各司其职的类型
final class OrderAPIClient { func fetch() async throws -> [Order] { ... } }
final class OrderCache { func save(_ orders: [Order]) { ... } }
final class OrderToastPresenter { func showSuccess() { ... } }
对扩展开放,对修改关闭。 新增能力时,通过扩展(新增类型 / 实现协议)而不是改动已有稳定代码。
// ❌ 新增一种支付方式就要改 switch
func pay(type: String) {
switch type {
case "wechat": ...
case "alipay": ...
// 新增 applepay 就得改这里
}
}
// ✅ 抽象出协议,新支付方式只新增实现,不改调用方
protocol PaymentMethod { func pay(amount: Decimal) async throws }
final class WeChatPayment: PaymentMethod { ... }
final class AliPayPayment: PaymentMethod { ... }
final class ApplePayPayment: PaymentMethod { ... } // 新增时无需改调用方
子类型必须可以替换其父类型而不破坏程序正确性。 继承关系必须是真正的"is-a",不要用继承实现"共享代码"。
// ❌ 鸟类都会飞 —— 但企鹅不会,让它继承 Bird 并 override fly() 会破坏语义
class Bird { func fly() { ... } }
class Penguin: Bird { override func fly() { fatalError("penguins can't fly") } }
// ✅ 抽象出真正共享的契约
protocol Bird { var name: String { get } }
protocol Flyable { func fly() }
final class Sparrow: Bird, Flyable { ... }
final class Penguin: Bird { ... } // 不实现 Flyable
不应强迫客户依赖它用不到的方法。 多个小而专一的协议,优于一个臃肿的大协议。
// ❌ 一个协议塞所有能力
protocol DataSource {
func fetchList() async throws -> [Item]
func fetchDetail(id: String) async throws -> Item
func upload(_ file: Data) async throws
func downloadReport() async throws -> URL
}
// ✅ 按职责拆小协议,使用方只依赖自己需要的
protocol ItemListProviding { func fetchList() async throws -> [Item] }
protocol ItemDetailProviding { func fetchDetail(id: String) async throws -> Item }
protocol FileUploading { func upload(_ file: Data) async throws }
protocol ReportDownloading { func downloadReport() async throws -> URL }
高层模块不应依赖低层模块,二者都应依赖抽象。 ViewController 应该依赖 protocol,而不是直接 new 一个具体的网络类或单例。这也让单元测试可以注入 mock。
// ❌ VC 直接依赖具体实现,无法替换 / mock
final class OrderListViewController: UIViewController {
private let api = OrderAPIClient() // 硬编码具体类
}
// ✅ VC 依赖协议,具体实现通过 init 注入
protocol OrderFetching { func fetch() async throws -> [Order] }
final class OrderListViewController: UIViewController {
private let fetcher: OrderFetching
init(fetcher: OrderFetching) {
self.fetcher = fetcher
super.init(nibName: nil, bundle: nil)
}
}
一个对象应对其他对象保持最少了解。 只和"直接朋友"通信,避免 a.b.c.d.doSomething() 这样的链式调用穿透多层内部结构。
// ❌ VC 穿透多层去访问 user 的 address 的 city
func updateCityLabel() {
cityLabel.text = viewModel.user.profile.address.city.name
}
// ✅ 让中间层提供聚合好的值,调用方只与 viewModel 一个"朋友"打交道
extension OrderListViewModel {
var displayCity: String { user.profile.address.city.name }
}
func updateCityLabel() {
cityLabel.text = viewModel.displayCity
}
final,继承很少用;使用继承时必须通过 LSP 检验这六条原则与第 1 节的"分而治之"互为表里:职责拆分得当,文件组织自然清晰;文件职责单一,设计原则自然成立。
新增的代码不得引入任何编译器 / 静态分析 / SwiftLint 警告,已有警告在改动到相关代码时就地解决。 警告一旦积累,会迅速失去信号价值——真正要命的问题会淹没在几百条"历史遗留"里,没人再会去看。
// swiftlint:disable 和 @available 绕过:除非有非常明确的业务理由并写明注释。屏蔽不是解决。-warnings-as-errors(逐步推进):对新增模块开启 Xcode 的 "Treat Warnings as Errors",把红线向前推。| 警告类别 | 禁止的"糊弄"做法 | 正确做法 |
|---|---|---|
| 未使用变量 / 参数 | 用 _ = variable 强行消 warning | 真不用就删掉;需要保留参数时用 _ 作为参数名 |
| 废弃 API (deprecated) | @available 屏蔽 | 升级到新 API;必须用旧 API 时加 @available(*, deprecated, message:) 说明迁移计划 |
| 可选值强制解包警告 | 改成 ! 跳过检查 | 用 guard let / if let 真正处理 nil |
| 字面量类型推断警告 | 强转绕过 | 显式声明类型 |
| 协议方法未实现 | 空实现 + 注释"先这样" | 要么实现,要么不声明符合该协议 |
| SwiftLint 行超长 / 复杂度 | // swiftlint:disable:next | 拆方法、拆变量、拆文件 |
// ❌ 用 _ = 糊弄未使用变量警告
let response = try await api.fetch()
_ = response // 禁止:要么用,要么删
// ❌ 用 ! 消除"表达式总是非 nil"类型警告
let urlString = config.endpoint
let url = URL(string: urlString)! // 禁止
// ✅ 要么正确处理,要么明确为什么安全
guard let url = URL(string: config.endpoint) else {
assertionFailure("config.endpoint 必须是合法 URL")
return
}
// ❌ 忽略 deprecated 警告
@available(iOS, deprecated: 13.0) // 没有迁移计划就是拖延
func legacyMethod() { ... }
// ✅ 升级到新 API
// 用 UIScene 相关 API 替换旧的 UIApplication 生命周期回调
对于接手时已存在的历史警告:
// swiftlint:disable 批量屏蔽已有警告——那只是把地毯掀起来把灰藏到下面散落在代码里的数字和字符串是 bug 的温床——改一处忘一处,或者下一个人根本不知道它含义。
// ❌ 魔法数字 / 字符串
if user.age >= 18 { ... }
headerView.frame.size.height = 44
NotificationCenter.default.post(name: Notification.Name("UserLoginSuccess"), object: nil)
// ✅ 提升为有命名的常量 / 枚举 / Notification.Name 扩展
enum Legal {
static let adultAge = 18
}
enum Metrics {
static let navigationBarHeight: CGFloat = 44
}
extension Notification.Name {
static let userLoginSuccess = Notification.Name("UserLoginSuccess")
}
任何会在 UI 上出现的字符串,不要直接硬编码中文,统一走 NSLocalizedString 或项目约定的本地化封装。即便当前只支持中文,也为将来留好扩展口。
// ❌
titleLabel.text = "确认删除这条订单?"
// ✅
titleLabel.text = NSLocalizedString("order.delete.confirm.title", comment: "删除订单确认弹窗标题")
API Key / 路由 / 事件名等容易打错的字符串,用 enum / struct 包装成强类型:
// ❌
analytics.track(event: "order_paid", params: ["amt": 100])
// ✅
enum AnalyticsEvent: String {
case orderPaid = "order_paid"
}
analytics.track(event: .orderPaid, params: ["amount": 100])
所有 UIKit 调用必须在主线程。类型上可用 @MainActor 约束,运行时不确定线程时显式切回主线程。
// ❌ 后台线程直接改 UI
URLSession.shared.dataTask(with: url) { data, _, _ in
self.titleLabel.text = "done"
}.resume()
// ✅ 切回主线程
URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
Task { @MainActor [weak self] in
self?.titleLabel.text = "done"
}
}.resume()
// ✅ 或者类本身声明 @MainActor
@MainActor
final class OrderListViewModel { ... }
DispatchQueue.main.sync主线程 sync 到主线程必定死锁;非主线程 sync 到主线程容易死锁。禁止使用。
// ❌ 禁止
DispatchQueue.main.sync { titleLabel.text = "foo" }
// ✅
DispatchQueue.main.async { self.titleLabel.text = "foo" }
async / await新代码优先 async / await,避免 callback hell 和遗忘调用 completion 导致的内存泄漏。老代码迁移时按影响面评估。
// ✅ 结构清晰
func loadOrders() async throws -> [Order] {
let token = try await auth.currentToken()
return try await api.fetchOrders(token: token)
}
可变共享状态必须用 actor / 锁 / 串行队列保护;只读共享数据可用 let。
printprint 只在本地 debug 时临时用,提交前必须清理。正式日志统一走项目的日志封装(如 Logger、os_log 或项目内统一的日志 facade)。
// ❌ 提交到仓库
print("user=\(user), token=\(token)")
// ✅ 使用统一日志接口 + 分级
Logger.network.info("order fetched", metadata: ["count": orders.count])
token、密码、身份证、手机号、身份认证相关字段一律不入日志。如必须记录,做脱敏处理。
// ❌ 吞错误
do { try riskyCall() } catch { }
// ❌ 打印后当没事发生
do { try riskyCall() } catch { print(error) }
// ✅ 要么向上抛,要么明确处理(UI 提示 / 降级 / 埋点)
do {
try riskyCall()
} catch {
Logger.app.error("riskyCall failed", metadata: ["error": "\(error)"])
analytics.track(.riskyCallFailed, error: error)
showErrorAlert(error)
}
enum,不要抛 NSError自定义错误用 enum: Error,带上足够的上下文让调用方可判别:
enum OrderError: Error {
case notFound(id: String)
case expired(expiredAt: Date)
case network(underlying: Error)
}
try? 只在"不关心失败原因"时使用// ✅ 真的不关心失败原因
let cached = try? cache.read(key) // 缓存失败就当 nil
// ❌ 关键路径用 try? 会静默丢失错误信息
let order = try? api.fetchOrder(id: id) // 禁止:网络失败、解析失败都变成 nil
ViewController 应是粘合层,不应承担业务逻辑、数据转换、网络编排。
ViewModelService / RepositoryUIView 子组件ChildViewController判断标准:一个 VC 文件应在 300 行以内完成职责;超出就该评估能不能拆出 ViewModel / Service / 子 View。
文件组织
Helpers.swift 类的杂糅文件?+UI.swift / +TableView.swift / +Event.swift / +Prefetch.swift ...)?堆在主文件里一律失败// MARK: - 分区?命名
userID 而非 userId)?is/has/should/can 开头?_、m_ 等历史前缀?访问控制与修饰符
private?外部访问用 private(set)?final?self 使用了 [weak self]?delegate 属性 weak?! / try!?设计原则
a.b.c.d.x 式火车调用(LoD)?注释与文档
/// 描述大致意图?/// 涵盖到:具体功能、使用限制/前置条件、线程限制、边界/幂等、副作用(不相关可省,相关的不能漏)?- Parameter 列出含义/单位/取值范围/是否允许 nil/回调线程?返回值 - Returns: 说明特殊情况?抛出 - Throws: 说明什么条件抛什么错?/// 说明作用,对外字段标出语义边界?// 注释只解释 WHY,不出现解释 WHAT 的冗余?警告与质量
print 残留?无注释掉的废代码?常量与本地化
并发与错误
DispatchQueue.main.sync?try? 静默忽略?违反规范时的处理方式:发现违反本规范的代码时,在修改该代码的当前任务中一并修正;不在无关任务里顺手重构全局代码。