Section 11/131 menit

11. Tips & Best Practices

11. Tips & Best Practices

1. Jangan Campur GCD dan Swift Concurrency di Layer yang Sama

swift
// BURUK — campur GCD dan Swift Concurrency
func badMix() async {
    let result = await fetch()
    DispatchQueue.main.async {  // tidak perlu! sudah di actor context
        self.label.text = result
    }
}

// BAIK — konsisten dengan Swift Concurrency
@MainActor
func goodApproach() async {
    let result = await fetch()
    label.text = result  // sudah di main thread lewat @MainActor
}

2. Selalu Simpan Task Reference Jika Perlu Cancel

swift
@MainActor
class SearchViewController: UIViewController {
    private var activeTask: Task<Void, Never>?

    func search(query: String) {
        activeTask?.cancel()  // cancel search sebelumnya
        activeTask = Task {
            let results = try? await searchService.search(query: query)
            guard !Task.isCancelled else { return }
            displayResults(results ?? [])
        }
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        activeTask?.cancel()  // bersihkan saat view hilang
    }
}

3. @MainActor ≠ DispatchQueue.main

swift
// @MainActor — COMPILE TIME guarantee, lebih kuat
@MainActor func updateUI() {
    label.text = "Updated"
}

// DispatchQueue.main.async — RUNTIME dispatch, bisa nested
DispatchQueue.main.async {
    DispatchQueue.main.async {  // tidak deadlock, tapi tidak efisien
        label.text = "Updated"
    }
}

// @MainActor tidak bisa digunakan di callback non-async
URLSession.shared.dataTask(with: url) { data, _, _ in
    // Di sini bukan @MainActor context meskipun class-nya @MainActor
    // Harus gunakan Task { @MainActor in ... } atau DispatchQueue.main.async
    Task { @MainActor in
        self.label.text = "Done"
    }
}

4. Hindari @unchecked Sendable Berlebihan

swift
// JANGAN — @unchecked Sendable sebagai jalan pintas
final class DataHolder: @unchecked Sendable {
    var items: [String] = []  // ← Tidak ada proteksi! Tetap data race!
}

// LEBIH BAIK — gunakan actor
actor DataHolder {
    var items: [String] = []
}

// ATAU — buat immutable
struct DataHolder: Sendable {
    let items: [String]  // let, bukan var
}

5. nonisolated untuk Pure Functions

swift
actor UserManager {
    var users: [User] = []

    // Harus await dari luar — mengakses state actor
    func addUser(_ user: User) {
        users.append(user)
    }

    // nonisolated — tidak akses state, tidak perlu await
    nonisolated func validateEmail(_ email: String) -> Bool {
        email.contains("@") && email.contains(".")
    }

    // nonisolated computed property — bebas dari isolation
    nonisolated var description: String {
        "UserManager"
    }
}

let manager = UserManager()
// Tidak perlu await!
let isValid = manager.validateEmail("user@example.com")

6. Aktifkan Warning Sebelum Swift 6 Mode

Lakukan secara bertahap:

swift
-warn-concurrency                          → warning ringan
-Xfrontend -strict-concurrency=targeted   → warning menengah
-Xfrontend -strict-concurrency=complete   → hampir setara Swift 6
Swift 6 Language Mode                      → semua jadi error

7. Actor Tidak Menjamin Urutan Eksekusi

swift
actor Counter {
    var value = 0

    func increment() {
        value += 1
    }
}

let counter = Counter()

// PERHATIAN: Tidak ada jaminan urutan eksekusi di antara Task berbeda
Task { await counter.increment() }
Task { await counter.increment() }
Task { await counter.increment() }
// Hasil akhir selalu 3, tapi urutan eksekusinya tidak terdefinisi

8. Prefer AsyncStream daripada Combine untuk Kode Baru

swift
// Combine — masih valid, tapi lebih kompleks untuk use case sederhana
let publisher = PassthroughSubject<Event, Never>()
var cancellables = Set<AnyCancellable>()

publisher
    .sink { event in handleEvent(event) }
    .store(in: &cancellables)

// AsyncStream — lebih simpel, terintegrasi dengan Swift Concurrency
let (stream, continuation) = AsyncStream.makeStream(of: Event.self)

Task {
    for await event in stream {
        handleEvent(event)
    }
}

9. Jangan Block Main Thread

swift
// SALAH — blocking main thread!
func loadData() {
    let result = Task { await service.fetch() }.value  // ← DEADLOCK jika di @MainActor
}

// BENAR — propagate async ke atas
func loadData() async {
    let result = await service.fetch()
    displayResult(result)
}

// ATAU dari non-async context
func viewDidLoad() {
    Task {
        await loadData()
    }
}

10. Structured Concurrency untuk Scope Terbatas

swift
// Gunakan withTaskGroup untuk operasi yang scope-nya jelas
func processImages(_ urls: [URL]) async throws -> [UIImage] {
    try await withThrowingTaskGroup(of: UIImage.self) { group in
        for url in urls {
            group.addTask { try await downloadImage(from: url) }
        }

        var images: [UIImage] = []
        for try await image in group {
            images.append(image)
        }
        return images
    }
    // Semua task selesai saat keluar dari scope ini
}

// Hindari Task.detached untuk operasi yang hasilnya masih dibutuhkan caller