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.