Section 3/211 menit

3. Protocols

3. Protocols

swift
import UIKit

// MARK: - Display Logic (View ← Presenter)

// Protocol ini dimiliki oleh View.
// Semua method wajib @MainActor karena memanggil UIKit.
// Presenter akan await method ini ketika memanggilnya dari @MainActor-nya sendiri.
@MainActor
protocol UserListDisplayLogic: AnyObject {
    func displayUsers(_ viewModel: UserList.FetchUsers.ViewModel)
    func displayLoading(_ isLoading: Bool)
    func displayError(message: String)
    func displaySelectedUser(_ viewModel: UserList.SelectUser.ViewModel)
}

// MARK: - Business Logic (Interactor ← View)

// Protocol ini dimiliki oleh Interactor.
// Method dibuat async karena Interactor adalah actor —
// memanggilnya dari @MainActor ViewController butuh await.
protocol UserListBusinessLogic: AnyObject, Sendable {
    func fetchUsers(request: UserList.FetchUsers.Request) async
    func selectUser(request: UserList.SelectUser.Request) async
}

// MARK: - Presentation Logic (Presenter ← Interactor)

// Protocol ini dimiliki oleh Presenter.
// @MainActor di sini karena Presenter harus memformat dan langsung
// memanggil View yang juga @MainActor.
// Ketika Interactor (actor) memanggil method ini, Swift otomatis
// menjadwalkan eksekusi ke main thread lewat await.
@MainActor
protocol UserListPresentationLogic: AnyObject {
    func presentUsers(_ response: UserList.FetchUsers.Response)
    func presentLoading(_ isLoading: Bool)
    func presentError(_ error: Error)
    func presentSelectedUser(_ response: UserList.SelectUser.Response)
}

// MARK: - Routing Logic (Router ← View)

// @MainActor karena navigasi selalu butuh UIKit (push, present, dll)
@MainActor
protocol UserListRoutingLogic: AnyObject {
    func routeToUserDetail(userId: String, userName: String)
}

// MARK: - Data Passing (Router ← View, shared dengan Interactor)

// DataStore adalah jembatan data antara View dan Router.
// Disimpan di Interactor dan dibaca oleh Router saat navigasi.
// Sendable karena bisa diakses dari beberapa isolation context.
protocol UserListDataStore: AnyObject {
    var selectedUser: User? { get }
}

Penjelasan poin penting:

  • @MainActor di protocol berarti semua conforming type harus memenuhi kontrak tersebut di main thread. Ketika Interactor (actor) memanggil presenter.presentUsers(...), Swift akan otomatis menjadwalkan ke main thread — tanpa kita perlu DispatchQueue.main.async.
  • Sendable di UserListBusinessLogic memungkinkan protocol ini disimpan di dalam actor sebagai property. Tanpa ini, menyimpan referensi ke protocol di dalam actor akan menyebabkan compile error di Swift 6.
  • async method di UserListBusinessLogic merefleksikan kenyataan bahwa Interactor adalah actor — memanggilnya dari konteks berbeda memerlukan await.