Section 4/131 menit
4. Actor & @MainActor
4. Actor & @MainActor
Teori: Isolation Berbasis Tipe
Actor memproteksi state-nya dengan memastikan hanya satu Task yang mengaksesnya pada satu waktu. Tidak perlu lock manual — compiler dan runtime yang menjamin ini.
swift
Thread A ──► await cache.value ──► (antri)
│
Thread B ──► await cache.value ──► (antri)
│
Actor (serial access)
│
Task A berjalan dulu
Task B berjalan setelah A selesai
actor — Custom Actor
swift
// Tanpa actor — DATA RACE! Bisa crash atau corrupt data
class UnsafeImageCache {
private var store: [String: UIImage] = [:] // ← bisa diakses dari mana saja!
func store(_ image: UIImage, for key: String) {
store[key] = image // ← tidak thread-safe
}
func image(for key: String) -> UIImage? {
return store[key]
}
}
// DENGAN actor — compiler menjamin thread safety
actor ImageCache {
private var store: [String: UIImage] = [:]
// Akses ke state hanya bisa lewat await dari luar actor
func store(_ image: UIImage, for key: String) {
store[key] = image
}
func image(for key: String) -> UIImage? {
return store[key]
}
// nonisolated — tidak butuh await saat dipanggil, tidak akses state actor
nonisolated var description: String {
return "ImageCache"
}
}
// Cara menggunakan
let cache = ImageCache()
Task {
// await wajib karena ImageCache adalah actor
await cache.store(image, for: "avatar_123")
let img = await cache.image(for: "avatar_123")
}
@MainActor — Isolation ke Main Thread
@MainActor adalah global actor bawaan Swift yang mewakili main thread.
Tandai class/property/function dengan @MainActor untuk memastikan dijalankan di main thread.
swift
// @MainActor pada class — semua properti dan method berjalan di main thread
@MainActor
class ProfileViewModel {
// Properti ini otomatis di-isolate ke main thread
var userName: String = ""
var isLoading: Bool = false
var errorMessage: String? = nil
private let service: NetworkService
init(service: NetworkService) {
self.service = service
}
// Method ini berjalan di main thread — aman update UI langsung
func fetchProfile(userId: String) async {
isLoading = true
errorMessage = nil
do {
// service.fetchUser adalah async func — suspension point
// setelah await kembali, kita sudah di main thread lagi
let user = try await service.fetchUser(id: userId)
userName = user.name
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
// nonisolated untuk pure computation yang tidak sentuh state @MainActor
nonisolated func formatDate(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter.string(from: date)
}
}
isolated Parameter — Inject Isolation
swift
// Fungsi yang menerima actor sebagai parameter terisolasi
func updateUI(on viewController: isolated UIViewController & AnyObject,
with data: UserData) {
// Di sini kita isolated ke viewController
// Bisa akses properti viewController tanpa await
}
// Lebih umum: inject @MainActor
@MainActor
func updateLabels(name: String, email: String) {
nameLabel.text = name
emailLabel.text = email
}
Perbedaan Swift 5.x vs Swift 6
swift
// Di Swift 5.x: ini menghasilkan WARNING (tidak error)
class MyViewController: UIViewController {
var items: [String] = [] // Tidak ada isolation
func loadFromBackground() {
Task.detached {
// ⚠️ Warning: crossing actor boundary tanpa await
// Di Swift 6 ini adalah ERROR
self.items = ["A", "B"] // Akses main thread property dari background
}
}
}
// Solusi yang benar di Swift 5.x (dan Swift 6):
@MainActor
class MyViewController: UIViewController {
var items: [String] = []
func loadFromBackground() {
Task.detached {
let newItems = await fetchItems()
// Kembali ke MainActor untuk update state
await MainActor.run {
self.items = newItems
}
}
}
}