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