MarketplaceSKILL.mdLeída en ef648e0
swift-actor-persistence
Thread-safe data persistence in Swift using actors — in-memory cache with file-backed storage, eliminating data races by design.
Resumen
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
Sendabletypes for all data crossing actor boundaries - Keep the actor's public API minimal — only expose domain operations, not persistence details
- Use
.atomicwrites 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
@ObservableViewModels 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
Archivos 1 archivo
Instálala a mano
Deska ejecuta esto por ti, para cada agente que elijas. Para ejecutarlo tú en una terminal:
npx skills add affaan-m/ecc --skill swift-actor-persistenceMás de affaan-m
- docker-patternsaffaan-mDocker and Docker Compose patterns for local development, container security, networking, volume strategies, and multi-service orchestration. Use when setting up containerized development environments or reviewing Docker configurations.4,800 instalaciones en skills.sh0
- security-reviewaffaan-mUse this skill when adding authentication, handling user input, working with secrets, creating API endpoints, or implementing payment/sensitive features. Provides comprehensive security checklist and patterns.4,800 instalaciones en skills.sh0
- backend-patternsaffaan-mBackend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes. Use when building or reviewing Node.js, Express, or Next.js API routes and their data access.4,700 instalaciones en skills.sh0
- frontend-patternsaffaan-mFrontend development patterns for React, Next.js, state management, performance optimization, and UI best practices. Use when building or reviewing React or Next.js components, state, or render performance.4,700 instalaciones en skills.sh0
- coding-standardsaffaan-mBaseline cross-project coding conventions for naming, readability, immutability, and code-quality review. Use detailed frontend or backend skills for framework-specific patterns. Use when reviewing code quality or naming with no framework-specific skill that applies.4,500 instalaciones en skills.sh0