Section 12/171 menit
12. Anti-Pattern yang Harus Dihindari
12. Anti-Pattern yang Harus Dihindari
AP 12.1: Task { @MainActor in ... } di Init
Kalau init perlu kerja async, sediakan factory method async — jangan launch task di init.
swift
// ❌ Launch di init: timing tidak terprediksi
@MainActor
final class ViewModel {
var items: [Item] = []
init() {
Task { self.items = try await load() } // race dengan caller yang baca items
}
}
// ✓ Factory async
@MainActor
final class ViewModel {
var items: [Item] = []
private init(items: [Item]) { self.items = items }
static func create() async throws -> ViewModel {
let items = try await load()
return ViewModel(items: items)
}
}
AP 12.2: Memakai @MainActor di Model Domain
Model domain (User, Order, Product) bukan UI. Jangan di-@MainActor.
swift
// ❌
@MainActor
struct User { ... }
// ✓
struct User: Sendable { ... }
AP 12.3: Capture self Strong di Long-Running Task
Long-running task yang capture self strong → memory leak.
swift
// ❌
@MainActor
final class FeedVM {
var subscription: Task<Void, Never>?
func start() {
subscription = Task {
for await item in stream {
self.items.append(item) // strong capture
}
}
}
}
// ✓ Weak self
@MainActor
final class FeedVM {
var subscription: Task<Void, Never>?
func start() {
subscription = Task { [weak self] in
for await item in stream {
guard let self else { break }
self.items.append(item)
}
}
}
}
AP 12.4: Pakai DispatchQueue.main.async di Code Baru
Code baru tidak perlu DispatchQueue.main.async. Pakai @MainActor, await MainActor.run, atau MainActor.assumeIsolated.
swift
// ❌ Style lama
DispatchQueue.main.async {
self.label.text = result
}
// ✓ Style baru
await MainActor.run {
self.label.text = result
}
// Atau kalau caller sudah @MainActor, langsung saja:
self.label.text = result
AP 12.5: unowned/unsafelyUnwrapped di Closure Async
Closure async bisa hidup lebih lama dari yang kamu pikir. unowned jadi mudah jadi dangling.
swift
// ❌ Crash kalau self di-dealloc selama await
Task { [unowned self] in
let data = try await api.fetch()
self.label.text = data.title // boom
}
// ✓
Task { [weak self] in
let data = try await api.fetch()
self?.label.text = data.title
}
AP 12.6: Mencampur lock dan await
Jangan pernah await saat memegang lock manual. Itu jalan termudah ke deadlock dan priority inversion.
swift
// ❌ Lock dipegang melalui suspension point
let lock = NSLock()
lock.lock()
defer { lock.unlock() }
let data = try await api.fetch() // suspension dengan lock dipegang
Kalau butuh sinkronisasi melalui suspension, gunakan actor — bukan lock manual.