Outbox Mimarisi: Mesajların dökülmesini önlemek için tasarlandı
Bu sayfa Japonca aslından makine çevirisiyle hazırlanmıştır. Japonca aslını okuyun
13 Şubat 2026
Sorun: Hemen temizleseniz bile mesajların dökülmesini önlemek için
SimpleMemo'de, kullanıcı gönder düğmesine dokunduğu anda kullanıcı arayüzü temizlenir ve bir sonraki notu hemen yazmaya başlayabilirsiniz. Bu, Captio stilinin kökü olan UX'dir.
Ancak, gerçek ağın 300ms ile 2 saniye gecikmesi vardır, başarısız olabilir ve ilk etapta çevrimdışı olabilir. Birçok uygulama, kullanıcı arayüzünü temizlemeden önce ağ yanıtını bekler. Güvenli ama UX kötü. Aksine, hemen temizlerseniz, mesajı kaybetme riski vardır.
Outbox modeli bu çelişkili gereksinimi aynı anda çözer. Mesaj, iletim musluğu anında yerel şifreli depoda (Outbox) kalıcı olarak saklanır, kullanıcı arayüzü hemen temizlenir ve ağ iletimi arka planda gerçekleştirilir. İletim başarılı olursa, Outbox'den silin ve başarısız olursa otomatik olarak yeniden deneyecektir. Sonuç olarak, iletilmemiş mesajı terminalde bırakın ve yeniden gönderin ve deneyim gecikmesi sıfıra yakındır.
Outbox desen tasarım akışı
Aşağıdakiler, gönder düğmesine dokunmaktan mesajı göndermeye kadar olan tüm akıştır.
- Gönder dokunun → İlk olarak, AES-GCM'i Outbox'e şifreleyin ve kalıcı olarak kaydedin
Kullanıcı gönder düğmesine dokunduğu anda, mesaj AES-GCM 256bit ile şifrelenir ve cihazın yerel depolama alanında kalıcı olarak saklanır. Bu adım tamamlanana kadar kullanıcı arayüzü temizlenmeyecektir. Başka bir deyişle, Outbox depolaması dökülmeleri önleyen ilk dalgakıran olacaktır. - Kullanıcı arayüzünü hemen temizleyin (kullanıcı bir sonraki notu yazmaya başlayabilir)
Outbox Kaydetme başarılı olduğunda, metin görünümünü temizleyin ve iletim animasyonunu oynatın. Kullanıcının bakış açısından, "gönderildi" ile eş anlamlıdır. Herhangi bir ağ yanıtı beklemiyorum. Send-to-Reset'in amacı, bekleme hissetmeden kısa olmaktır (animasyon dahil 0.25 saniye). - Arka planda Relay API aracılığıyla bir e-posta gönderin
UI temizlemesine paralel olarak SendManager, Cloudflare Workers'ta Relay API'e bir HTTP isteği gönderir. Bu işlem tamamen asenkrondur ve ana iş parçacığını engellemez. - Başarılıysa, Outbox'den silin
Röle API'den 200 yanıtını aldıktan sonra, Outbox'ten ilgili mesajı güvenli bir şekilde silin. Bu, mesajın yaşam döngüsünü tamamlar. - Başarısız olursa, otomatik olarak yeniden gönderilir
Bir ağ hatası veya sunucu hatası durumunda, hat döndürüldüğünde, uygulama açıldığında, arka plan işleme (BGTaskScheduler. Yürütme zamanlamasına iOS) tarafından karar verilir ve yeniden gönderilir. Otomatik olarak kaç kez yeniden gönderileceği konusunda bir üst sınır vardır. - Çevrimdışıysa, NWPathMonitor hat kurtarmayı algılar ve otomatik olarak yeniden gönderir
'i gönderirken çevrimdışıyken, Network.framework'un NWPathMonitor'u hat durumunu izler ve Wi-Fi veya hücresel'in dönüşünü algıladığında, Outbox'nin gönderilmemiş mesajını otomatik olarak yeniden gönderir (yukarıdaki 5 gibi, otomatik yeniden gönderme sayısında bir üst sınır vardır).
SendManager kodu
SendManager'ın send() işlevi, Outbox modelinin giriş noktasıdır. İlk olarak, Outbox'ye kaydedin, ağ bağlantısı yoksa sıraya girin ve bir bağlantı varsa hemen iletimi gerçekleştirin.
// 保存完了と送信結果を分ける説明用の簡略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, ...)
}
Önemli nokta, OutboxManager.shared.add(body:)'in kalıcı depolamaya eşzamanlı olarak yazmasıdır. Bu çağrı başarılı olduğunda, mesaj terminalde güvenli bir şekilde saklanır. Ağ iletiminin sonucu ne olursa olsun, mesaj Outbox'de kalacaktır.
AES-GCM Şifreleme Uygulaması
Outbox'de saklanan mesaj kullanıcı için özel bir nottur. Yerel depolama olmasına rağmen düz metin olarak saklanamaz. Apple CryptoKit'in AES-GCM'sini kullanarak, şifreleme için herhangi bir harici SDK kullanmayan bir uygulamadır.
- Apple CryptoKit — iOS 13 ve sonraki sürümleri için kullanılabilir. Şifreleme işlemi için harici SDK kullanmayın (yalnızca orijinal Apple).
- 256-bit simetrik anahtar — Anahtarlık'a kaydet.
- Kimliği doğrulanmış şifreleme — AES-GCM, verilerin gizliliğini ve bütünlüğünü aynı anda garanti eder. Kurcalanmış veriler şifre çözme sırasında tespit edilir.
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
}
}
anahtarı ilk kez gerektiğinde otomatik olarak oluşturulur ve Anahtarlıkta saklanır. Diğer uygulamalardan okunamayan ve yedeklemeden başka bir terminale taşınmayan "sadece bu cihaz, ilk kilit açmadan sonra okuyun" (kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) ayarıdır. Yeniden başlattıktan sonra kilidini bir kez açarsanız, kilit sırasında arka plan işleminden de okuyabilirsiniz. Ek olarak, yukarıdaki kod örneği, makalenin yazıldığı sırada basitleştirilmiş bir sürümdür ve mevcut kod, anahtarlık okunamadığında "anahtar yok" olarak ele alarak yeni bir anahtar oluşturmaz.
Veri yolunu yalnızca orijinal Apple ile yapılandırmanın anlamı
Outbox mimarisinde kullanılan tüm çerçeveler Apple'e özgüdür.
- CryptoKit — AES-GCM şifreleme. Üçüncü taraf şifreleme kütüphanesi gerekmez.
- Network.framework - NWPathMonitor ile hat durumu izleme. Ulaşılabilirlik kütüphanesi gerekli değildir.
- BackgroundTasks — BGTaskScheduler tarafından arka plan yeniden deneyin.
Bekleme yolunda harici bir kitaplık olmaması aşağıdaki anlamlara sahiptir.
- Harici güncellemelere bağlı değil — Üçüncü taraf kitaplığın güncellenmesi veya sonlandırılması nedeniyle bekleyen işlem çalışmayı durdurmuyor.
- Güvenlik - Üçüncü taraf kodu, gönderilmeyi bekleyen notun gövdesine dokunan işleme (şifreleme, kaydetme, iletim) girmez.
Ek olarak, tüm uygulama harici bir SDK (Google oturum açma, Firebase, AppsFlyer) kullanır (bkz. ilk gizlilik tasarımı). Burada bahsedilen "yalnızca orijinal Apple", iletilmeyi bekleyen verilerin yolu ile ilgilidir.
UI temizleme ve ağ iletiminin ayrılması
Outbox modelinin gerçek değeri, kullanıcı arayüzü katmanının ve ağ katmanının tamamen ayrılmasında yatmaktadır. Gönder düğmesinin dokunma işleyicisine bir göz atalım.
@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()
}
}
}
Bu basitleştirilmiş örnekte onSaved yalnızca cihaza kaydetme başarılı olduktan sonra çağrılır; ardından animasyon çalıştırılır ve girilen metin temizlenir. Kaydetme başarısız olursa metin korunur. completion, gönderim sonucunu veya kuyruğa eklenmeyi bildiren ayrı bir bildirimdir; arayüzün temizlenmesi ağ yanıtını beklemez.
Send-to-Reset'in amacı bekleyen hissetmeyen kısalık'dır. Animasyon dahil 0.25 saniye. Gönder düğmesine dokunduktan hemen sonra kullanıcı bir sonraki notu yazmaya başlayabilir. Bu Captio tarzı UX'dir.
Sık sorulan sorular
S. Çevrimdışı olarak gönderilen notlara ne olur?
Outbox tarafından şifrelenir ve kaydedilir ve satır geri yüklendiğinde (NWPath Monitor tarafından algılandığında), uygulama açıldığında ve arka plan işlendiğinde otomatik olarak yeniden gönderir. Otomatik olarak yeniden gönderilecek sayıda bir üst sınır vardır ve üst sınıra ulaşan notlar için "Geçmiş"i basılı tutun ve "Yeniden Gönder"i seçin.
S. Uygulamayı kapatsam bile notlar kaybolmaz mı?
Outbox kalıcı bir depolama alanıdır. Uygulamayı kapatsanız veya iPhone'i yeniden başlatsanız bile, kalacaktır. Uygulama bir daha başlatıldığında veya arka plan görevi otomatik olarak yeniden deneyecektir.
S. Neden AES-GCM'i seçtiniz?
Apple CryptoKit yerel olarak desteklenir ve Kimliği Doğrulanmış Şifreleme ile kurcalamayı tespit etmek de mümkündür. Şifrelemeyi işlemek için harici bir kütüphaneye gerek olmaması da bir avantajdır.
S. İletim arızası durumunda yeniden deneme sayısı nedir?
Otomatik olarak tekrar gönderilecektir. Satır döndürüldüğünde, uygulama açıldığında tetikleyici arka plan işlemedir (BGTaskScheduler. Yürütme süresi iOS tarafından belirlenir). Gönderilemeyen notlar, iletimi beklemekten silinmeden tekrar gönderilecektir, ancak otomatik olarak yeniden gönderme sayısında bir sınır vardır.
İlgili makaleler
Kaynaklar
- Apple Geliştirici — AES.GCM (CryptoKit) — Outbox Verileri Şifrelemek için Kullanılan AES-GCM Kimlik Doğrulama Şifrelemesine Resmi Referans
- Apple Geliştirici — NWPathMonitor (Ağ Çerçevesi) — Çevrimdışı algılama ve ağ durumu izleme için kullanılır
- Apple Geliştirici — BGTaskScheduler — Arka planda yeniden gönderme işlemini planlamak için kullanılır
- Chris Richardson — Pattern: Transactional outbox (microservices.io) — İşlemsel Outbox modelinin açıklaması (aynı işlemde bekleme tablosuna gönderilecek mesajı yazın ve başka bir işlem gönderilir)