Section 4/211 menit
4. Worker — actor
4. Worker — actor
swift
import Foundation
// Worker bertanggung jawab atas satu hal saja: ambil data dari sumber eksternal.
// Dijadikan actor untuk memastikan tidak ada concurrent access ke state internalnya
// (misalnya URLSession, cache, atau connection pool).
actor UserWorker {
// URLSession sudah thread-safe (Sendable), tapi kita tetap simpan di dalam
// actor agar konfigurasi session tidak bisa diubah dari luar secara bersamaan.
private let session: URLSession
private let baseURL: URL
init(
session: URLSession = .shared,
baseURL: URL = URL(string: "https://jsonplaceholder.typicode.com")!
) {
self.session = session
self.baseURL = baseURL
}
// Fungsi ini berjalan di dalam isolation domain actor ini (background thread).
// Return type [User] adalah Sendable sehingga aman dikirim ke caller (Interactor).
func fetchUsers() async throws -> [User] {
let url = baseURL.appendingPathComponent("/users")
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData)
// await di sini melepas thread actor sementara — actor bisa melayani
// permintaan lain selagi menunggu network response.
let (data, response) = try await session.data(for: request)
// Validasi HTTP response — tidak butuh await, ini synchronous check.
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw WorkerError.invalidResponse
}
// JSONDecoder.decode adalah operasi CPU-bound.
// Karena kita berada di actor (bukan main thread), ini aman dan tidak
// memblokir UI.
let dtos = try JSONDecoder().decode([UserDTO].self, from: data)
// Transformasi dari DTO (raw API model) ke Domain Model.
// Transformasi ini juga terjadi di background — efisien.
return dtos.map { dto in
User(
id: String(dto.id),
name: dto.name,
email: dto.email,
avatarURL: URL(string: "https://i.pravatar.cc/150?u=\(dto.id)")
)
}
}
enum WorkerError: Error, LocalizedError {
case invalidResponse
var errorDescription: String? {
switch self {
case .invalidResponse: return "Server mengembalikan respons tidak valid."
}
}
}
}
// MARK: - DTO (Data Transfer Object)
// DTO hanya digunakan di Worker — tidak perlu Sendable karena
// tidak pernah keluar dari isolation domain Worker.
private struct UserDTO: Decodable {
let id: Int
let name: String
let email: String
}
Penjelasan poin penting:
actor UserWorkermenjamin bahwasessiondanbaseURLtidak diakses secara concurrent dari beberapa thread. MeskipunURLSessionsudah Sendable, membungkusnya dalam actor adalah praktik yang baik untuk skalabilitas (misalnya saat nanti menambah retry logic atau request queuing).UserDTOadalahprivate— detail implementasi network tidak bocor ke layer lain. HanyaUser(domain model) yang keluar dari Worker.await session.data(for:)melepas isolation domain Actor sementara — inilah kekuatan Actor: bisa melayani permintaan lain selagi menunggu I/O.