Skip to content
cccskills
FreePublished on GitHub

swift-actor-persistence

Swift 中使用 actor 实现的线程安全数据持久化 —— 结合内存缓存与文件存储,从设计上消除数据竞争。

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md5.1 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

使用 Swift Actors 实现线程安全持久化 (Swift Actors for Thread-Safe Persistence)

使用 Swift actor 构建线程安全数据持久化层的模式。该模式结合了内存缓存(In-memory caching)与基于文件的存储,利用 actor 模型(Actor model)在编译时消除数据竞争(Data races)。

何时启用

  • 在 Swift 5.5+ 中构建数据持久化层
  • 需要对共享可变状态(Shared mutable state)进行线程安全访问
  • 希望消除手动同步操作(如 locks、DispatchQueues)
  • 构建具有本地存储的离线优先(Offline-first)应用

核心模式 (Core Pattern)

基于 Actor 的仓库模式 (Actor-Based Repository)

Actor 模型保证了序列化访问(Serialized access)—— 由编译器强制执行,确保不会出现数据竞争。

public actor LocalRepository<T: 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)
        // 在 init 期间同步加载(此时 actor 隔离尚未生效)
        self.cache = Self.loadSynchronously(from: fileURL)
    }

    // MARK: - 公共 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 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) })
    }
}

使用方法 (Usage)

由于 actor 隔离(Actor isolation),所有调用都会自动变为异步:

let repository = LocalRepository<Question>()

// 读取 —— 从内存缓存中进行快速 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")

结合 @Observable ViewModel

@Observable
final class QuestionListViewModel {
    private(set) var questions: [Question] = []
    private let repository: LocalRepository<Question>

    init(repository: LocalRepository<Question> = 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()
    }
}

关键设计决策 (Key Design Decisions)

决策理由
使用 Actor (而非 class + lock)编译器强制执行线程安全,无需手动同步
内存缓存 + 文件持久化缓存提供快速读取,磁盘提供持久化写入
在 init 中同步加载避免异步初始化的复杂性,且对本地文件影响较小
以 ID 为键的字典 (Dictionary)通过标识符进行 O(1) 查找
泛型支持 Codable & Identifiable可在任何模型类型中复用
原子化文件写入 (.atomic)防止崩溃时出现部分写入导致的数据损坏

最佳实践 (Best Practices)

  • 对所有跨 actor 边界的数据使用 Sendable 类型
  • 保持 actor 的公共 API 最小化 —— 仅暴露业务领域操作,不暴露持久化细节
  • 使用 .atomic 写入 以防止应用在写入过程中崩溃导致数据损坏
  • 在 init 中同步加载 —— 异步初始化会增加复杂性,而对本地文件的同步读取收益更高
  • 结合 @Observable ViewModel 实现响应式 UI 更新

避免的反模式 (Anti-Patterns to Avoid)

  • 在新的 Swift 并发代码中使用 DispatchQueue 或 NSLock 而非 actor
  • 将内部缓存字典直接暴露给外部调用者
  • 在没有验证的情况下让文件 URL 变为可配置
  • 遗忘所有 actor 方法调用都是 await 的 —— 调用者必须处理异步上下文
  • 使用 nonisolated 绕过 actor 隔离(这违背了使用 actor 的初衷)

适用场景 (When to Use)

  • iOS/macOS 应用中的本地数据存储(用户数据、设置、缓存内容)
  • 稍后与服务器同步的离线优先(Offline-first)架构
  • 任何被应用多个部分并发访问的共享可变状态
  • 使用现代 Swift 并发(Swift Concurrency)替换旧有的基于 DispatchQueue 的线程安全方案

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

生产级 API 的 REST API 设计模式,包括资源命名、状态码、分页、过滤、错误响应、版本控制和速率限制。

The English summary is being prepared. Showing the original description.

xu-xiang/everything-claude-code-zh2KUpdated Mar 5, 2026

REST API设计模式,包括资源命名、状态码、分页、过滤、错误响应、版本控制和生产API的速率限制。

The English summary is being prepared. Showing the original description.

xu-xiang/everything-claude-code-zh2KUpdated Mar 5, 2026

编写文章、指南、博客帖子、教程、新闻通讯(newsletter)以及其他长篇内容。这些内容具有从提供的示例或品牌指南中提取出的独特语气。当用户需要比段落更长的精美文案,且对语气一致性、结构和可信度有要求时,请使用此技能(Skill)。

The English summary is being prepared. Showing the original description.

xu-xiang/everything-claude-code-zh2KUpdated Mar 5, 2026

根据提供的示例或品牌指导,以独特的语气撰写文章、指南、博客帖子、教程、新闻简报等长篇内容。当用户需要超过一段的精致书面内容时使用,尤其是当语气一致性、结构和可信度至关重要时。

The English summary is being prepared. Showing the original description.

xu-xiang/everything-claude-code-zh2KUpdated Mar 5, 2026

撰写文章、指南、博客、教程、时事通讯等长篇内容,并根据提供的示例或品牌指南呈现独特的语言风格。当用户需要篇幅超过一段的精炼文字,且重视风格一致性、结构和可信度时,请使用此技能。

The English summary is being prepared. Showing the original description.

xu-xiang/everything-claude-code-zh2KUpdated Mar 5, 2026

编写文章、指南、博客、教程、时事通讯(Newsletter)等长内容,支持从示例或品牌指南中提取独特的语感语调。适用于需要撰写超过一个段落的精炼文本,尤其是对语气一致性、结构和可信度有较高要求时。

The English summary is being prepared. Showing the original description.

xu-xiang/everything-claude-code-zh2KUpdated Mar 5, 2026

See all skills by xu-xiang

Report a problem with this skill