Topik wawancara representatif

Bagaimana cara Anda bermigrasi ke konkurensi yang mudah dipahami (approachable concurrency) di Swift 6.2?

CodingSulit
Tim Redaksi Offer.ccDipublikasikan Diperbarui

Pertanyaan

Anda mengelola modul pemrosesan gambar yang ditulis untuk Swift 5.10. Tim ingin menjaga UI tetap responsif sambil mengaktifkan pemeriksaan konkurensi ketat secara bertahap di Swift 6.2. Bagaimana Anda menetapkan batasan isolasi, memilih @concurrent, dan membuktikan bahwa migrasi menghindari data race dan serialisasi yang tidak disengaja?

1. Skenario dan batasan

Anda memelihara aplikasi pengeditan gambar. UI mengakses cache sementara pengambilan jaringan, decoding, dan pembuatan thumbnail membutuhkan biaya komputasi yang mahal. Kode lama mengandalkan perpindahan thread implisit, beberapa API bersifat nonisolated async, dan pengujian hanya memeriksa gambar akhir. Anda harus mengadopsi Swift 6.2, mempertahankan scrolling yang mulus dan hasil yang deterministik, serta secara bertahap mengaktifkan pemeriksaan data-race Swift 6.

Mulailah dengan mencatat baseline: frame time pada main-thread, throughput decode, jumlah tugas aktif, hit rate cache, latensi pembatalan, dan diagnostik kompilator sebelum dan sesudah migrasi. Tanpa data ini, regresi performa dan perbaikan kebenaran logika sangat mudah tertukar.

2. Jelaskan perubahan Swift 6.2 terlebih dahulu

Swift 6.2 menawarkan pengaturan opsional -default-isolation MainActor, yang secara default mengisolasi kode dalam suatu target ke main actor. Ini juga menambahkan @concurrent untuk menandai fungsi yang dimaksudkan untuk berjalan secara konkuren. Dengan NonisolatedNonsendingByDefault, fungsi nonisolated async secara default mempertahankan actor pemanggil alih-alih berpindah secara otomatis ke executor konkuren global; gunakan @concurrent jika semantik paralel memang diinginkan.

Opsi-opsi ini mengurangi kerumitan anotasi tetapi tidak membuat setiap operasi berjalan paralel. Isolasi default menjelaskan siapa yang dapat mengakses state dengan aman. @concurrent secara eksplisit menyatakan bahwa pekerjaan dapat meninggalkan actor saat ini dan berjalan secara paralel.

3. Tetapkan batasan isolasi

Pertahankan state cache dan UI yang dapat bermutasi dalam tipe @MainActor. Jadikan transformasi nilai murni dan decoder dengan input dan output yang tidak dapat diubah (immutable) sebagai Sendable. Lapisan unduhan harus mengembalikan nilai alih-alih objek dengan afinitas thread tersembunyi. Untuk setiap API, dokumentasikan actor pemanggil, state yang dilindungi, dan apakah eksekusi paralel diizinkan.

Sebagai contoh, pertahankan perlindungan cache pada main actor:

swift
@MainActor
final class ImageCache {
    private var values: [URL: Image] = [:]

    func value(for url: URL) -> Image? { values[url] }
    func insert(_ image: Image, for url: URL) { values[url] = image }
}

Jangan menyebarkan MainActor.run di sekitar kode lama hanya untuk membungkam peringatan. Panduan migrasi memperlakukannya sebagai alat transisi; API jangka panjang harus mengekspresikan isolasi dalam tipe dan signature-nya.

4. Tentukan kapan harus menggunakan @concurrent

Tambahkan @concurrent hanya jika pekerjaan aman dijalankan secara paralel, input dan output-nya memenuhi persyaratan pengiriman (sending requirements), dan baseline menunjukkan adanya peningkatan. Decoding gambar sering kali memenuhi syarat: proses ini tidak menyentuh cache main-actor secara langsung dan mengembalikan nilai yang baru dibuat.

swift
@MainActor
struct ImagePipeline {
    static func image(from url: URL) async throws -> Image {
        let cached = cache.value(for: url)
        if let cached { return cached }

        let image = try await decode(url: url)
        cache.insert(image, for: url)
        return image
    }

    @concurrent
    private static func decode(url: URL) async throws -> Image {
        let (data, _) = try await URLSession.shared.data(from: url)
        return try ImageDecoder.decode(data)
    }
}

