深入 ECC 的 Swift Patterns 规则:面向协议设计、值类型、Actor 并发与可测试依赖注入

深入 ECC 的 Swift Patterns 规则:面向协议设计、值类型、Actor 并发与可测试依赖注入
深入 ECC 的 Swift Patterns 规则:面向协议设计、值类型、Actor 并发与可测试依赖注入【免费下载链接】ECCThe agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.项目地址: https://gitcode.com/GitHub_Trending/ev/ECC本文以 ECC 仓库中的 Swift Patterns 规则文件 为主体,逐条拆解其四大核心 Swift 设计模式——面向协议的仓储设计、基于值类型的状态建模、Actor 并发隔离与默认参数依赖注入,并结合仓库中该规则直接引用的 swift-actor-persistence 与 swift-protocol-di-testing 两个技能文档,给出从模式定义到落地实现、再到用 Swift Testing 验证的完整链路。读完本文,你可以理解 ECC 如何用一套“规则 技能”的分层体系,把 Swift 现代并发与可测试架构约束沉淀为 AI 编程助手可直接执行的编码规范。规则文件定位:何时生效、如何分层.cursor/rules/swift-patterns.md 是面向 Cursor 的规则文件,其 YAML frontmatter 决定了激活方式:--- description: Swift patterns extending common rules globs: [**/*.swift, **/Package.swift] alwaysApply: false ---三个字段含义明确:globs指定规则只对匹配**/*.swift与**/Package.swift的文件生效;alwaysApply: false表示规则不会全局注入,而是在助手处理 Swift 源码时按文件匹配触发;description则供工具层检索与展示。需要特别说明的是,该文件并非孤立存在。仓库中还存在一个同源镜像 rules/swift/patterns.md,正文与 Cursor 版本完全一致,仅 frontmatter 格式不同(使用paths字段而非globs),且显式声明:This file extends common/patterns.md with Swift specific content.这体现了 ECC 规则体系的分层设计。根据 rules/README.md,规则库由common 通用层 语言特定目录组成,通用层 rules/common/patterns.md 定义了语言无关的原则——其中就包含 Repository Pattern(把数据访问封装在一致接口后,业务逻辑依赖抽象接口而非存储机制),而本文主角swift-patterns.md正是给这个通用模式提供了 Swift 语境的具象写法。README 同时界定了 Rules 与 Skills 的分工:Rules定义广泛适用的标准与检查清单,回答“做什么”;Skills(skills/目录)提供面向具体任务的深度可操作参考,回答“怎么做”。语言规则文件会在合适处引用对应技能——swift-patterns.md文末的 References 一节正是如此,分别指向 actor 持久化与协议依赖注入两个技能,构成“规则定方向、技能给纵深”的引用链。规则的落地方式有两种(见 rules/README.md):# 方式一:安装脚本(推荐),common 语言规则集一起安装 ./install.sh swift # 方式二:手动拷贝,注意必须保留目录结构,不可用 /* 拍平 mkdir -p ~/.claude/rules/ecc cp -r rules/common ~/.claude/rules/ecc/ cp -r rules/swift ~/.claude/rules/ecc/README 特别强调不能把 common 与语言目录拍平到同一目录,因为两者存在同名文件(如patterns.md),拍平会导致语言文件覆盖通用规则,并破坏../common/相对引用。另外按 Rule Priority 一节,当语言特定规则与通用规则冲突时,语言特定规则优先(specific overrides general)。模式一:面向协议的设计(Protocol-Oriented Design)原文档的第一条规则:定义小而聚焦的协议,并用协议扩展提供共享默认实现。给出的核心示例是一个泛型异步仓储协议:protocol Repository: Sendable { associatedtype Item: Identifiable Sendable func find(by id: Item.ID) async throws - Item? func save(_ item: Item) async throws }逐点拆解其设计意图:associatedtype Item: Identifiable Sendable:通过关联类型让同一个Repository协议可以服务于任意实体,同时以约束保证元素可标识、可安全跨并发域传递。这正是 rules/common/patterns.md 中 Repository Pattern 的 Swift 化表达——接口只暴露find/save这类标准操作,具体存储(数据库、API、文件)由实现方决定,业务逻辑与存储机制解耦。协议继承Sendable:强制所有Repository实现具备 Sendable 语义,使其可以安全地作为属性存入 actor、跨任务边界传递。这一点在后文的 actor 持久化实现中会再次出现。async throws方法签名:把并发与错误处理写进协议契约,而不是留给实现者自行选择。调用方天然处于 async 上下文中,无需关心底层是否使用了队列或锁。“用协议扩展做共享默认实现”:原文虽只给出协议本体,但结合仓库中 swift-protocol-di-testing 技能 的实践可以看到典型用法——默认参数注入(见下文模式四)本质上是协议 默认实现的组合,协议扩展与默认实现共同让生产代码保持零配置。模式二:值类型建模(Value Types)原文档第二条给出两条约束:用struct承载数据传输对象(DTO)与模型;用带关联值的enum建模互斥状态:enum LoadStateT: Sendable: Sendable { case idle case loading case loaded(T) case failed(Error) }这个LoadState是一个教科书式的状态机建模:四种加载状态互斥且完备,loaded(T)与failed(Error)通过关联值把“成功的数据”与“失败的错误”绑定进状态本身,使“成功却无数据”或“失败却无原因”成为类型系统不允许的情况。两个值得注意的细节:T: Sendable约束:状态枚举经常需要在 Task 之间传递或在 ViewModel 中异步赋值,要求载荷可 Sendable 保证了它在 Swift 并发下的合法性。整体声明Sendable:与模式一中的Repository: Sendable呼应,整个规则文件的隐含前提是——所有跨边界的数据类型都应显式 Sendable。值类型优先还带来一个直接收益:struct 的复制语义消除了 DTO 上的共享可变状态,配合“测试隔离”原则(rules/swift/testing.md 要求每个测试获得全新实例、测试间无共享可变状态),让测试天然稳定。模式三:Actor 并发模式(从简单缓存到文件级持久化)原文档第三条:对共享可变状态,使用 actor 而非锁或 dispatch queue。给出的基础示例是一个泛型缓存 actor:actor CacheKey: Hashable Sendable, Value: Sendable { private var storage: [Key: Value] [:] func get(_ key: Key) - Value? { storage[key] } func set(_ key: Key, value: Value) { storage[key] value } }其要旨是:可变字典storage被 actor 隔离包裹,所有外部访问都经 actor 队列串行化,数据竞争在编译期即被排除,调用方以await访问,无需引入NSLock、DispatchQueue等手动同步原语。纵深实现:带文件持久化的 actor 仓储文档 References 指向的 swift-actor-persistence 技能 把上述基础缓存推进为生产可用的持久化层:内存缓存 文件落盘,适用于 Swift 5.5 的 iOS/macOS 离线场景。核心实现如下(引自该技能文档):public actor LocalRepositoryT: Codable Identifiable where T.ID String { private var cache: [String: T] [:] private let fileURL: URL public init(directory: URL .documentsDirectory, filename: String data.json) { self.fileURL directory.appendingPathComponent(filename) // Synchronous load during init (actor isolation not yet active) self.cache Self.loadSynchronously(from: fileURL) } // MARK: - Public API public func save(_ item: T) throws { cache[item.id] item try persistToFile() } public func delete(_ id: String) throws { cache[id] nil try persistToFile() } public func find(by id: String) - T? { cache[id] } public func loadAll() - [T] { Array(cache.values) } // MARK: - Private private func persistToFile() throws { let data try JSONEncoder().encode(Array(cache.values)) try data.write(to: fileURL, options: .atomic) } private static func loadSynchronously(from url: URL) - [String: T] { guard let data try? Data(contentsOf: url), let items try? JSONDecoder().decode([T].self, from: data) else { return [:] } return Dictionary(uniqueKeysWithValues: items.map { ($0.id, $0) }) } }由于 actor 隔离,所有对外调用自动变为异步:let repository LocalRepositoryQuestion() // 读 — 内存缓存 O(1) 查找 let question await repository.find(by: q-001) let allQuestions await repository.loadAll() // 写 — 更新缓存并原子落盘 try await repository.save(newQuestion) try await repository.delete(q-001)该技能文档还以一张设计决策表解释了每个选择的原因:决策理由用 actor 而非 class 锁编译器强制线程安全,无需手动同步内存缓存 文件持久化读走缓存快,写落盘保证持久init中同步加载避免异步初始化带来的复杂度以 ID 为键的字典按标识符 O(1) 查找泛型约束Codable Identifiable对任意模型类型可复用原子文件写入(.atomic)崩溃时不会产生部分写入与 UI 层结合时,技能文档给出了ObservableViewModel 的接法:Observable final class QuestionListViewModel { private(set) var questions: [Question] [] private let repository: LocalRepositoryQuestion init(repository: LocalRepositoryQuestion LocalRepository()) { self.repository repository } func load() async { questions await repository.loadAll() } func add(_ question: Question) async throws { try await repository.save(question) questions await repository.loadAll() } }注意 ViewModel 的init再次使用了默认参数注入——与模式四一脉相承。技能文档同时列出了必须规避的反模式,对使用 actor 的开发者极具实战价值:在新 Swift 并发代码中使用DispatchQueue/NSLock替代 actor;把内部cache字典暴露给外部调用方;让文件 URL 可配置却不加校验;忘记 actor 方法调用都必须await;用nonisolated绕过 actor 隔离(直接摧毁并发安全保证)。模式四:依赖注入(协议 默认参数)原文档第四条给出了 ECC 推荐的 Swift 依赖注入范式——注入协议,并为参数提供默认值;生产环境走默认实现,测试注入 mock:struct UserService { private let repository: any UserRepository init(repository: any UserRepository DefaultUserRepository()) { self.repository repository } }any UserRepository表明这里依赖的是存在类型(存在协议值),默认参数DefaultUserRepository()让业务代码UserService()一行构造即可工作;而测试只需显式传入 mock,不需要反射、容器或代码生成。这种轻量 DI 与仓库中 swift-protocol-di-testing 技能 的完整实践完全对齐,后者把外部依赖(文件系统、网络、iCloud)抽象到小而聚焦的协议后面,实现确定性测试(无真实 I/O)。五步完整实践:协议、默认实现、Mock、注入、测试第 1 步:定义单一职责的小协议(引自技能文档):// 文件系统访问 public protocol FileSystemProviding: Sendable { func containerURL(for purpose: Purpose) - URL? } // 文件读写操作 public protocol FileAccessorProviding: Sendable { func read(from url: URL) throws - Data func write(_ data: Data, to url: URL) throws func fileExists(at url: URL) - Bool } // Bookmark 存储(如沙盒应用) public protocol BookmarkStorageProviding: Sendable { func saveBookmark(_ data: Data, for key: String) throws func loadBookmark(for key: String) throws - Data? }第 2 步:创建生产环境默认实现:public struct DefaultFileSystemProvider: FileSystemProviding { public init() {} public func containerURL(for purpose: Purpose) - URL? { FileManager.default.url(forUbiquityContainerIdentifier: nil) } } public struct DefaultFileAccessor: FileAccessorProviding { public init() {} public func read(from url: URL) throws - Data { try Data(contentsOf: url) } public func write(_ data: Data, to url: URL) throws { try data.write(to: url, options: .atomic) } public func fileExists(at url: URL) - Bool { FileManager.default.fileExists(atPath: url.path) } }第 3 步:创建带错误模拟能力的 mock。关键在于暴露可配置的readError/writeError,使失败路径可被确定性触发:public final class MockFileAccessor: FileAccessorProviding, unchecked Sendable { public var files: [URL: Data] [:] public var readError: Error? public var writeError: Error? public init() {} public func read(from url: URL) throws - Data { if let error readError { throw error } guard let data files[url] else { throw CocoaError(.fileReadNoSuchFile) } return data } public func write(_ data: Data, to url: URL) throws { if let error writeError { throw error } files[url] data } public func fileExists(at url: URL) - Bool { files[url] ! nil } }第 4 步:用默认参数注入到 actor 中——这里同时体现了模式三(Actor)与模式四(DI)的组合:public actor SyncManager { private let fileSystem: FileSystemProviding private let fileAccessor: FileAccessorProviding public init( fileSystem: FileSystemProviding DefaultFileSystemProvider(), fileAccessor: FileAccessorProviding DefaultFileAccessor() ) { self.fileSystem fileSystem self.fileAccessor fileAccessor } public func sync() async throws { guard let containerURL fileSystem.containerURL(for: .sync) else { throw SyncError.containerNotAvailable } let data try fileAccessor.read( from: containerURL.appendingPathComponent(data.json) ) // Process data... } }生产代码SyncManager()直接使用真实 I/O;测试则只替换需要打桩的协议。第 5 步:用 Swift Testing 写测试。技能文档给出的三个测试覆盖了“容器缺失”“正常读取”“读错误”三类路径:import Testing Test(Sync manager handles missing container) func testMissingContainer() async { let mockFileSystem MockFileSystemProvider(containerURL: nil) let manager SyncManager(fileSystem: mockFileSystem) await #expect(throws: SyncError.containerNotAvailable) { try await manager.sync() } } Test(Sync manager reads data correctly) func testReadData() async throws { let mockFileAccessor MockFileAccessor() mockFileAccessor.files[testURL] testData let manager SyncManager(fileAccessor: mockFileAccessor) let result try await manager.loadData() #expect(result expectedData) } Test(Sync manager handles read errors gracefully) func testReadError() async { let mockFileAccessor MockFileAccessor() mockFileAccessor.readError CocoaError(.fileReadCorruptFile) let manager SyncManager(fileAccessor: mockFileAccessor) await #expect(throws: SyncError.self) { try await manager.sync() } }技能文档总结的最佳实践与反模式,可归纳为:单一职责:每个协议只管一个外部关注点,禁止“上帝协议”;Sendable 一致性:协议跨 actor 边界使用时必须要求Sendable一致性;默认参数:生产代码零配置,只有测试才显式指定 mock;错误模拟:mock 暴露可配置错误属性,专门用于测试失败路径;只 mock 边界:只打桩文件系统/网络/外部 API 这类外部依赖,不 mock 无外部依赖的内部类型;反模式包括:用一个巨型协议覆盖所有外部访问、用#if DEBUG条件编译替代正规 DI、actor 场景下漏掉Sendable、以及对无外部依赖的类过度设计。测试闭环:Swift Testing 的集成方式模式四的测试示例依赖 Swift Testing,而仓库中与之配套的 rules/swift/testing.md(及其 Cursor 镜像 .cursor/rules/swift-testing.md)规定了统一的测试框架选型:框架:新测试一律使用 Swift Testing(import Testing),以Test与#expect表达断言,例如:Test(User creation validates email) func userCreationValidatesEmail() throws { #expect(throws: ValidationError.invalidEmail) { try User(email: not-an-email) } }测试隔离:每个测试获得全新实例——在init中搭建、deinit中清理,测试之间零共享可变状态;参数化测试:通过arguments展开同一用例:Test(Validates formats, arguments: [json, xml, csv]) func validatesFormat(format: String) throws { let parser try Parser(format: format) #expect(parser.isValid) }覆盖率:swift test --enable-code-coverage这条测试规则与swift-patterns.md构成闭环:值类型保证状态可穷举,actor 保证并发安全,DI 保证依赖可替换,Semantic 上为“无 I/O、确定性、快速”的 Swift Testing 用例铺平道路。两份规则共享相同的文件 glob(**/*.swift、**/Package.swift),即处理 Swift 文件时会被一并激活。如何把这套 Swift 规则应用到你的项目结合 rules/README.md 的说明,推荐流程如下:按技术栈安装规则集。Swift 项目执行./install.sh swift(脚本会同时处理 common 与 swift 两个目录);手动安装则执行cp -r rules/common ~/.claude/rules/ecc/与cp -r rules/swift ~/.claude/rules/ecc/,保持目录结构完整,不要拍平,否则语言文件会覆盖同名通用文件且../common/引用失效。理解优先级。当rules/swift/与rules/common/冲突时,语言特定规则优先;通用规则中允许被覆盖的条目会以 “Language note” 标注。规则定标准,技能给纵深。swift-patterns.md给出“应该怎么写”(协议 Sendable、值类型状态机、actor 隔离、默认参数 DI);遇到具体任务时再展开对应技能——持久化选 swift-actor-persistence,测试与 mock 选 swift-protocol-di-testing,两者文档中均包含完整的代码模板、设计决策表、最佳实践与反模式清单,可直接对照实现。用配套测试规则验收。以 rules/swift/testing.md 的 Swift Testing 约定与swift test --enable-code-coverage作为落地验证手段。小结.cursor/rules/swift-patterns.md 虽然篇幅精炼,但四条规则各自命中 Swift 现代工程的一个要害:面向协议设计(以associatedtypeSendableasync throws契约化仓储接口)、值类型(以带关联值的枚举穷举互斥状态)、Actor(以编译器而非锁解决共享可变状态)、依赖注入(以默认参数实现生产零配置、测试一键换 mock)。ECC 的设计智慧在于不把这些规则写成散文,而是与同源镜像 rules/swift/patterns.md、通用层 rules/common/patterns.md 及两个深度技能文档组成“通用原则 → 语言规则 → 技能纵深”的可检索、可执行体系,使 AI 助手与人类开发者在同一个 Swift 项目上获得一致的架构约束。【免费下载链接】ECCThe agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.项目地址: https://gitcode.com/GitHub_Trending/ev/ECC创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

最新新闻

日新闻

周新闻

月新闻