Catalog
affaan-m/swift-actor-persistence

affaan-m

swift-actor-persistence

Thread-safe data persistence in Swift using actors — in-memory cache with file-backed storage, eliminating data races by design.

global
New~1.2k
v1.1Saved May 11, 2026

Swift Actors for Thread-Safe Persistence

Patterns for building thread-safe data persistence layers using Swift actors. Combines in-memory caching with file-backed storage, leveraging the actor model to eliminate data races at compile time.

When to Activate

  • Building a data persistence layer in Swift 5.5+
  • Need thread-safe access to shared mutable state
  • Want to eliminate manual synchronization (locks, DispatchQueues)
  • Building offline-first apps with local storage

Core Pattern

Actor-Based Repository

The actor model guarantees serialized access — no data races, enforced by the compiler.

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) })
    }
}

Usage

All calls are automatically async due to actor isolation:

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")

Combining with @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

Decision Rationale
Actor (not class + lock) Compiler-enforced thread safety, no manual synchronization
In-memory cache + file persistence Fast reads from cache, durable writes to disk
Synchronous init loading Avoids async initialization complexity
Dictionary keyed by ID O(1) lookups by identifier
Generic over Codable & Identifiable Reusable across any model type
Atomic file writes (.atomic) Prevents partial writes on crash

Best Practices

  • Use Sendable types for all data crossing actor boundaries
  • Keep the actor's public API minimal — only expose domain operations, not persistence details
  • Use .atomic writes to prevent data corruption if the app crashes mid-write
  • Load synchronously in init — async initializers add complexity with minimal benefit for local files
  • Combine with @Observable ViewModels for reactive UI updates

Anti-Patterns to Avoid

  • Using DispatchQueue or NSLock instead of actors for new Swift concurrency code
  • Exposing the internal cache dictionary to external callers
  • Making the file URL configurable without validation
  • Forgetting that all actor method calls are await — callers must handle async context
  • Using nonisolated to bypass actor isolation (defeats the purpose)

When to Use

  • Local data storage in iOS/macOS apps (user data, settings, cached content)
  • Offline-first architectures that sync to a server later
  • Any shared mutable state that multiple parts of the app access concurrently
  • Replacing legacy DispatchQueue-based thread safety with modern Swift concurrency
Files1
1 files · 1.0 KB

Select a file to preview

Overall Score

87/100

Grade

A

Excellent

Safety

90

Quality

88

Clarity

85

Completeness

82

Summary

This skill teaches thread-safe data persistence in Swift using the actor model, combining in-memory caching with file-backed JSON storage. It provides a reusable `LocalRepository<T>` actor pattern with synchronous initialization, O(1) lookups, and atomic file persistence, designed for offline-first apps and eliminating manual synchronization.

Detected Capabilities

code example generationactor pattern implementationfile I/O operationsJSON encoding/decodingSwift concurrency guidance

Trigger Keywords

Phrases that MCP clients use to match this skill to user intent.

swift actor persistencethread-safe storageoffline-first datalocal json storageswift concurrencyactor-based repository

Use Cases

  • Build thread-safe local storage layers in Swift apps
  • Implement offline-first data synchronization with file persistence
  • Replace legacy DispatchQueue synchronization with modern Swift concurrency
  • Create generic reusable repository patterns for domain models
  • Combine actor-based persistence with SwiftUI reactive ViewModels

Quality Notes

  • Excellent documentation with clear rationale table for design decisions
  • Well-structured code examples showing both the actor pattern and ViewModel integration
  • Comprehensive best practices and anti-patterns section helps developers avoid common pitfalls
  • Uses Swift language features appropriately (Sendable, async/await, actor isolation)
  • Clear scope boundaries — focuses only on local file persistence, not network sync or authentication
  • Good educational value explaining the 'why' behind actor-based persistence vs legacy approaches
  • Examples show proper use of atomic writes to prevent data corruption
  • Includes practical usage patterns that developers can immediately adapt to their own models
Model: claude-haiku-4-5-20251001Analyzed: May 11, 2026

Reviews

Add this skill to your library to leave a review.

No reviews yet

Be the first to share your experience.

Version History

v1.1

Content updated

2026-04-20

Latest
v1.0

Seeded from github.com/affaan-m/everything-claude-code

2026-03-16

Add affaan-m/swift-actor-persistence to your library

Command Palette

Search for a command to run...