Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
affaan-m avatar

Swift Actor Persistence

  • 1.4k installs
  • 238k repo stars
  • Updated August 5, 2026
  • affaan-m/ecc

This is a copy of swift-actor-persistence by affaan-m - installs and ranking accrue to the original listing.

swift-actor-persistence is a Claude Code skill that implements thread-safe, data-race-free persistence layers in Swift using actors and file-backed storage for developers building offline-first Apple platform apps.

About

swift-actor-persistence is an ECC skill with patterns for thread-safe data persistence using Swift actors on Swift 5.9+ targeting iOS 17+ and macOS 14+. It combines in-memory caching with file-backed storage so shared mutable state stays isolated without manual locks or DispatchQueues. Developers activate it when building offline-first apps that need compile-time data-race safety instead of ad hoc synchronization. The actor model serializes access while preserving ergonomic Swift concurrency for read and write paths.

  • Actor-isolated LocalRepository with compile-time data race elimination
  • Combines in-memory cache with automatic JSON file persistence
  • Generic over any Codable & Identifiable model type
  • Synchronous initialization with async public API
  • Eliminates manual locks, DispatchQueues, and @MainActor boilerplate

Swift Actor Persistence by the numbers

  • 1,351 all-time installs (skills.sh)
  • +84 installs in the week ending Aug 4, 2026 (Skillselion tracking)
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/affaan-m/ecc --skill swift-actor-persistence

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs1.4k
repo stars238k
Last updatedAugust 5, 2026
Repositoryaffaan-m/ecc

How do you build thread-safe Swift persistence with actors?

Implement thread-safe, data-race-free persistence layers in Swift projects using actors and file-backed storage.

Who is it for?

Swift developers on iOS 17+ or macOS 14+ building offline-first apps who want actor-isolated persistence without manual locking.

Skip if: Projects below Swift 5.9 or teams using Core Data or CloudKit exclusively should skip swift-actor-persistence for those stacks instead.

When should I use this skill?

The user needs thread-safe Swift persistence, actor-isolated storage, or offline-first local data layers without data races.

What you get

Swift actor persistence types, file-backed storage adapters, and data-race-free cache access patterns.

  • Actor-isolated persistence types
  • File-backed storage implementation

By the numbers

  • Requires Swift 5.9+ targeting iOS 17+ and macOS 14+

Files

SKILL.mdMarkdownGitHub ↗

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.9+ (iOS 17+, macOS 14+)
  • 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(items.map { ($0.id, $0) }, uniquingKeysWith: { _, latest in latest })
    }
}

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

DecisionRationale
Actor (not class + lock)Compiler-enforced thread safety, no manual synchronization
In-memory cache + file persistenceFast reads from cache, durable writes to disk
Synchronous init loadingAvoids async initialization complexity
Dictionary keyed by IDO(1) lookups by identifier
Generic over Codable & IdentifiableReusable 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

Related skills

How it compares

Choose swift-actor-persistence for actor-native file and cache layers; pick Core Data or SQLite wrappers when relational migrations dominate.

FAQ

Which Swift and OS versions does swift-actor-persistence require?

swift-actor-persistence assumes Swift 5.9 or newer on iOS 17+ and macOS 14+. Those releases provide the actor concurrency features the persistence patterns rely on for compile-time isolation.

Does swift-actor-persistence replace Core Data?

swift-actor-persistence focuses on lightweight actor-isolated file and memory persistence. Teams needing object graphs, migrations, or CloudKit sync may still choose Core Data while using actors for simpler cached state.

Backend & APIsbackendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.