
Swift Actor Persistence
- 5.6k installs
- 238k repo stars
- Updated August 5, 2026
- affaan-m/everything-claude-code
Swift Actor Persistence is an agent skill that teaches thread-safe local data layers in Swift using actors, in-memory caching, and atomic file storage.
About
This skill teaches patterns for thread-safe data persistence in Swift 5.9+ using actors, combining an in-memory cache with file-backed JSON storage. The core pattern is a generic LocalRepository actor keyed by Identifiable IDs: synchronous load on init, O(1) reads from cache, and atomic file writes on save or delete. All public calls are async due to actor isolation, which replaces manual locks or DispatchQueue synchronization with compiler-enforced safety. The docs include a full actor implementation, usage examples, and integration with @Observable ViewModels for reactive UI updates. Design decisions cover Sendable types, minimal public APIs, atomic writes to prevent corruption, and anti-patterns like nonisolated bypasses. Activate when building offline-first iOS or macOS apps, local user data stores, or replacing legacy concurrency code with modern Swift actor-based repositories. The table of design decisions explains why actors beat class-plus-lock approaches and why dictionary keyed lookups keep reads fast while durable JSON on disk survives restarts.
- Generic LocalRepository actor with Codable and Identifiable models
- In-memory cache plus atomic JSON file persistence on disk
- Compiler-enforced thread safety without locks or DispatchQueues
- @Observable ViewModel integration for reactive SwiftUI updates
- Anti-patterns guide covering nonisolated bypass and exposed caches
Swift Actor Persistence by the numbers
- 5,646 all-time installs (skills.sh)
- +220 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #29 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
swift-actor-persistence capabilities & compatibility
- Capabilities
- swift actor repository · local json persistence · thread safe cache · swiftui integration
- Use cases
- frontend · database
What swift-actor-persistence says it does
Thread-safe data persistence in Swift using actors — in-memory cache with file-backed storage, eliminating data races by design.
The actor model guarantees serialized access — no data races, enforced by the compiler.
npx skills add https://github.com/affaan-m/everything-claude-code --skill swift-actor-persistenceAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5.6k |
|---|---|
| repo stars | ★ 238k |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 5, 2026 |
| Repository | affaan-m/everything-claude-code ↗ |
How do you store and read shared mutable app data in Swift without data races or manual synchronization?
Thread-safe Swift actor repository with in-memory cache and atomic file persistence for iOS and macOS apps.
Who is it for?
iOS or macOS developers on Swift 5.9+ building offline-first apps with local persistence and concurrent access.
Skip if: Server-side Swift services, Core Data or SwiftData migrations, or teams still targeting pre-actor Swift concurrency only.
When should I use this skill?
Building a data persistence layer in Swift 5.9+, needing thread-safe shared state, or replacing DispatchQueue locks with actors.
What you get
You get a reusable actor-based repository with fast cache reads, durable JSON writes, and async APIs ready for SwiftUI ViewModels.
- Actor persistence layer code
- File-backed storage pattern
By the numbers
- Targets Swift 5.5+ actor concurrency model
Files
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
| 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
DispatchQueueorNSLockinstead 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
nonisolatedto 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
Forks & variants (1)
Swift Actor Persistence has 1 known copy in the catalog totaling 1.4k installs. They canonicalize to this original listing.
- affaan-m - 1.4k installs
FAQ
Why use an actor instead of a class with NSLock?
Actors give compiler-enforced serialized access, eliminating manual synchronization and data races without DispatchQueue or lock management.
Do all repository calls need await?
Yes. Actor isolation makes every public method async, so callers must use await for reads and writes across concurrency domains.
How does persistence survive app crashes?
Writes use JSONEncoder plus data.write with the .atomic option so partial files are not left on disk if the app terminates mid-write.
Is Swift Actor Persistence safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.