Tunjukkan bahwa @concurrent bukanlah sakelar pengatur kecepatan. Penggunaan berlebihan menambah biaya penjadwalan, memori, dan sinkronisasi; jika decoder secara internal bersifat serial atau gambarnya sangat kecil, throughput dapat menurun.

5. Tangani konteks pemanggil dan Sendable

Dengan semantik konteks pemanggil yang diaktifkan, fungsi async dapat berlanjut pada actor pemanggil. Ini membantu ketika membutuhkan state yang terisolasi pada pemanggil, tetapi pekerjaan CPU yang panjang dapat memblokir actor tersebut, jadi pisahkan tahap murni @concurrent. Nilai yang melintasi actor harus berupa Sendable atau dilindungi oleh actor, lock, atau batasan sinkronisasi yang setara.

Jangan membungkam setiap diagnostik dengan @unchecked Sendable. Gunakan hanya sebagai upaya terakhir yang terbatas setelah membuktikan invarian, primitif sinkronisasi, dan masa pakai (lifetime), serta pasangkan dengan uji beban konkurensi (concurrency stress tests).

6. Migrasi secara bertahap dengan fitur perkakas

Aktifkan fitur-fitur yang akan datang dan diagnostik ketat dalam satu modul terlebih dahulu, menggunakan fitur perkakas migrasi untuk peringatan dan fix-it. Stabilkan batasan isolasi dan Sendable dalam API publik, lalu alihkan modul tersebut ke mode bahasa Swift 6. Pastikan setiap fase dapat dibatalkan (revertible) dan arsipkan diagnostik berdasarkan modul.

Urutan praktisnya adalah tipe nilai dan batasan protokol, layanan tingkat rendah, actor cache, dan terakhir lokasi panggilan UI. Kesalahan kemudian akan muncul di batas-batas isolasi alih-alih di ratusan ekspresi await yang diubah. Selama proses kompatibilitas, pertahankan antarmuka mode Swift 5 untuk klien yang ada sambil meningkatkan tingkat pemeriksaan di dalam modul implementasi.

7. Buktikan tidak adanya race condition dan serialisasi yang tidak disengaja

Selain pengujian fungsional, tambahkan uji beban konkurensi: permintaan simultan untuk satu URL, pembatalan acak, penulisan berulang, dan pembacaan lintas-actor. Gunakan Thread Sanitizer dan diagnostik konkurensi yang ketat untuk mendeteksi race condition. Lakukan benchmark pada cache dingin dan hangat, ukuran gambar, dan batas konkurensi, dengan membandingkan frame time, throughput, memori puncak, dan latensi pembatalan.

Periksa konteks tugas dan tumpukan asinkron untuk memastikan decoding benar-benar berjalan secara konkuren sementara akses cache tetap berada di main actor. Jika @concurrent tidak meningkatkan frame time, periksa apakah ada decoder yang memblokir, satu lock yang menserialisasi pekerjaan, atau terlalu banyak tugas kecil.

8. Rubrik dan pertanyaan lanjutan

Wajib dijelaskan

  • Bedakan isolasi default, konteks pemanggil, dan paralelisme eksplisit; async tidak berarti thread latar belakang.
  • Buktikan keamanan dengan Sendable, batasan actor, dan pembatalan alih-alih menyembunyikan kesalahan dengan @unchecked Sendable.
  • Sediakan migrasi bertahap, uji beban, dan baseline performa dengan penjelasan mengenai sinyal regresi.

Pertanyaan lanjutan

  • Jika decoder tidak thread-safe, apakah Anda akan menempatkannya di belakang actor, serial executor, atau layanan di luar proses (out-of-process service), dan mengapa?
  • Bagaimana Anda menggabungkan (coalesce) unduhan duplikat untuk satu URL tanpa membuat seluruh cache menjadi serial?
  • Bagaimana modul Swift 5 dan Swift 6 dapat berbagi API selama migrasi, dan batasan mana yang harus dirilis terlebih dahulu?

Panduan penilaian

Jawaban yang sangat baik menghubungkan pemodelan isolasi, fitur perkakas migrasi, dan bukti runtime: tentukan state dan konteks eksekusi, gunakan bidang @concurrent sekecil mungkin yang diperlukan untuk paralelisme, lalu validasi hasilnya dengan diagnostik kompilator, Sanitizer, dan data benchmark.

Sumber publik

Pertanyaan terkait

Alat wawancara terkait

Gunakan Tangkapan Layar untuk perintah coding

Ambil tangkapan layar soal, lalu telusuri batasan, solusi, kode, edge case, dan kompleksitas secara berurutan.

Lihat alat