بنقرة واحدة
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? 静默忽略?违反规范时的处理方式:发现违反本规范的代码时,在修改该代码的当前任务中一并修正;不在无关任务里顺手重构全局代码。