Back home

Seri Konkurensi Swift 07|Permasalahan umum saat menggabungkan SwiftUI dengan async/menunggu

Jebakan sebenarnya biasanya bukan pada sintaksisnya, tetapi pada apakah "siklus hidup halaman" dan "siklus hidup tugas" selaras.

SwiftUI dan async/await sama-sama elegan jika dilihat secara terpisah, namun jika digabungkan, masalah yang paling mudah terungkap adalah siklus hidup.

Lebih tepatnya, ini adalah dislokasi antara dua siklus kehidupan:

  • Kapan halaman SwiftUI muncul, digambar ulang, atau hilang?
  • Kapan tugas asinkron dimulai, dijeda, diakhiri, dan dibatalkan?

Jika kedua hal ini tidak selaras, meskipun kode tersebut dapat dijalankan saat ini, kode tersebut akan berkembang dengan mudah di kemudian hari:

  • Ulangi permintaan
  • Halaman berkedip
  • Tulis kembali hasil lama
  • Status pemuatan membingungkan
  • Tugas masih diperbarui setelah meninggalkan halaman

Jadi yang ingin dibicarakan dalam artikel ini adalah: Halaman asinkron SwiftUI rentan terhadap kekacauan, dan apa akar penyebab di balik kekacauan ini.

1. Kesalahan penilaian paling umum: memperlakukan View sebagai objek stabil

Ini adalah kebiasaan termudah yang diterapkan oleh banyak pengembang dengan latar belakang UIKit.

Setiap orang akan default ke:

  • Halaman muncul satu kali
  • Permintaan untuk mengirim sekali
  • Perbarui halaman saat ini setelah permintaan kembali

Namun View di SwiftUI lebih seperti deskripsi status, bukan instance stabil yang dapat disimpan dalam waktu lama.

Artinya jika default yang ada di benak Anda adalah “Pemandangan di depan saya akan selalu ada, jadi tugas ini secara alami adalah miliknya”, maka akan mudah menimbulkan masalah di kemudian hari.

SwiftUI tidak mengharuskan tugas dikaitkan dengan objek yang stabil, hanya saja mudah untuk membodohi diri sendiri dengan berpikir bahwa Anda sudah melakukannya.

2. Kesalahan terbesar onAppear adalah ia digunakan sebagai pintu masuk inisialisasi satu kali.

Banyak artikel yang mengatakan: onAppear dapat dieksekusi beberapa kali. Pernyataan ini ada benarnya, namun tidak cukup.

Bahaya sebenarnya adalah sering kali ditulis sebagai entri standar de facto untuk “inisialisasi halaman”:

.onAppear {
  Task {
    await loadData()
  }
}

Masalahnya bukan kode ini pasti salah, tapi mudah untuk menambahkannya secara mental:

“Saat halaman ini muncul, hanya akan dijalankan sekali.”

Setelah Anda berpikir seperti ini, hal berikut akan muncul satu demi satu:

  • Ulangi permintaan
  • Status reset berulang kali
  • Berulang kali mengubur poin
  • Data dihapus segera setelah ditampilkan dan dimulai kembali.

Jadi ide yang lebih stabil adalah: **Biarkan proses asinkron itu sendiri menjadi idempoten, terdeduplikasi, atau dapat diganti. **

3. Jebakan kedua: menggabungkan status halaman dan status tugas

Status umum di halaman SwiftUI meliputi:

  • Kriteria filter saat ini
  • Konten data terkini
  • Apakah sedang memuat?
  • pesan kesalahan
  • Tugas yang sedang berjalan

Jika negara bagian ini tidak memiliki lapisan yang jelas, mereka dapat dengan mudah disatukan.

Bau tak sedap yang paling umum adalah:

  • items
  • isLoading
  • error
  • isRefreshing
  • keyword
  • selectedTab

Setiap nilai secara individual masuk akal, namun sulit untuk mengatakan bagaimana mereka berhubungan satu sama lain. Kemudian halaman tersebut akan memasuki keadaan yang sangat canggung:

  • Sepertinya dia dalam kondisi apa pun
  • Namun tidak ada negara bagian yang benar-benar mengungkapkan “semantik apa yang ada di halaman tersebut sekarang”

Dalam hal ini, segera setelah hasil asinkron muncul, status apa pun dapat diubah, dan masalahnya hanya akan terungkap cepat atau lambat.

4. Kesalahan ketiga: hasil lama ditulis kembali ke UI saat ini

Ini adalah salah satu masalah yang paling sering terjadi di halaman asinkron SwiftUI.

Skenario umum meliputi:

  • Pengguna dapat berpindah tab dengan cepat
  • Kata kunci pencarian berubah terus menerus
  • Berulang kali mengganti kondisi filter
  • Halaman memicu penyegaran dan pemuatan pertama satu demi satu

Di permukaan, Anda mungkin berpikir bahwa Anda baru saja “mengirim banyak tugas”, namun kenyataannya, masalah sebenarnya adalah:

**Meskipun tugas lama masih diselesaikan secara hukum, tugas tersebut tidak lagi sesuai dengan status halaman saat ini. **

