Section 8/161 menit

8. Shared Mutable State — Pola Aman

8. Shared Mutable State — Pola Aman

Pola 1: Actor untuk Shared State

swift
// Shared cache yang aman di-access dari banyak concurrent task
actor ImageCache {
    private var storage: [URL: CachedImage] = [:]
    private var ongoingRequests: [URL: Task<CachedImage, Error>] = [:]
    
    func image(for url: URL) async throws -> CachedImage {
        // Hit cache
        if let cached = storage[url] {
            return cached
        }
        
        // Deduplicate concurrent requests untuk URL yang sama
        if let ongoingTask = ongoingRequests[url] {
            return try await ongoingTask.value
        }
        
        // Mulai request baru
        let task = Task<CachedImage, Error> {
            let (data, _) = try await URLSession.shared.data(from: url)
            return CachedImage(data: data, url: url)
        }
        
        ongoingRequests[url] = task
        
        do {
            let image = try await task.value
            storage[url] = image
            ongoingRequests.removeValue(forKey: url)
            return image
        } catch {
            ongoingRequests.removeValue(forKey: url)
            throw error
        }
    }
    
    func invalidate(url: URL) {
        storage.removeValue(forKey: url)
    }
    
    func invalidateAll() {
        storage.removeAll()
    }
}

Pola 2: Mutex untuk Low-Overhead Synchronization

swift
import Synchronization

// Mutex dari Swift 6 Synchronization framework
// Lebih ringan dari actor untuk operasi yang sangat singkat
final class ThreadSafeCounter: Sendable {
    private let value = Mutex<Int>(0)
    
    func increment() {
        value.withLock { $0 += 1 }
    }
    
    func decrement() {
        value.withLock { $0 -= 1 }
    }
    
    var current: Int {
        value.withLock { $0 }
    }
}

// Mutex untuk state yang lebih kompleks
final class RequestTracker: Sendable {
    struct State {
        var activeRequests: Int = 0
        var totalCompleted: Int = 0
        var errors: [Error] = []
    }
    
    private let state = Mutex<State>(State())
    
    func requestStarted() {
        state.withLock { $0.activeRequests += 1 }
    }
    
    func requestCompleted() {
        state.withLock {
            $0.activeRequests -= 1
            $0.totalCompleted += 1
        }
    }
    
    func requestFailed(with error: Error) {
        state.withLock {
            $0.activeRequests -= 1
            $0.errors.append(error)
        }
    }
    
    var snapshot: State {
        state.withLock { $0 }
    }
}

Pola 3: Copy-on-Write Value Types

swift
// Value types dengan COW semantics secara otomatis thread-safe untuk reads
// karena setiap thread mendapat copy-nya sendiri
struct DocumentState {
    private var _content: String
    private var _metadata: [String: String]
    
    // Sendable karena semua stored properties Sendable
    init(content: String = "", metadata: [String: String] = [:]) {
        self._content = content
        self._metadata = metadata
    }
    
    mutating func updateContent(_ newContent: String) {
        _content = newContent
    }
    
    mutating func setMetadata(key: String, value: String) {
        _metadata[key] = value
    }
    
    var content: String { _content }
    var metadata: [String: String] { _metadata }
}

// Actor yang menyimpan value type — akses concurrent aman
actor DocumentStore {
    private var documents: [DocumentID: DocumentState] = [:]
    
    func update(_ id: DocumentID, with state: DocumentState) {
        documents[id] = state
    }
    
    func document(for id: DocumentID) -> DocumentState? {
        documents[id]  // Return copy — caller mendapat snapshot immutable
    }
}

Pola 4: Atomic untuk Simple Primitives

swift
import Synchronization

// Atomic untuk counter, flags, sequence numbers
final class SequenceGenerator: Sendable {
    private let counter = Atomic<Int>(0)
    
    func next() -> Int {
        // Atomic fetch-and-add — tidak butuh lock
        counter.fetchAdd(1, ordering: .relaxed)
    }
}

// Atomic flag untuk cancellation
final class CancellationToken: Sendable {
    private let cancelled = Atomic<Bool>(false)
    
    func cancel() {
        cancelled.store(true, ordering: .releasing)
    }
    
    var isCancelled: Bool {
        cancelled.load(ordering: .acquiring)
    }
}