Section 4/171 menit
4. Prinsip Dasar yang Harus Dipegang
4. Prinsip Dasar yang Harus Dipegang
Prinsip 1: Default ke Value Type Sendable
Untuk model/DTO/value object: selalu desain sebagai struct atau enum dengan stored property immutable. Mereka otomatis Sendable.
swift
// ✓ Default desain untuk model
struct UserProfile: Sendable, Identifiable, Equatable {
let id: UUID
let name: String
let avatarURL: URL?
let joinedAt: Date
}
Prinsip 2: Mutable State Hidup di Actor
Kalau ada state yang berubah dari banyak tempat, letakkan di actor. Jangan pakai class biasa dengan lock manual — itu lebih sulit di-review dan rawan bug.
swift
// ✓ Mutable state di actor
actor SessionStore {
private var sessions: [UUID: Session] = [:]
func add(_ s: Session) { sessions[s.id] = s }
func remove(id: UUID) { sessions[id] = nil }
}
Prinsip 3: UI Selalu @MainActor
ViewModel, presenter, navigation coordinator, view extension — semuanya @MainActor. Tidak ada pengecualian.
swift
// ✓ ViewModel @MainActor
@MainActor
@Observable
final class HomeViewModel {
var items: [Item] = []
var isLoading = false
}
Prinsip 4: Function Async Hanya Kalau Memang Butuh
Jangan menambahkan async di signature kalau body fungsi tidak punya await. Async menyebar — sekali ditambah, semua caller harus jadi async.
swift
// ❌ async tanpa await
func formatName(_ name: String) async -> String {
return name.uppercased() // tidak ada await
}
// ✓ sync
func formatName(_ name: String) -> String {
return name.uppercased()
}
Prinsip 5: Structured > Unstructured
Selalu prefer async let / TaskGroup / withTaskCancellationHandler daripada Task { } bebas. Structured concurrency punya garansi: parent menunggu children, cancellation propagate otomatis.
swift
// ❌ Unstructured
func loadAll() async {
Task { await loadUsers() } // detached lifecycle
Task { await loadPosts() } // bocor kalau caller dibatalkan
}
// ✓ Structured
func loadAll() async {
async let users = loadUsers()
async let posts = loadPosts()
_ = await (users, posts) // menunggu keduanya, cancellation propagate
}