Setelah hasil lama masih dapat ditulis ke UI saat ini, tampilan yang Anda lihat biasanya hanya:

  • Halaman berkedip
  • Daftarnya tiba-tiba mundur
  • Status pemuatan berakhir secara tiba-tiba
  • Pesan kesalahan muncul tanpa alasan

Fenomena ini sangat mirip dengan “bug kecil”, namun akar penyebabnya sangat mirip: Validitas hasil tugas tidak dikelola.

5. Lubang keempat: sebarkan semua entri asynchronous di View

Jika entri ini muncul di halaman secara bersamaan:

  • onAppear { Task { ... } }
  • refreshable { await ... }
  • onChange(of:) { Task { ... } }
  • Klik tombol untuk membuka Task lainnya

Semuanya tampak sah secara individual, tetapi jika digabungkan, semuanya dengan cepat menjadi masalah:

**Hubungan tugas di luar kendali. **

Jawabannya cenderung semakin sulit:

  • siapa pintu masuk utama
  • Siapa yang harus membatalkan siapa?
  • Siapa yang berhak mengubah status tampilan saat ini?
  • Putaran tugas manakah yang sesuai dengan pembaruan status tertentu?

Oleh karena itu, banyak halaman asinkron SwiftUI memiliki terlalu banyak pintu masuk, setiap pintu masuk dapat langsung memicu tugas, dan akhirnya tidak ada lapisan koordinasi terpadu.

6. Jebakan kelima: defaultnya adalah “selama UI dapat diperbarui”

SwiftUI menyembunyikan banyak detail pembaruan UI, yang memberikan ilusi kepada orang-orang bahwa selama saya akhirnya mengubah status, halaman akan disegarkan secara alami.

Namun pertanyaan sebenarnya bukanlah “apakah akan disegarkan?” tetapi “apakah memenuhi syarat untuk menyegarkan saat ini.”

Misalnya:

  • Apakah hasil saat ini sudah kedaluwarsa?
  • Apakah halaman saat ini masih aktif?
  • Apakah status saat ini masih sesuai dengan putaran tugas ini?
  • Haruskah modifikasi saat ini dilakukan berdasarkan semantik aktor utama?

Jika hal-hal ini tidak ditanggapi dengan serius, halaman tersebut mungkin tidak langsung mogok, tetapi perlahan-lahan akan menumpuk banyak “kebingungan yang tidak disengaja”.

7. Pendekatan yang lebih stabil: biarkan Tampilan memicu maksud dan biarkan lapisan status mengelola tugas

Metode pengorganisasian yang saya sukai adalah:

  • Tampilan bertanggung jawab untuk mengekspresikan maksud pengguna
  • ViewModel atau lapisan negara bertanggung jawab untuk mengelola tugas dan kualifikasi hasil
  • Tampilan hanya menggunakan status yang diurutkan

Oleh karena itu, sebaiknya View mengetahui lebih sedikit tentang detail berikut:

  • Apakah akan membatalkan tugas lama
  • Hasil mana yang telah kedaluwarsa
  • Apakah pemuatan saat ini memuat untuk pertama kalinya atau menyegarkan?
  • Apakah status kesalahan harus menimpa konten lama

Jika masalah ini dibiarkan pada Tampilan, halaman akan dengan cepat berubah dari “UI deklaratif” menjadi “shell deklaratif yang dibungkus dalam lapisan logika asinkron terdesentralisasi”.

8. Saran yang mendekati pertarungan sebenarnya

Jika halaman SwiftUI mulai rumit, saya biasanya memaksakan diri untuk menjawab pertanyaan berikut terlebih dahulu:

  1. Entri tugas apa yang ada di halaman tersebut?
  2. Apakah tugas serupa dijalankan secara berdampingan, diganti atau diabaikan.
  3. Status mana yang merupakan status semantik halaman dan mana yang hanya merupakan status proses internal.
  4. Apakah hasil lama masih boleh diubah ke halaman saat ini.
  5. Setelah keluar dari halaman, tugas mana yang harus dilanjutkan dan mana yang harus dihentikan.

Jika Anda tidak dapat menjawab pertanyaan ini, biasanya berarti model tugas halaman belum ditetapkan.

9. Kesimpulan: Kesulitan sebenarnya dari halaman asinkron SwiftUI adalah penyelarasan siklus hidup

Situasi umum adalah kendala utama antara SwiftUI dan async/await adalah sintaksis. Namun situasi yang lebih nyata adalah:

  • View bukanlah objek stabil seperti yang Anda bayangkan di permukaan.
  • onAppear bukan semantik inisialisasi satu kali
  • Hasil lama tidak akan otomatis kadaluwarsa karena sudah “kadaluarsa”
  • Jika terlalu banyak entri tugas di tingkat halaman, pasti akan mulai berantakan.

Jadi halaman asinkron SwiftUI yang benar-benar stabil adalah:

Pertama, selaraskan siklus hidup halaman dengan siklus hidup tugas, lalu tulis kode asinkron tertentu.

Hanya jika hal ini ditentukan sebelumnya, ringannya SwiftUI dan keanggunan async/await akan benar-benar menjadi keunggulan, daripada membuat kekacauan lebih mudah untuk ditulis.