Arsitektur Outbox: Dirancang agar pesan tidak tumpah
Halaman ini diterjemahkan secara otomatis dari bahasa Jepang. Baca versi asli berbahasa Jepang
13 Februari 2026
Masalah: Untuk menghindari tumpahan pesan bahkan jika Anda segera menghapus transmisi
Di SimpleMemo, UI dihapus saat pengguna mengetuk tombol kirim dan dapat segera mulai menulis catatan berikutnya. Ini adalah UX yang merupakan akar dari gaya Captio.
Namun, jaringan asli memiliki latensi 300ms~2 detik, mungkin gagal, dan mungkin offline sejak awal. Banyak aplikasi menunggu respons jaringan sebelum menghapus UI. Itu aman, tetapi UX buruk. Sebaliknya, jika Anda segera membersihkannya, ada risiko kehilangan pesannya.
Pola Outbox menyelesaikan persyaratan yang bertentangan ini pada saat yang bersamaan. Pesan disimpan secara permanen di penyimpanan terenkripsi lokal (Outbox) pada saat ketukan transmisi, UI segera dihapus, dan transmisi jaringan dilakukan di latar belakang. Jika transmisi berhasil, hapus dari Outbox, dan jika gagal, secara otomatis akan mencoba lagi. Akibatnya, meninggalkan pesan yang tidak terkirim di terminal dan mengirimkannya kembali, dan latensi pengalaman mendekati nol.
Alur desain pola Outbox
Berikut ini adalah keseluruhan alur dari mengetuk tombol kirim hingga mengirim pesan.
- Kirim ketukan → Pertama, enkripsi AES-GCM ke Outbox dan simpan secara permanen
Saat pengguna mengetuk tombol kirim, pesan dienkripsi dengan AES-GCM 256bit dan disimpan secara permanen di penyimpanan lokal perangkat. UI tidak akan dihapus sampai langkah ini selesai. Dengan kata lain, penyimpanan Outbox akan menjadi pemecah gelombang pertama untuk mencegah tumpahan. - Segera hapus UI (pengguna dapat mulai menulis catatan berikutnya)
Outbox Saat penyimpanan berhasil, hapus tampilan teks dan putar animasi transmisi. Dari sudut pandang pengguna, itu identik dengan "terkirim". Saya tidak menunggu respons jaringan apa pun. Tujuan dari Send-to-Reset adalah menjadi singkat tanpa merasa menunggu (0.25 detik termasuk animasi). - Kirim email melalui Relay API di latar belakang
UI Secara paralel dengan kliring, SendManager mengirimkan permintaan HTTP ke Relay API pada Pekerja Cloudflare. Proses ini sepenuhnya asinkron dan tidak memblokir utas utama. - Jika berhasil, hapus dari Outbox
Relay Setelah menerima respons 200 dari API, hapus pesan yang sesuai dari Outbox dengan aman. Ini melengkapi siklus hidup pesan. - Jika gagal, itu akan secara otomatis mengirim ulangDalam kasus kesalahan jaringan
atau kesalahan server, biarkan menunggu transmisi, ketika saluran kembali, ketika aplikasi dibuka, pemrosesan latar belakang (BGTaskScheduler. Waktu eksekusi ditentukan oleh iOS) dan mengirimkannya kembali. Ada batas atas pada berapa kali untuk mengirim ulang secara otomatis. - Saat offline, NWPathMonitor mendeteksi pemulihan saluran dan secara otomatis mengirim ulangSaat offline pada saat mengirim
, NWPathMonitor dari Network.framework memantau status saluran, dan ketika Wi-Fi dan pemulihan seluler terdeteksi, pesan yang tidak terkirim dari Outbox secara otomatis dikirim kembali (seperti 5 di atas, ada batas atas untuk jumlah pembatalan otomatis).
Kode SendManager
Fungsi send() dari SendManager adalah titik masuk untuk pola Outbox. Pertama, simpan ke Outbox, antrian jika tidak ada koneksi jaringan, dan segera jalankan transmisi jika ada koneksi.
// 保存完了と送信結果を分ける説明用の簡略API
@MainActor
func send(message: String, onSaved: () -> Void,
completion: @escaping (SendResult) -> Void) {
// まずOutboxに保存(クラッシュや圏外で失わないように)
let outboxMessage: OutboxMessage
do {
outboxMessage = try OutboxManager.shared.add(body: message)
} catch {
DispatchQueue.main.async { completion(.failure(error)) }
return
}
// 同期保存に成功した場合だけ、メインスレッドでUIへ通知する
onSaved()
// ネットワーク接続がない場合はキューに入れたことを通知
guard NetworkMonitor.shared.isConnected else {
BackgroundTaskManager.shared.scheduleRetryTask()
DispatchQueue.main.async { completion(.queued) }
return
}
// 送信処理を実行
performSend(message: message, outboxId: outboxMessage.id, ...)
}
Poin penting adalah OutboxManager.shared.add(body:) menulis ke penyimpanan persisten secara sinkron. Ketika panggilan ini berhasil, pesan disimpan dengan aman di terminal. Apa pun hasil transmisi jaringannya, pesannya akan tetap ada di Outbox.
Implementasi enkripsi AES-GCM
Pesan yang disimpan di Outbox adalah memo pribadi pengguna. Meskipun itu adalah penyimpanan lokal, itu tidak dapat disimpan dalam teks biasa. Menggunakan Apple CryptoKit's AES-GCM, ini adalah sebuah implementasi yang tidak menggunakan SDK eksternal untuk enkripsi.
- Apple CryptoKit — Tersedia untuk iOS 13 atau yang lebih baru. Jangan gunakan SDK eksternal untuk pemrosesan enkripsi (hanya Apple asli).
- Kunci simetris 256 bit — disimpan di Keychain.
- Enkripsi dengan otentikasi — AES-GCM menjamin kerahasiaan dan integritas data secara bersamaan. Data yang dirusak terdeteksi pada saat dekripsi.
enum OutboxEncryption {
private static var encryptionKey: SymmetricKey {
if let existingKey = KeychainHelper.load(key: "outbox_encryption_key") {
return SymmetricKey(data: existingKey)
}
let newKey = SymmetricKey(size: .bits256)
let keyData = newKey.withUnsafeBytes { Data($0) }
KeychainHelper.save(key: "outbox_encryption_key", data: keyData)
return newKey
}
static func encrypt(_ plainText: String) throws -> Data {
guard let data = plainText.data(using: .utf8) else {
throw OutboxError.encryptionFailed
}
let sealedBox = try AES.GCM.seal(data, using: encryptionKey)
guard let combined = sealedBox.combined else {
throw OutboxError.encryptionFailed
}
return combined
}
}
Kunci dibuat otomatis saat pertama kali dibutuhkan dan disimpan di Keychain. Pengaturan penyimpanannya adalah “hanya perangkat ini, dapat dibaca setelah pertama kali dibuka kuncinya” (kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly). Aplikasi lain tidak dapat membacanya, dan pemulihan cadangan tidak memindahkannya ke perangkat lain. Setelah perangkat dibuka kuncinya sekali sesudah dimulai ulang, proses latar belakang dapat membacanya meskipun perangkat kembali terkunci. Contoh kode di atas merupakan versi sederhana saat artikel ditulis. Dalam kode saat ini, kegagalan membaca Keychain tidak dianggap sebagai ketiadaan kunci dan tidak menyebabkan pembuatan kunci baru.
Arti dari mengonfigurasi jalur data hanya dengan Apple asli
Semua kerangka kerja yang digunakan dalam arsitektur Outbox berasal dari Apple.
- CryptoKit — Enkripsi AES-GCM. Tidak diperlukan perpustakaan enkripsi pihak ketiga.
- Network.framework - Pemantauan status jalur dengan NWPathMonitor. Perpustakaan Reachability tidak diperlukan.
- BackgroundTasks — Latar belakang coba lagi oleh BGTaskScheduler.
Fakta bahwa tidak ada perpustakaan eksternal di jalur tunggu memiliki arti sebagai berikut.
- Tidak terpengaruh oleh pembaruan eksternal — Proses menunggu transmisi tidak berhenti bekerja karena pembaruan atau penghentian perpustakaan pihak ketiga.
- Keamanan - Kode pihak ketiga tidak memasukkan pemrosesan (enkripsi, penyimpanan, transmisi) yang menyentuh isi memo yang menunggu untuk dikirim.
Selain itu, seluruh aplikasi juga menggunakan SDK eksternal (Google sign-in, Firebase, AppsFlyer) (lihat Desain Privasi Pertama). "Apple hanya asli" yang disebutkan di sini adalah tentang jalur data yang menunggu untuk dikirimkan.
Pemisahan pembersihan UI dan transmisi jaringan
Nilai sebenarnya dari pola Outbox terletak pada pemisahan lengkap lapisan UI dan lapisan jaringan. Mari kita lihat pegangan ketuk tombol kirim.
@objc private func sendButtonTapped() {
PerformanceLogger.shared.beginSendToReset()
let message = textView.text ?? ""
isSending = true
sendBarButton.isEnabled = false
SendManager.shared.send(message: message, onSaved: { [weak self] in
// 端末への保存成功後にだけ消す。ネットワーク応答は待たない。
self?.performSendAnimation { [weak self] in
guard let self = self else { return }
// アニメーション中に編集された入力は残す。
if self.textView.text == message {
self.clearTextView()
}
PerformanceLogger.shared.endSendToReset()
}
}) { [weak self] result in
self?.isSending = false
self?.updateSendButtonState()
switch result {
case .success:
PerformanceLogger.shared.logEvent(name: "SendSuccess")
case .failure(let error):
self?.handleSendError(error)
case .queued:
self?.showQueuedFeedback()
}
}
}
Dalam contoh sederhana ini, onSaved hanya dipanggil setelah penyimpanan di perangkat berhasil; setelah itu animasi dijalankan dan teks masukan dihapus. Jika penyimpanan gagal, teks tetap dipertahankan. completion adalah pemberitahuan terpisah tentang hasil pengiriman atau penambahan ke antrean; pengosongan UI tidak menunggu respons jaringan.
Tujuan dari Send-to-Reset adalah kependekan yang tidak terasa menunggu. 0.25 detik termasuk animasi. Segera setelah mengetuk tombol kirim, pengguna dapat mulai menulis catatan berikutnya. Ini adalah UX gaya Captio.
Pertanyaan umum
Q. Apa yang terjadi pada catatan yang dikirim secara offline?
Ini dienkripsi dan disimpan di Outbox, dan secara otomatis mengirim ulang ketika saluran dipulihkan (terdeteksi oleh NWPathMonitor), ketika aplikasi dibuka, dan pemrosesan latar belakang. Ada batas atas pada jumlah waktu untuk mengirim ulang secara otomatis, dan untuk catatan yang telah mencapai batas atas, tekan dan tahan di "Riwayat" dan pilih "Kirim Ulang".
Q. Apakah catatan tidak akan hilang bahkan jika saya menutup aplikasi?
Outbox adalah penyimpanan persisten. Bahkan jika Anda menutup aplikasi atau memulai ulang iPhone, itu akan tetap ada. Lain kali aplikasi diluncurkan, atau tugas latar belakang akan secara otomatis mencoba lagi.
Q. Mengapa Anda memilih AES-GCM?
Apple CryptoKit didukung secara asli, dan deteksi gangguan juga dimungkinkan dengan Enkripsi yang Diautentikasi. Fakta bahwa tidak perlu perpustakaan eksternal untuk memproses enkripsi juga merupakan keuntungan.
Q. Berapa jumlah perlukaan ulang jika terjadi kegagalan transmisi?
Ini akan dikirim lagi secara otomatis. Pemicunya adalah pemrosesan latar belakang ketika baris dikembalikan, ketika aplikasi dibuka (BGTaskScheduler. Waktu eksekusi ditentukan oleh iOS). Memo yang tidak dapat dikirim akan dikirim lagi tanpa menghapusnya dari menunggu pengiriman, tetapi ada batasan berapa kali pengiriman ulang otomatis.
Artikel terkait
Referensi
- Pengembang Apple — AES.GCM (CryptoKit) — Referensi Resmi Outbox untuk Enkripsi Otentikasi AES-GCM Digunakan untuk Enkripsi Data
- Pengembang Apple — NWPathMonitor (Kerangka Kerja Jaringan) — Digunakan untuk deteksi offline dan pemantauan status jaringan
- Pengembang Apple — BGTaskScheduler — Digunakan untuk mengirim ulang penjadwalan pemrosesan di latar belakang
- Chris Richardson — Pola: Kotak keluar transaksional (microservices.io) — Penjelasan pola transaksional Outbox (tulis pesan yang Anda kirim dalam proses yang sama di meja tunggu, dan proses lain mengirimkannya)