Claude d66b5fa480 docs: fix zh-CN parity — add 44 missing files to ja-JP
Add files present in zh-CN but missing from ja-JP:
- commands: claw, context-budget, devfleet, docs, projects, prompt-optimize, rules-distill (7 files)
- skills: regex-vs-llm-structured-text, remotion-video-creation, repo-scan, research-ops,
  returns-reverse-logistics, rules-distill, rust-patterns, rust-testing, skill-comply,
  skill-stocktake, social-graph-ranker, swift-actor-persistence, swift-concurrency-6-2,
  swift-protocol-di-testing, swiftui-patterns, team-builder, terminal-ops, token-budget-advisor,
  ui-demo, unified-notifications-ops, video-editing, videodb (+reference/*), visa-doc-translate,
  workspace-surface-audit, x-api (37 files)

Result: ja-JP now has 517 files vs zh-CN 412 files.
zh-CN parity: 0 missing files (complete parity achieved).
2026-05-17 02:31:40 -04:00

144 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: swift-actor-persistence
description: Swiftでactorを使用してスレッドセーフなデータ永続化を実装する——メモリキャッシュとファイルバックドストレージを組み合わせ、設計によってデータ競合を排除する。
origin: ECC
---
# スレッドセーフな永続化のための Swift Actor
Swiftのactorを使用してスレッドセーフなデータ永続化レイヤーを構築するパターン。メモリキャッシュとファイルバックドストレージを組み合わせ、actorモデルを活用してコンパイル時にデータ競合を排除する。
## 起動条件
* Swift 5.5以降でデータ永続化レイヤーを構築する場合
* 共有可変状態へのスレッドセーフアクセスが必要な場合
* 手動の同期ロック、DispatchQueueを排除したい場合
* ローカルストレージを持つオフラインファースとアプリを構築する場合
## コアパターン
### Actorベースのリポジトリ
Actorモデルはシリアライズされたアクセスを保証する——コンパイラによって強制されるデータ競合なし。
```swift
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)
// 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の分離により、すべての呼び出しは自動的に非同期になる
```swift
let repository = LocalRepository<Question>()
// Read — fast O(1) lookup from in-memory cache
let question = await repository.find(by: "q-001")
let allQuestions = await repository.loadAll()
// Write — updates cache and persists to file atomically
try await repository.save(newQuestion)
try await repository.delete("q-001")
```
### @Observable ViewModel との組み合わせ
```swift
@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()
}
}
```
## 重要な設計上の決定
| 決定 | 理由 |
|----------|-----------|
| Actorを使用クラス + ロックではなく) | コンパイラによって強制されるスレッド安全性、手動同期不要 |
| メモリキャッシュ + ファイル永続化 | キャッシュからの高速読み取り、ディスクへの永続的な書き込み |
| 初期化時の同期ロード | 非同期初期化の複雑さを回避 |
| IDをキーとする辞書 | 識別子によるO(1)検索 |
| ジェネリック `Codable & Identifiable` | あらゆるモデル型で再利用可能 |
| アトミックなファイル書き込み(`.atomic` | クラッシュ時の部分書き込みを防ぐ |
## ベストプラクティス
* **Actorの境界を越えるすべてのデータに `Sendable` 型を使用する**
* **Actorのパブリックなアビリティを最小化する** —— 永続化の詳細ではなく、ドメイン操作のみを公開する
* **`.atomic` 書き込みを使用する** —— 書き込み中のアプリクラッシュによるデータ破損を防ぐ
* **`init` で同期的にロードする** —— 非同期イニシャライザはローカルファイルに対するわずかな利点のために複雑さが増す
* **`@Observable` ViewModelと組み合わせる** —— リアクティブなUI更新を実現する
## 避けるべきアンチパターン
* Swiftの新しい並行処理コードでActorの代わりに `DispatchQueue` または `NSLock` を使用する
* 内部のキャッシュ辞書を外部の呼び出し元に公開する
* 検証なしでファイルURLを設定可能にする
* すべてのActor メソッド呼び出しが `await` であることを忘れる——呼び出し元は非同期コンテキストを処理する必要がある
* Actor の分離をバイパスするために `nonisolated` を使用する(本末転倒)
## 使用場面
* iOS/macOSアプリのローカルデータストレージユーザーデータ、設定、キャッシュコンテンツ
* 後でサーバーと同期するオフラインファーストアーキテクチャ
* アプリの複数の部分から並行アクセスされる共有可変状態
* `DispatchQueue` ベースのレガシーなスレッド安全機構を最新のSwift並行処理に置き換える