---
title: 'Veri, Ağ ve Eşzamanlılık'
source: 'https://academia.sh/tr/kurslar/ios-veri-ve-ag'
language: tr
updated: '2026-10-21T05:03:08+00:00'
license: 'CC BY-SA 4.0'
lessons: 19
outcomes:
    - 'Ağ isteklerini eşzamansız olarak ve hata yönetimiyle kurar'
    - 'Yerel kalıcılık seçeneklerini veri modeline göre seçer'
    - 'Eşzamanlılık modelini yarış koşullarından kaçınarak kullanır'
    - 'Bir işlemin sağlam mı, yinelemeye mi, sıraya mı yoksa ikisine birden mi duyarlı olduğunu ayırt eder'
    - 'Ağ ve disk işlemlerini küçük bir modelle kurar ve modelin ağın ya da diskin kendisi olmadığını tanır'
---

# Veri, Ağ ve Eşzamanlılık

Ağ isteği yeniden denenir, kullanıcı düğmeye iki kez basar, iki görev aynı durumu farklı sırayla günceller, eşitleme iki aygıtın yazdığını farklı sırayla getirir. Kurs her düzeneği tek bir soruyla ölçer — bu işlem yinelenirse ya da sırası değişirse sonuç değişir mi? İşlem sağlam mı, yinelemeye mi duyarlı, sıraya mı, yoksa ikisine birden mi? Ağ istemcisi ve JSON dönüşümü, hata ve yeniden deneme, kimlik doğrulama ve önbellek, eşzamansız fonksiyonlar, görevler ve aktörler, ana iş parçacığı yalıtımı, kuyruklar ve tepkisel akışlar, tercih, anahtarlık, dosya ve veritabanı saklama, bulut eşitleme ve uzak bildirimler aynı çerçeveden geçirilir.

## Ağ Katmanı

Oturum yapılandırması ve görev türleri, tip güvenli JSON dönüşümü, ağ hatalarının sınıflandırılması ve yeniden deneme, belirteç yönetimi, bellek ve disk önbelleği ile uygulama arka plandayken aktarım.

### Ağ İstemcisi

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/ag-istemcisi

Oturum yapılandırması ve görev türlerinin anlatımı; ağ katmanının arkasındaki altı işlemin yinelenirse ya da sırası değişirse sonucunun değişip değişmediğinin dört kategoride ölçümü.

Önceki kursta (M30/K04) bir ekranın gösterdiği değeri modelin değeriyle karşılaştırdık ve ekranın bayat kalıp kalmadığına baktık. Bu kurs ekranın arkasına iner: değerin geldiği, saklandığı ve işlendiği katmana. Bu katmanda soru değişir. Ekran değeri gösterir ya da göstermez. Ağ katmanındaki bir işlem ise başka bir şeyle sınanır: aynı işlem iki kez uygulanırsa, ya da iki işlemin sırası değişirse sonuç farklı çıkar mı?

Bu sorunun ağda sık sorulması boşuna değil. İstek yolda kaybolabilir, yanıt geç gelebilir, kullanıcı aynı düğmeye iki kez basabilir, iki yanıt ters sırada ulaşabilir. Hiçbiri hata vermez. Yalnız durum sessizce başka bir değere döner. Bu derste önce ağ istemcisinin yapı taşlarını tanıyacağız: oturum, yapılandırma ve görev. Ardından altı işlemi tek bir çekirdekle ölçeceğiz.

Ölçü ekseni **duyarlılıktır**. Bir işlemi bir kez ve iki kez uygularız; sonuçlar farklıysa işlem **yinelemeye duyarlıdır**. İki işlemi iki ayrı sırayla uygularız; sonuçlar farklıysa işlem **sıraya duyarlıdır**. İkisi birden varsa **çift duyarlı**, hiçbiri yoksa **sağlam** denir. Çıktıda dört ad ASCII yazılır: `saglam`, `yinelemeye-duyarli`, `siraya-duyarli`, `cift-duyarli`.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

`Trial` dört durumu tutar: `once` işlemin bir kez, `twice` iki kez uygulanmasından çıkan durumdur. `forward` önce `a`, sonra `b`; `backward` önce `b`, sonra `a` uygulanmasından çıkar. `sensitivity` yalnız iki eşitsizliğe bakar. `replay` bu dört durumu bir başlangıç durumundan üretir. **Kural nedeni görmez, eşitsizliği görür:** iki durumun neden farklı olduğunu söylemez, yalnız farklı olduklarını söyler. Hangi durumun karşılaştırıldığını ders söyler.

#### Oturum ve Yapılandırma

Bir ağ isteği üç ayrı nesne ister. **Yapılandırma** (configuration) oturumun nasıl davranacağını söyler: zaman aşımı, önbellek, ek başlıklar, ağ yoksa beklenip beklenmeyeceği. **Oturum** (session) bu yapılandırmayla çalışan istemcidir; aynı oturumdan çıkan istekler aynı kuralları paylaşır. **Görev** (task) tek bir aktarımdır: bir isteği alır, bir yanıt ya da hata döndürür.

`URLSessionConfiguration.default` kalıcı bir önbellek ve çerez deposu kullanır. `.ephemeral` bunların hiçbirini diske yazmaz; oturum kapanınca iz kalmaz. Üçüncü yapılandırma arka plan oturumudur ve bu konunun son dersinde işlenir.

```swift
import Foundation

let configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForRequest = 15
configuration.waitsForConnectivity = true
configuration.httpAdditionalHeaders = ["Accept": "application/json"]
let session = URLSession(configuration: configuration)
```

Yapılandırma oturum kurulurken okunur. Oturum kurulduktan sonra yapılandırmayı değiştirmek oturumu etkilemez; değişiklik için yeni bir oturum kurulur. Bu yüzden uygulamalar çoğunlukla az sayıda oturum kurar ve onları her yerde paylaşır.

#### Görev Türleri

Üç görev türü işi ayırır. **Veri görevi** (data task) yanıtı belleğe alır; küçük yanıtlar için uygundur. **Yükleme görevi** (upload task) isteğin gövdesini ayrı bir veri ya da dosya olarak gönderir. **İndirme görevi** (download task) yanıtı doğrudan bir dosyaya yazar; büyük gövdeler belleği şişirmesin diye kullanılır. Eşzamansız biçimde ilk ikisi `data(for:)` ve `upload(for:from:)` çağrılarıdır; ikisi de bir `(Data, URLResponse)` çifti döndürür.

```swift
// profil istegi — KAYNAK
import Foundation

func fetchProfile(session: URLSession) async throws -> Data {
    let request = URLRequest(url: URL(string: "https://api.example.com/profile")!)
    let (data, response) = try await session.data(for: request)
    guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }
    return data
}
```

Bu parça çalıştırılmaz; ağa çıkar. `data(for:)` bir **eşzamansız fonksiyondur** (asynchronous function): çağrı yanıt gelene kadar **duraklama noktasında** bekler ve akış sonra kaldığı yerden sürer. Yanıtın gerçek bir yanıt olup olmadığı ayrıca denetlenir; durum kodu `HTTPURLResponse` üzerinden okunur.

```swift
import Foundation

func send(order: Data, session: URLSession) async throws -> Int {
    var request = URLRequest(url: URL(string: "https://api.example.com/orders")!)
    request.httpMethod = "POST"
    let (_, response) = try await session.upload(for: request, from: order)
    return (response as? HTTPURLResponse)?.statusCode ?? 0
}
```

Yükleme görevinde gövde isteğin içine değil `from:` parametresine verilir. Bu parça da KAYNAK kalır. Yeni bir sipariş yaratan bir `POST` isteğinin yinelenince ne olduğu bu dersin altı işleminden biridir.

#### Altı İşlem

Aşağıdaki **model**, her işlemin duruma ne yaptığını kurar; ağın ya da diskin kendisi değildir. Altı işlemin her biri ağ katmanının bildik bir işine karşılık gelir: bir önbelleğe yazma, etkisizlik anahtarlı bir gönderim, yeniden denenen bir sipariş, bir rozet sayısını artırma, bir tercihi yazma, bir günlüğe satır ekleme. Çekirdeğin `replay` işleviyle her biri bir kez, iki kez ve iki sırada uygulanır.

```swift
// main.swift — alti islem, dort duyarlilik
func cached(_ key: String) -> ([String]) -> [String] {
    { keys in Array(Set(keys + [key])).sorted() }
}

func submit(_ key: String) -> ([String]) -> [String] {
    { received in received.contains(key) ? received : received + [key] }
}

func placeOrder(_ count: Int) -> Int { count + 1 }

func badge(_ count: Int) -> Int { count + 1 }

func theme(_ value: String) -> (String) -> String {
    { _ in value }
}

func log(_ line: String) -> ([String]) -> [String] {
    { lines in lines + [line] }
}

let cacheWrite = replay("onbellege-yaz", from: [String](), cached("profil"), then: cached("akis")).report()
let keyedSend = replay("anahtarli-gonderim", from: [String](), submit("k-41"), then: submit("k-42")).report()
let retry = replay("yeniden-dene", from: 0, placeOrder, then: placeOrder).report()
let push = replay("rozet-artir", from: 3, badge, then: badge).report()
let preference = replay("tercih-yaz", from: "sistem", theme("koyu"), then: theme("acik")).report()
let journal = replay("gunluge-ekle", from: [String](), log("giris"), then: log("cikis")).report()

countSensitivities([cacheWrite, keyedSend, retry, push, preference, journal])
```

```
onbellege-yaz | bir=["profil"] iki=["profil"] ab=["akis", "profil"] ba=["akis", "profil"] -> saglam
anahtarli-gonderim | bir=["k-41"] iki=["k-41"] ab=["k-41", "k-42"] ba=["k-42", "k-41"] -> siraya-duyarli
yeniden-dene | bir=1 iki=2 ab=2 ba=2 -> yinelemeye-duyarli
rozet-artir | bir=4 iki=5 ab=5 ba=5 -> yinelemeye-duyarli
tercih-yaz | bir=koyu iki=koyu ab=acik ba=koyu -> siraya-duyarli
gunluge-ekle | bir=["giris"] iki=["giris", "giris"] ab=["giris", "cikis"] ba=["cikis", "giris"] -> cift-duyarli
saglam: 1
yinelemeye-duyarli: 2
siraya-duyarli: 2
cift-duyarli: 1
```

`onbellege-yaz` sağlam: aynı anahtarı iki kez yazmak anahtar kümesini değiştirmez ve iki anahtarın sırası sonucu değiştirmez, çünkü küme sıralı basılır. `anahtarli-gonderim` sıraya duyarlı: etkisizlik anahtarı aynı isteğin ikinci gelişini yok sayar, bu yüzden `bir` ile `iki` aynıdır; ama `k-41` ile `k-42` farklı isteklerdir ve varış sırası listenin sırasını belirler. `yeniden-dene` yinelemeye duyarlı: sayaç her uygulamada artar. `rozet-artir` de yinelemeye duyarlı, aynı nedenle. `tercih-yaz` sıraya duyarlı: son yazan kazanır. `gunluge-ekle` ikisine birden duyarlı: satır iki kez eklenir ve iki satırın sırası değişir.

**AG1.** Bu ölçüm altı işlemin her birini tek bir başlangıç durumundan kurar. Başka bir başlangıç durumunda bir işlemin sınıfı değişebilir.

**AG2.** `onbellege-yaz` anahtarları sıralı küme olarak tutar. Eklenme sırasını koruyan bir önbellek ayrıca ölçülmelidir.

Dağılım bir bulgu değildir. Bir sağlam, iki yinelemeye duyarlı, iki sıraya duyarlı ve bir çift duyarlı işlem çıkması işlemleri biz seçtiğimiz için çıktı. Başka altı işlemle başka bir dağılım çıkardı.

#### Etkisizlik Neyi Çözer

`anahtarli-gonderim` bu dersin en öğretici satırıdır, çünkü yinelemeyi çözen şeyin sırayı çözmediğini gösterir. HTTP'de aynı isteği art arda göndermenin sonucu değiştirmediği yöntemlere **etkisiz** (idempotent) denir; `GET` ve `PUT` böyledir, `POST` değildir. Sunucu bir isteğe kimlik veren bir **etkisizlik anahtarı** kabul ederse, yeni bir sipariş yaratan `POST` da yinelemeye dayanıklı olabilir: aynı anahtarla gelen ikinci istek yok sayılır.

Ölçümde bu tam böyle çıktı. `bir` ile `iki` aynı: yineleme çözüldü. Ama iki farklı anahtar olduğunda sıra hâlâ sonucu belirliyor: `ab` ile `ba` aynı değil. **Etkisizlik yinelemeyi çözer, sırayı çözmez.** Sırayı çözmek için başka bir araç gerekir: sürüm numarası, birleştirilebilir bir veri yapısı ya da sunucuda sıralama. Sonraki derslerde bunların bir kısmını ölçeceğiz.

#### Gerçek Uygulamada Nerede Görülür

Ölçülen sıra duyarlılığı yapay bir laboratuvar durumu değildir. Bir ekran açılırken profil isteği gönderir; kullanıcı aşağı çekip yenilediğinde ikinci bir istek daha çıkar. İki yanıt ağdan ters sırayla dönerse ekrana en son gelen yazılır ve bu, eski isteğin yanıtı olabilir. Sonuç, kullanıcının az önce yaptığı bir değişikliğin ekranda sessizce geri alınmış görünmesidir. Hata kaydında bunun izi yoktur: hiçbir istek başarısız olmamıştır, her yanıt kendi başına doğrudur.

Yeniden deneme ise yineleme duyarlılığının tipik örneğidir. Sipariş gönderen istek zaman aşımına uğradığında istemci yanıtın gelmediğini görür; ama sunucu isteği almış ve siparişi kaydetmiş olabilir. İstemci aynı isteği yeniden gönderirse sunucuda ikinci bir sipariş oluşur. Ölçümün `iki` sütunu tam olarak bu durumu gösterir: `yeniden-dene` satırında işlem yinelendiğinde sayı bir daha artmıştır.

Başlangıç durumu meselesi daha da sinsidir. Aynı işlem, boş bir önbellekle açılan bir uygulamada ve önceki oturumdan dolu kalmış bir önbellekle açılan bir uygulamada farklı davranabilir. Aşağıdaki sınırlayıcı ölçüm bunu gösterir: aynı işlem çifti, başlangıç durumuna göre farklı sınıfa düşer. Bu yüzden bir isteğin yinelemeye dayanıklı olup olmadığı yalnız isteğe bakılarak söylenemez; işlemin hangi durumdan koştuğu da sorulur.

#### Yanlış Kurulum ve Düzeltmesi

En yaygın yanlış kurulum, yeniden denemeyi isteğin türüne bakmadan her başarısız isteğe uygulamaktır. Profili `GET` ile okumak yinelenince bir şey bozmaz; ama aynı kural sipariş yaratan bir `POST` isteğine de uygulanırsa her zaman aşımı ikinci bir sipariş riski taşır.

Düzeltme iki adımdır. Birincisi, yinelemeye duyarlı isteğe bir etkisizlik anahtarı eklemektir: istemci anahtarı ilk denemede üretir ve her yeniden denemede aynısını gönderir, sunucu da aynı anahtarla gelen ikinci isteği yok sayar. Ölçümdeki `anahtarli-gonderim` satırında `bir` ile `iki` sütunlarının aynı çıkması bunun karşılığıdır. İkincisi, sıraya duyarlı yanıtlarda ekranın yalnız en son gönderilen isteğin yanıtını kabul etmesidir; eski bir isteğin geç gelen yanıtı yok sayılır. Bu, sıra sorununu ortadan kaldırmaz ama sırayı tek bir yerde görünür kılar; sıraya duyarlı bir işlemin güvenli hâle gelmesinin yolu çoğu zaman sıranın artık değişememesidir.

Rozet için düzeltme farklıdır. Sayıyı yerelde artırmak yerine sunucunun bildirdiği mutlak değeri yazmak, işlemi yinelemeye karşı korur: aynı değer ikinci kez yazılsa da sonuç değişmez. Mutlak değerin de kendi sorusu vardır, iki değer ters sırayla gelirse eskisi kalır; bunu bu derste ayrıca ölçmedik, yalnız yönünü söylüyoruz.

#### KAYNAK Parçayı Okumak

Dersteki iki KAYNAK parça çalıştırılmaz, çünkü ikisi de ağa çıkar ve sonuçları sunucuya bağlıdır. Parçalar yine de okunmaya değerdir. `fetchProfile` bir okumadır: aynı isteği iki kez göndermek sunucudaki durumu değiştirmez ve modeldeki karşılığı `onbellege-yaz` satırıdır. `send(order:session:)` ise yeni bir kayıt yaratan bir `POST` isteğidir ve modeldeki `placeOrder` işlevine denk düşer. Model ile gerçek arasındaki fark, gerçek istekte yinelemenin istemcinin bilgisi dışında da olabilmesidir: bağlantı koptuğunda istemci isteğin sunucuya ulaşıp ulaşmadığını bilemez. Bu fark ölçülen sınıfı değiştirmez; yalnız yinelemenin nereden çıkabileceğini söyler.

Parçalardaki durum kodu denetimi de önemlidir. `fetchProfile` yalnız 200'ü kabul eder; `send(order:session:)` ise durum kodunu olduğu gibi döndürür ve kararı çağırana bırakır. Zaman aşımında hiç yanıt gelmez ve istemci isteğin işlenip işlenmediğini bilmez. Bu durumu sunucunun açıkça reddettiği bir istekle aynı hata saymak, yeniden denemenin yinelemeye duyarlı bir isteği ikinci kez göndermesine yol açar.

#### Sık Yapılan Hatalar

- Her istek için yeni bir oturum kurmak. Yapılandırma ve bağlantılar paylaşılmaz.
- Yanıtın `HTTPURLResponse` olup olmadığını ve durum kodunu denetlememek. Sunucu hata kodu döndürse de çağrı hata fırlatmaz.
- Büyük bir gövdeyi veri görevi ile belleğe almak. İndirme görevi dosyaya yazar.
- Yinelenen bir kullanıcı eylemini ağ katmanında yok saymak. Düğmeye iki kez basmak iki istek doğurur; yineleme sorusu istemcide ve sunucuda ayrı ayrı cevaplanır.
- Etkisizlik anahtarını sıra sorununu da çözdüğünü varsayarak kullanmak.

#### Ölçülen Sonucun Yorumu

Altı işlemin altısı da bir ağ katmanında karşımıza çıkabilecek işlerdi ve dört kategoriye dağıldı. Önemli olan dağılım değil, bir işlemin sınıfının iki sorudan çıktığıdır: yinelenirse ne olur, sırası değişirse ne olur. Bu iki sorunun cevabı bir işlemden öbürüne değişir ve çoğu zaman işlemin adından çıkarılamaz. `placeOrder` ile `badge` aynı gövdeye sahip olsa da biri bir sipariş, öbürü bir sayaçtır; kullanıcı için sonuçları çok farklıdır, kural için aynıdır.

Ağ katmanında bu ayrım üç yerde işe yarar. Yeniden denemede: yinelemeye duyarlı bir işlem denenince ikinci kayıt doğurur. Yanıt sırasında: sıraya duyarlı bir işlem iki yanıt ters gelirse bayat değer bırakır. Önbellekte: sağlam bir işlem hata riski taşımadan tekrar edilebilir. Sonraki beş ders bu üç yeri sırayla ölçer.

#### Sınırlayıcı Ölçüm

Bir işlemin sınıfı yalnız işleme değil başlangıç durumuna da bağlıdır. Aşağıda aynı iki işlem, `x * 2` ve `x * 3`, iki başlangıçtan çalıştırılır.

```swift
// main.swift — ayni iki islem, iki baslangic
let doubled = replay("sifirdan", from: 0, { $0 * 2 }, then: { $0 * 3 }).report()
let fromOne = replay("birden", from: 1, { $0 * 2 }, then: { $0 * 3 }).report()
countSensitivities([doubled, fromOne])
```

```
sifirdan | bir=0 iki=0 ab=0 ba=0 -> saglam
birden | bir=2 iki=4 ab=6 ba=6 -> yinelemeye-duyarli
saglam: 1
yinelemeye-duyarli: 1
siraya-duyarli: 0
cift-duyarli: 0
```

Sıfırdan başlayınca iki işlem de sıfırı sıfır bırakır ve satır sağlamdır. Birden başlayınca ikinci uygulama değeri değiştirir ve satır yinelemeye duyarlıdır. İki sıra bu işlem çiftinde her iki başlangıçta da aynı sonucu verir, çünkü çarpmalar yer değiştirir. Aynı işlem, başlangıç durumu değişince başka sınıfa düştü.

**AG3.** Bu ölçüm yalnız sıfır ve bir başlangıçlarını karşılaştırır. Başka başlangıçlar ölçülmedi ve sınıf onlarda da değişebilir.

#### Özet

- Ağ istemcisi üç parçadan kurulur: yapılandırma davranışı söyler, oturum o davranışla çalışır, görev tek bir aktarımı yürütür.
- Veri görevi yanıtı belleğe alır, yükleme görevi gövdeyi ayrı gönderir, indirme görevi yanıtı dosyaya yazar.
- Duyarlılık iki soruyla ölçülür: yinelenirse ve sırası değişirse sonuç değişir mi.
- Altı işlem dört kategoriye ayrıldı: bir sağlam, iki yinelemeye duyarlı, iki sıraya duyarlı, bir çift duyarlı.
- Etkisizlik yinelemeyi çözer, sırayı çözmez; aynı işlem başlangıç durumu değişince başka sınıfa düşebilir.
- Bu dersin duyarlılığı: **dördü de**.

#### Sonraki Adım

Ağdan dönen yanıt bir bayt dizisidir; uygulamanın ihtiyacı ise tipli bir kayıttır. Sıradaki derste JSON Kodlama ve Çözme, bir yanıtı kayda çevirmenin kendisinin de sıraya duyarlı olabileceğini ölçecek: iki sürümlü bir kaydın hangi sırayla yazıldığı depodaki değeri belirler.

### JSON Kodlama ve Çözme

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/json-kodlama-ve-cozme

Bir ağ yanıtının tipli kayda çevrilmesi ve kaydın depoya yazılması; iki sürümlü bir kaydın hangi sırayla yazıldığının sonucu nasıl değiştirdiğinin ölçümü.

Önceki derste ağ istemcisinin üç parçasını ve altı işlemin dört duyarlılığını ölçtük. Ağdan dönen şey ise bir bayt dizisidir. Uygulama bu diziyi **kodlama ve çözme** (encoding and decoding) ile tipli bir kayda çevirir. Bu çevirinin kendisi güvenli görünür: tip uymuyorsa çözme hata verir, uyuyorsa kayıt hazırdır. Sorun çözmeden sonra başlar. Çözülen kayıt bir depoya yazılır ve iki yanıt aynı kaydın iki sürümünü taşıyorsa, hangisinin sonra yazıldığı depodaki değeri belirler.

Çekirdek aynıdır. Her derste olduğu gibi ilk ölçülen Swift bloğu `Replay.swift`'tir.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

Bu derste üç işlem ölçülecek: kaydı listeye eklemek, kaydı kimliğine göre depoya yazmak ve kaydı kodlayıp çözmek. Her biri bir kayıt tipi ve küçük bir raf üzerinde çalışır.

#### Kayıt ve Raf

`Record` hem `Codable`'dır hem de ölçüm çıktısında okunabilsin diye kısa bir `description` taşır. `Shelf` kayıtların durumunu tutar; `description` kayıtları kimliğe göre sıralayarak yazar, çünkü sözlük sırası bilinmez.

```swift
// Records.swift — kayit, raf ve uc yazma bicimi
import Foundation

struct Record: Codable, Equatable, CustomStringConvertible {
    let id: Int
    let name: String
    let version: Int
    var description: String { "\(id):\(name):v\(version)" }
}

struct Shelf: Equatable, CustomStringConvertible {
    var records: [Record] = []
    var description: String { records.sorted { $0.id < $1.id }.map(\.description).joined(separator: ",") }
}

func decodeRecord(_ text: String) -> Record {
    try! JSONDecoder().decode(Record.self, from: Data(text.utf8))
}

func append(_ record: Record) -> (Shelf) -> Shelf {
    { shelf in Shelf(records: shelf.records + [record]) }
}

func upsert(_ record: Record) -> (Shelf) -> Shelf {
    { shelf in
        var next = shelf.records
        if let index = next.firstIndex(where: { $0.id == record.id }) {
            next[index] = record
        } else {
            next.append(record)
        }
        return Shelf(records: next)
    }
}

func roundTrip(_ record: Record) -> Record {
    let encoder = JSONEncoder()
    encoder.outputFormatting = [.sortedKeys]
    let data = try! encoder.encode(record)
    return try! JSONDecoder().decode(Record.self, from: data)
}

func rename(_ name: String) -> (Record) -> Record {
    { record in Record(id: record.id, name: name, version: record.version) }
}
```

Üç işlemi iki yanıtın metniyle sınarız. `first` ve `second` aynı kimliği taşıyan iki sürümdür.

```swift
// main.swift — iki surum, uc yazma bicimi
let first = decodeRecord(#"{"id":7,"name":"defter","version":1}"#)
let second = decodeRecord(#"{"id":7,"name":"defter-yeni","version":2}"#)

let listed = replay("listeye-ekle", from: Shelf(), append(first), then: append(second)).report()
let stored = replay("kimlige-gore-yaz", from: Shelf(), upsert(first), then: upsert(second)).report()
let travel = replay("kodla-coz", from: first, roundTrip, then: rename("defter-yeni")).report()

countSensitivities([listed, stored, travel])
```

```
listeye-ekle | bir=7:defter:v1 iki=7:defter:v1,7:defter:v1 ab=7:defter:v1,7:defter-yeni:v2 ba=7:defter-yeni:v2,7:defter:v1 -> cift-duyarli
kimlige-gore-yaz | bir=7:defter:v1 iki=7:defter:v1 ab=7:defter-yeni:v2 ba=7:defter:v1 -> siraya-duyarli
kodla-coz | bir=7:defter:v1 iki=7:defter:v1 ab=7:defter-yeni:v1 ba=7:defter-yeni:v1 -> saglam
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 1
```

`listeye-ekle` çift duyarlıdır. Aynı yanıtı iki kez eklemek kaydı çoğaltır ve iki sürümün sırası listenin sırasını değiştirir. `kimlige-gore-yaz` yinelemeye karşı sağlamdır: `bir` ile `iki` aynıdır, çünkü aynı kimlikli kayıt yenisiyle değişir, eklenmez. Ama sıraya duyarlı kalır: `ab` ile `ba` farklıdır, çünkü son yazan sürüm kazanır. `kodla-coz` sağlamdır; kayıt kodlanıp çözülünce aynı kayıt döner ve yeniden adlandırmayla sırası değişse de sonuç aynıdır.

**AG4.** Ölçüm iki sürümün kimliğinin aynı olduğunu varsayar. Farklı kimlikli kayıtlar ayrı satırlara yazılır ve sırası yalnız sıralı gösterimde görünmez olur.

**AG5.** `rename` yalnız adı değiştirir ve sürüm numarasına dokunmaz. Sürümü de değiştiren bir işlem ayrıca ölçülmelidir.

#### Ağdan Gelen Yanıt

Gerçek bir uygulamada bu iki metin iki ayrı yanıtın gövdesidir. İstemci yanıtı bir bayt dizisi olarak alır ve çözer.

```swift
import Foundation

func loadRecord(session: URLSession, url: URL) async throws -> Record {
    let (data, _) = try await session.data(from: url)
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    return try decoder.decode(Record.self, from: data)
}
```

Bu parça KAYNAK kalır; ağa çıkar. Yukarıdaki model, yazma işleminin duruma ne yaptığını kurar; ağın kendisi değildir. `convertFromSnakeCase` sunucunun `created_at` gibi anahtarlarını `createdAt` gibi alan adlarına eşler. Çözme hata fırlatabilir; bu yüzden çağrı `try` ile yapılır ve çağıran hatayı ya yayar ya yakalar.

İki yanıtın hangi sırayla geleceğini istemci seçemez. İki istek aynı anda yola çıkarsa ikinci gönderilen önce dönebilir. Bu yüzden `kimlige-gore-yaz` satırındaki sıra duyarlılığı bir kuram değil, ağın normal bir davranışıdır.

#### Kodlamanın Neden Güvenli Olduğu

`kodla-coz` satırı sağlam çıktı, çünkü kodlama ile çözme birbirinin tersi olacak biçimde yazılır. `Record` üç alanı da sabit tiplerle taşır; kodlayıcı her alanı adıyla bir metne yazar, çözücü aynı adlarla geri okur. Araya giren bir karar yoktur: hangi alanın nereye gideceği tipten gelir, işlemin sırasından ya da kaç kez çalıştığından gelmez. Bu yüzden aynı kaydı iki kez kodlayıp çözmek aynı kaydı verir ve kaydın adını değiştirmekle gidiş dönüşün hangisinin önce geldiği sonucu etkilemez.

Bu güvenliğin iki koşulu vardır. Birincisi, kodlanan değerin kendisi kayıpsız olmalıdır. Bir `Double` alanı çok uzun bir ondalık kesir taşıyorsa metne yazılıp geri okunurken son basamaklar değişebilir; `Record`'un tam sayı ve metin alanları bu tehlikeyi taşımaz. İkincisi, çözücünün beklediği adlar kodlayıcının yazdığı adlarla aynı olmalıdır. Sunucu `created_at` yazıp istemci `createdAt` beklerse çözme ilk satırda hata fırlatır. Ölçüm ikisini de karşılanmış sayar; bu yüzden sağlam çıkması çevirinin kusursuz olduğunu değil, ölçülen kaydın çevirinin kolay olduğu bir kayıt olduğunu söyler.

#### Gerçek Uygulamada Nerede Görülür

Bir not uygulaması her kaydı sunucudan JSON olarak alır. Kullanıcı bir notu açar, uygulama notu çözer ve ekrandaki listeyi günceller. Aynı not, kullanıcı ekranı yenilediğinde bir kez daha gelir. Listeye ekleyen kod bu ikinci yanıtı yeni bir not sanar ve ekranda aynı not iki satır olarak görünür. Ölçümdeki `listeye-ekle` satırı bunun ta kendisidir: `iki` sütununda aynı kayıt iki kez durur.

Kullanıcı bunu bir görsel kusur olarak görür, geliştirici ise çoğunlukla ağ katmanında bir şey aramaya başlar. Oysa ağ katmanı doğru çalışmıştır; iki yanıt da geçerli ve doğru çözülmüştür. Hata, çözülmüş kaydın depoya yazılma biçimindedir. Çözmenin sağlam, yazmanın duyarlı olması bu dersin bölünme çizgisidir: sorunu arayacağınız yer çevirinin iki yakası değil, çevirinin çıktısıyla yapılan işlemdir.

İkinci bir durum sıradır. Kullanıcı bir notu hızla iki kez düzenler ve her düzenleme ayrı bir kaydetme isteği gönderir. Sunucu iki isteği işler ve ardından uygulama notu iki yanıtla yeniden okur. Yanıtların varış sırası gönderim sırasıyla aynı olmak zorunda değildir. Eski sürüm sonra gelirse ekranda eski metin kalır ve kullanıcı yazdığı metnin kaybolduğunu düşünür. `kimlige-gore-yaz` satırının `ab` ile `ba` sütunları arasındaki fark tam olarak budur.

#### Yanlış Kurulum ve Düzeltmesi

Yaygın yanlış kurulum, her yanıtı gelir gelmez depoya yazmaktır.

```swift
import Foundation

// Yanlış: gelen her yanıt, sürümüne bakılmadan kaydın üstüne yazılır
func receive(_ record: Record, into shelf: Shelf) -> Shelf {
    upsert(record)(shelf)
}
```

Bu parça KAYNAK kalır. Kimliğe göre yazmak yinelemeyi çözer, ama geç gelen eski bir yanıt yeni sürümün üstüne yazılır. Düzeltme, yalnız daha büyük sürümü kabul etmektir.

```swift
import Foundation

// Düzeltme: yalnız daha yeni sürüm depoya girer
func receiveNewer(_ record: Record, into shelf: Shelf) -> Shelf {
    if let current = shelf.records.first(where: { $0.id == record.id }), current.version >= record.version {
        return shelf
    }
    return upsert(record)(shelf)
}
```

Bu da KAYNAK kalır. İşlev, aynı kimlikli bir kayıt için depodaki sürümü yanıttakiyle karşılaştırır. Eşit ya da daha büyük sürüm depoda varsa gelen yanıt yok sayılır. Bu iki yanıtın sırasını ortadan kaldırır: hangi sırayla gelirlerse gelsinler depoda sürümü büyük olan kalır. Yineleme de çözülür, çünkü aynı sürümün ikinci gelişi yok sayılır. Ölçümde bu işlev yok; ölçülen `kimlige-gore-yaz` hâlâ sıraya duyarlıdır ve düzeltmenin etkisi yalnız bu açıklamada anlatılır.

Düzeltmenin bir koşulu vardır: sürüm numarasını sunucu vermelidir. İstemcinin kendi sayacı, iki istemci arasında karşılaştırılamaz. Sunucu sürüm vermiyorsa son değiştirilme zamanı benzer bir iş görür, ama saatlerin ayrışması bu yolu da kırılgan yapar.

#### Anahtar ve Tarih Stratejileri

Sunucu ile istemcinin adlandırma alışkanlığı çoğu zaman farklıdır. Sunucu kelimeleri alt çizgiyle ayırır, Swift kodu büyük harfle. Çözücüye bir anahtar stratejisi vermek bu farkı tek yerde kapatır.

```swift
import Foundation

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
```

Bu parça KAYNAK kalır. Tarih stratejisi, sunucunun gönderdiği tarih metninin hangi biçimde olduğunu çözücüye bildirir. Biçim yanlış seçilirse çözme `DecodingError` fırlatır ve tüm yanıt okunamaz. Stratejiyi her çağrıda yeniden kurmak yerine ağ katmanının tek bir yerinde kurmak, iki ekranın aynı alanı farklı okumasını önler.

Eksik ve fazla alan da aynı yerde karar verilen bir konudur. Yanıtta tipte olmayan bir alan varsa çözücü onu yok sayar; bu, sunucunun yeni alan eklemesini istemciyi kırmadan mümkün kılar. Tipte olup yanıtta bulunmayan zorunlu bir alan ise `keyNotFound` hatası verir. Alanın gerçekten bazen eksik olabileceğini biliyorsanız tipte isteğe bağlı olarak tanımlarsınız. Bunu yapmadan hatayı yutmak, kaydı sessizce yarım bırakır.

#### Ölçümün Gösterdiği ve Göstermediği

Üç satırın üç sınıfı, çevirinin nerede güvenli, nerede duyarlı olduğunu ayırır. Güvenli olan, metin ile kayıt arasındaki dönüşümdür; duyarlı olan, kaydın bir koleksiyona katılma biçimidir. Bu ayrım bir hatayı ararken zaman kazandırır. Çözme hatası alan bir uygulamada sorun yanıtın biçimindedir: bir alan eksik ya da tipi farklıdır. Çoğalan ya da eski değere dönen kayıt gören bir uygulamada ise çözme doğru çalışmıştır ve sorun yazma kuralındadır.

Ölçümün göstermediği şeyler de vardır. Kayıtta iç içe bir nesne ya da dizi olsaydı çözme daha fazla yerde başarısız olabilirdi. Tarih alanı olsaydı biçim seçimi sonucu değiştirirdi. Yanıtta gelen büyük sayılar, isteğe bağlı alanlar ve boş değerler ayrı davranışlar doğurur. Bu derste ölçülen kayıt bilerek küçük tutuldu, böylece duyarlılık sorusu çevirinin ayrıntılarına karışmadan görülebilsin. Gerçek bir uygulamada çözme hatası da bir hata sınıfıdır ve üçüncü derste anlatılan sınıflandırmanın içinde yer alır: çözülemeyen yanıtı yeniden denemek çoğunlukla aynı sonucu verir.

#### Çözme Hatasıyla Karşılaşmak

Çözme hatası sessiz bırakılırsa en pahalı hatalardan biri olur. Sunucu bir alanın adını ya da tipini değiştirdiğinde tüm yanıt okunamaz hâle gelir ve uygulama boş bir ekran gösterir. Hatayı yakalayan kod, `DecodingError`'ın hangi alanda ve hangi yolda oluştuğunu günlüğe yazmalıdır; yalnız "çözülemedi" demek, sorunu bulmayı saatlere uzatır. Kullanıcıya ise ayrıntı değil, anlaşılır bir ileti gösterilir.

Hatanın yeniden denenip denenmeyeceği de bir karardır. Çözülemeyen bir yanıtı aynı sunucudan yeniden istemek çoğunlukla aynı sonucu verir; hata yanıtın biçimindedir, ağda değildir. Bu yüzden çözme hatası üçüncü dersteki sınıflandırmada denenemez hatalar arasına girer. Bir istisna, yanıtın yarım kesilmiş olduğu durumdur; o zaman gövde eksiktir ve yeniden istemek sonucu düzeltebilir. İkisini ayırt etmek için yanıtın durum kodu ve uzunluk bilgisi birlikte okunur.

#### Sık Yapılan Hatalar

- Yanıtı çözüp doğrudan listeye eklemek. Aynı kayıt iki yanıtla gelince çoğalır.
- Ham metni karşılaştırarak iki yanıtı "aynı" saymak. Anahtar sırası farklı iki metin aynı kaydı taşıyabilir.
- Çözme hatasını yutmak. Sunucu bir alanı değiştirdiğinde uygulama sessizce eski değerle çalışır.
- Kayıtta sürüm numarası olmadığını varsayarak son gelen yanıtın en yeni olduğunu düşünmek.

#### Ölçülen Sonucun Yorumu

Üç işlemin üç sınıfı vardı: çift duyarlı, sıraya duyarlı ve sağlam. Kodlama ile çözme sağlamdı; sorun çevirinin kendisinde değil, çevrilen kaydın depoya nasıl yazıldığındaydı. Kimliğe göre yazmak yinelemeyi çözdü; sıra sorunu kaldı. Sırayı çözmek için kayıtta bir sürüm numarası taşımak ve yalnız daha büyük sürümü yazmak gerekir. Bu kural ölçülmedi; dördüncü derste benzer bir saklama kuralı ölçülecek.

#### Sınırlayıcı Ölçüm

Kural ham metne mi çözülmüş değere mi baktığını söylemez; hangisinin karşılaştırıldığı sonucu belirler. Aynı kaydı taşıyan, anahtar sırası farklı iki metin aşağıda iki biçimde karşılaştırılır.

```swift
// main.swift — ayni kayit, iki metin
import Foundation

let textA = #"{"id":7,"name":"defter","version":1}"#
let textB = #"{"version":1,"name":"defter","id":7}"#
print("ham metin esit mi: \(textA == textB)")
print("cozulmus kayit esit mi: \(decodeRecord(textA) == decodeRecord(textB))")
```

```
ham metin esit mi: false
cozulmus kayit esit mi: true
```

Ham metinler farklıdır, çözülmüş kayıtlar aynıdır. İki yanıtı ham metin olarak karşılaştıran bir istemci yinelenen yanıtı yeni sanar; çözülmüş değeri karşılaştıran istemci yok sayar. **Kural eşitsizliği görür, neyin karşılaştırıldığını görmez.**

**AG6.** Bu ölçüm anahtar sırasının farkını gösterir. Boşluk ya da sayı biçimi farkı ölçülmedi.

#### Özet

- Kodlama ve çözme tipli bir kaydı bir bayt dizisine ve geri çevirir; gidiş dönüş ölçümde sağlam çıktı.
- Çözülen kaydı listeye eklemek hem yinelemeye hem sıraya duyarlıdır.
- Kimliğe göre yazmak yinelemeyi çözer, iki sürümün varış sırasını çözmez.
- İki yanıtın varış sırası ağın davranışıdır; istemci seçemez.
- Ham metin eşitliği ile çözülmüş değer eşitliği aynı kararı vermez.
- Bu dersin duyarlılığı: **sıraya duyarlı**.

#### Sonraki Adım

Yanıt hiç gelmeyebilir ya da yarıda kesilebilir. Sıradaki derste Hata ve Yeniden Deneme, bir hatanın sınıflandırılmasını ve zaman aşımından sonra yeniden denenen bir siparişin neden ikinci kaydı doğurabildiğini ölçecek.

### Hata ve Yeniden Deneme

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/hata-ve-yeniden-deneme

Ağ hatalarının sınıflandırılması ve zaman aşımından sonra yeniden denenen bir siparişin sunucuda ikinci kaydı doğurup doğurmadığının ölçümü.

Ağ katmanında hata bir istisna değil, günlük bir olaydır. Bağlantı kopar, sunucu geç yanıtlar, yanıt yolda kaybolur. Ağ istemcisi bu hataları iki gruba ayırmak zorundadır: tekrar denemeye değenler ve değmeyenler. **Yeniden deneme** (retry) doğru yapılırsa uygulamayı kararlı kılar, yanlış yapılırsa aynı siparişi iki kez verir.

Bu derste hata sınıflandırması saf bir işlevdir ve ölçülür. Asıl soru ise sınıflandırmanın bıraktığı yerdedir: bir isteğin yeniden denenmesi sunucuda ne yapar? Çekirdek aynıdır.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

#### Hata Sınıflandırması

Dört hata ayırt edilir: zaman aşımı, bağlantı yokluğu, sunucu hatası ve istemci hatası. İlk ikisi ve beşyüzlü sunucu hataları yeniden denenebilir; dörtyüzlü istemci hataları denenemez, çünkü aynı istek aynı hatayı alır. Deneme sayısı bir **deneme bütçesiyle** sınırlanır ve bütçe bir tamsayıdır. Bekleme ya da **üstel geri çekilme** süresi bu derste modellenmez ve ölçülmez.

```swift
// Orders.swift — hata sinifi, siparis defteri ve yeniden deneme dongusu
enum Failure: Equatable {
    case timeout
    case offline
    case server(Int)
    case client(Int)
}

func isRetryable(_ failure: Failure) -> Bool {
    switch failure {
    case .timeout, .offline: return true
    case .server(let code): return code >= 500
    case .client: return false
    }
}

struct Ledger: Equatable, CustomStringConvertible {
    var orders: [String] = []
    var description: String { "[" + orders.joined(separator: ",") + "]" }
}

func place(_ item: String, key: String?) -> (Ledger) -> Ledger {
    { ledger in
        let tag = "\(item)@\(key ?? "-")"
        if key != nil && ledger.orders.contains(tag) { return ledger }
        return Ledger(orders: ledger.orders + [tag])
    }
}

func simulate(budget: Int, key: String?, lostResponses: Int) -> (attempts: Int, ledger: Ledger) {
    var ledger = Ledger()
    var attempts = 0
    while attempts < budget {
        attempts += 1
        ledger = place("kitap", key: key)(ledger)
        if attempts > lostResponses { break }
    }
    return (attempts, ledger)
}
```

`Failure` dört durumlu bir **numaralandırmadır**. `isRetryable` yalnız hatanın türüne bakar. `place` sunucunun siparişi kaydetmesini kurar: anahtar verilmişse ve aynı etiketli kayıt varsa ikinci kaydı eklemez. `simulate` istemcinin döngüsünü kurar. İstek her denemede sunucuya ulaşır ve kaydedilir; ilk `lostResponses` yanıt ise yolda kaybolur. İstemci kayıp yanıtı zaman aşımı olarak görür ve bütçe elverdiği sürece yeniden dener. **Model, işlemin duruma ne yaptığını kurar; ağın kendisi değildir.**

```swift
// main.swift — siniflandirma, yeniden deneme, iki yazma bicimi
print("zaman-asimi: \(isRetryable(.timeout))")
print("baglanti-yok: \(isRetryable(.offline))")
print("sunucu-503: \(isRetryable(.server(503)))")
print("istemci-404: \(isRetryable(.client(404)))")

let keyless = simulate(budget: 3, key: nil, lostResponses: 1)
let keyed = simulate(budget: 3, key: "k-9", lostResponses: 1)
print("anahtarsiz: deneme=\(keyless.attempts) defter=\(keyless.ledger)")
print("anahtarli: deneme=\(keyed.attempts) defter=\(keyed.ledger)")

let withoutKey = replay("anahtarsiz-siparis", from: Ledger(), place("kitap", key: nil), then: place("kitap", key: nil)).report()
let withKey = replay("anahtarli-siparis", from: Ledger(), place("kitap", key: "k-9"), then: place("kitap", key: "k-9")).report()
let spending = replay("butce-harca", from: 3, { max($0 - 1, 0) }, then: { max($0 - 1, 0) }).report()

countSensitivities([withoutKey, withKey, spending])
```

```
zaman-asimi: true
baglanti-yok: true
sunucu-503: true
istemci-404: false
anahtarsiz: deneme=2 defter=[kitap@-,kitap@-]
anahtarli: deneme=2 defter=[kitap@k-9]
anahtarsiz-siparis | bir=[kitap@-] iki=[kitap@-,kitap@-] ab=[kitap@-,kitap@-] ba=[kitap@-,kitap@-] -> yinelemeye-duyarli
anahtarli-siparis | bir=[kitap@k-9] iki=[kitap@k-9] ab=[kitap@k-9] ba=[kitap@k-9] -> saglam
butce-harca | bir=2 iki=1 ab=1 ba=1 -> yinelemeye-duyarli
saglam: 1
yinelemeye-duyarli: 2
siraya-duyarli: 0
cift-duyarli: 0
```

Dört hatanın üçü denenebilirdir, istemci hatası değildir. Ama dikkat çeken ikinci bölümdür. Aynı kayıp yanıt aynı döngüde iki farklı sonuç verdi: anahtarsız istek iki deneme sonunda iki kayıt bıraktı, anahtarlı istek aynı iki denemeden sonra tek kayıt bıraktı. İstemcinin gördüğü şey ikisinde de aynıdır: bir zaman aşımı ve ardından başarılı bir yanıt.

`anahtarsiz-siparis` yinelemeye duyarlıdır: ikinci uygulama ikinci kaydı ekler. `anahtarli-siparis` sağlamdır: aynı anahtar ikinci kaydı engeller. `butce-harca` yinelemeye duyarlıdır; ama bu hata değil, bütçenin işidir. Bütçe tam da yinelendikçe azalsın diye vardır. Hangi yinelemenin hata, hangisinin tasarım olduğunu kural söylemez.

**AG7.** Model sunucunun anahtarı tanıdığını varsayar. Gerçek bir sunucu anahtar başlığını yok sayabilir ve o zaman anahtarlı istek de ikinci kaydı doğurur.

**AG8.** Model kayıp yanıtı her zaman isteğin sunucuya ulaştığı biçimde kurar. Ulaşmayan istek ayrıca incelenir.

#### İstemcide Hatanın Okunması

Gerçek bir istemcide hata, bir `URLError`'ın kodundan okunur.

```swift
import Foundation

func classify(_ error: Error) -> Failure {
    guard let urlError = error as? URLError else { return .client(0) }
    switch urlError.code {
    case .timedOut: return .timeout
    case .notConnectedToInternet: return .offline
    default: return .client(0)
    }
}
```

Bu parça KAYNAK kalır. `URLError` ağ hatalarını bir kodla taşır; `.timedOut` ve `.notConnectedToInternet` bunlardan ikisidir. Parça yalnız iki kodu eşler; gerçek bir sınıflandırma daha fazlasını kapsar. Anahtar başlığının adı sunucuya bağlıdır; genelde bir istek başlığıyla gönderilir.

#### Yeniden Denemenin Bedeli

Yeniden deneme bir sigorta gibi görünür, ama ucuz değildir. Her deneme sunucuya bir istek daha yükler, kullanıcının bekleme süresini uzatır ve pil harcar. Sunucu zaten yük altındaysa, binlerce istemcinin aynı anda yeniden denemesi yükü azaltmaz, artırır. Bu yüzden iki sınırlama birlikte kullanılır: deneme bütçesi toplam deneme sayısını kısar, **üstel geri çekilme** ise denemeler arasındaki beklemeyi her seferinde katlar. Bütçe kaç kez denendiğini, geri çekilme ne sıklıkta denendiğini sınırlar.

Geri çekilmeye genellikle küçük bir rastgele pay eklenir. Aksi hâlde aynı anda başarısız olan bin istemci, aynı anda ve aynı aralıklarla yeniden dener ve sunucuya dalgalar hâlinde çarpar. Rastgele pay bu dalgaları dağıtır. Bu ders bekleme süresini modellemediği için bunu ölçmez; ama gerçek bir istemcide bütçe ile geri çekilmeyi ayrı iki karar olarak ele almak gerekir.

Bütçenin ölçümde yinelemeye duyarlı çıkması bir kusur değildir. `butce-harca` satırında `bir=2` ile `iki=1` farklıdır, çünkü bütçe tam da her harcamada azalsın diye vardır. Kural yinelemeye duyarlılığı yalnız bildirir; bunun istenen mi istenmeyen mi olduğunu ayırt etmez. Bu ayrımı yapan, ölçümü okuyan kişidir.

#### Gerçek Uygulamada Nerede Görülür

Bir alışveriş uygulamasında kullanıcı "Siparişi Onayla" düğmesine basar. Ağ yavaştır, istek sunucuya ulaşır ve sipariş kaydedilir, ama yanıt yolda kaybolur. İstemci zaman aşımı görür ve isteği yeniden gönderir. Anahtar yoksa sunucu ikinci isteği yepyeni bir sipariş sayar ve kullanıcı iki kez ücretlendirilir. `anahtarsiz` satırındaki `defter=[kitap@-,kitap@-]` tam olarak bu durumdur.

Kullanıcı bunu çoğunlukla ayrı bir hata olarak yaşar: "Siparişim neden iki kez geldi?" Destek ekibi iki kaydı görür, ikisi de geçerli görünür ve hangisinin silineceği belirsizdir. Sorunu yeniden üretmek zordur, çünkü yalnız yanıtın kaybolduğu nadir bir durumda ortaya çıkar. Geliştirici testte ağ hep çalıştığı için sorunu hiç görmez. Üretimde ise milyonlarca istek arasında her gün birkaç kez gerçekleşir.

Aynı durum yalnız ödemeyle sınırlı değildir. Bir mesajı göndermek, bir yorum eklemek, bir sepete ürün koymak gibi sunucuda yeni bir kayıt doğuran her işlem aynı riski taşır. Okuma işlemleri ise genellikle güvenle yeniden denenir, çünkü sunucudaki durumu değiştirmezler.

#### Yanlış Kurulum ve Düzeltmesi

Yaygın yanlış kurulum, her hatada ve sınırsız biçimde yeniden denemektir.

```swift
import Foundation

// Yanlış: her hata yeniden denenir, bütçe yok, bekleme yok
func sendWithRetry(_ request: URLRequest, using session: URLSession) async throws -> Data {
    while true {
        do {
            let (data, _) = try await session.data(for: request)
            return data
        } catch {
            continue
        }
    }
}
```

Bu parça KAYNAK kalır. Üç sorunu vardır: bir `404` bile sonsuza dek denenir, sunucu çöktüğünde istemci saniyede yüzlerce istek gönderir ve kullanıcı ekranın neden hiç ilerlemediğini anlayamaz. Düzeltme sınıflandırmayı, bütçeyi ve beklemeyi bir araya getirir.

```swift
import Foundation

// Düzeltme: yalnız denenebilir hata, sınırlı bütçe, katlanan bekleme
func sendWithRetry(_ request: URLRequest, using session: URLSession, budget: Int = 3) async throws -> Data {
    var delay: UInt64 = 500_000_000
    for attempt in 1...budget {
        do {
            let (data, response) = try await session.data(for: request)
            if let http = response as? HTTPURLResponse, http.statusCode >= 500, attempt < budget {
                try await Task.sleep(nanoseconds: delay)
                delay *= 2
                continue
            }
            return data
        } catch {
            guard isRetryable(classify(error)), attempt < budget else { throw error }
            try await Task.sleep(nanoseconds: delay)
            delay *= 2
        }
    }
    throw URLError(.unknown)
}
```

Bu da KAYNAK kalır. Dönüş yalnız beşyüzlü yanıtlarda ve denenebilir hatalarda bekler. Dörtyüzlü yanıt beklemeden döner; çağıran onu kendi hatası olarak okur. Bekleme her denemede katlanır. Bütçe bittiğinde son hata çağırana atılır, böylece kimse hatanın yutulduğunu fark etmeden bekleyip durmaz. Düzeltilmiş işlev yeniden denemeyi güvenli yapmaz; yalnız sınırlar. İsteğin yinelemeye karşı sağlam olması için ayrıca etkisizlik anahtarı gerekir, ve bu `anahtarli-siparis` satırının gösterdiği şeydir.

#### Hatayı Kullanıcıya Taşımak

Bütçe tükendiğinde hata sessizce kaybolmamalıdır. Kullanıcıya iki şey gösterilir: ne olduğu ve ne yapabileceği. Bağlantı yoksa "İnternet bağlantınız yok" ve bağlantı gelince otomatik yeniden deneme önerisi anlamlıdır. Sunucu hatasında "Şu anda ulaşamıyoruz, birazdan yeniden deneyin" yeterlidir. İstemci hatasında ise yeniden denemenin bir yararı yoktur; kullanıcıya girdiğini düzeltmesi söylenir.

Anahtarsız bir yazma isteği zaman aşımıyla bittiğinde durum daha da inceliklidir. İstemci isteğin sunucuya ulaşıp ulaşmadığını bilmez; bu, sınırlayıcı ölçümün gösterdiği belirsizliktir. Doğru davranış, kullanıcıya "Sonuç belirsiz" demek ve sunucudaki durumu sorgulamadan ikinci bir yazma göndermemektir. Anahtar varsa belirsizlik ortadan kalkar, çünkü ikinci istek aynı anahtarla zararsızca tekrarlanabilir.

#### Okuma ve Yazma İsteklerinin Ayrımı

Yeniden deneme kararının ilk girdisi hata değil, isteğin türüdür. Bir okuma isteği sunucudaki durumu değiştirmez; yanıtı kaybolsa da ikinci kez göndermek güvenlidir. Bir yazma isteği ise durumu değiştirir ve ikinci gönderim, durumun iki kez değişmesi demektir. Protokol bu ayrımı belli ölçüde kendisi taşır: okuma istekleri tanım gereği tekrarlanabilir kabul edilir, yeni bir kayıt doğuran istekler kabul edilmez. Güncelleme ve silme istekleri de çoğunlukla tekrarlanabilir olarak tasarlanır, çünkü aynı güncellemeyi ikinci kez uygulamak sonucu değiştirmez.

Pratikte bu, istemcide iki ayrı yol anlamına gelir. Okuma yolu hatayı sınıflandırır, bütçe elverdiği sürece bekleyip yeniden dener. Yazma yolu ise önce isteğin bir etkisizlik anahtarı taşıyıp taşımadığına bakar. Anahtar varsa okuma yolu gibi davranır. Anahtar yoksa yeniden deneme kararı kullanıcıya bırakılır ya da sunucudaki durum sorgulanarak verilir. Ölçümdeki `anahtarsiz-siparis` ile `anahtarli-siparis` satırları, bu iki yolun farkını sonuçlarıyla gösterir.

#### Bütçeyi Ne Harcar

Her hata bütçeden aynı payı almaz. Bağlantı yokluğunda istek hiç yola çıkmaz; bunu bir deneme saymak bütçeyi boşa harcar. Daha iyi bir kurgu, bağlantı gelene kadar beklemek ve bütçeyi yalnız gerçekten gönderilen istekler için tüketmektir. Sunucu hatalarında ise her gönderim sunucuya yük bindirir ve bütçe doğrudan bu yükü sınırlar. Zaman aşımı ikisinin arasındadır: istek yola çıkmıştır, ama sonucu bilinmez.

Bütçenin büyüklüğü de hatanın doğasına bağlıdır. Kısa süreli bir ağ kesintisi için üç deneme genellikle yeterlidir. Sunucunun uzun süre erişilemez olduğu bir durumda onlarca deneme yalnız yük yaratır; o zaman istemcinin vazgeçmesi ve kullanıcıyı bilgilendirmesi daha doğrudur.

#### Sık Yapılan Hatalar

- Her hatayı yeniden denemek. Bir `404` yanıtı kaç kez tekrarlanırsa tekrarlansın `404` olarak kalır.
- Yeniden denemeye üst sınır koymamak. Bütçesiz döngü sunucuyu daha da yorar.
- Anahtarsız bir `POST`'u zaman aşımından sonra yeniden denemek.
- Zaman aşımını "istek başarısız oldu" diye okumak. Zaman aşımı yalnız yanıtın gelmediğini söyler.
- Bekleme süresi olmadan art arda denemek. Bu derste süre ölçülmez ama gerçek istemcide gerekir.

#### Ölçülen Sonucun Yorumu

Yeniden deneme bir sınıflandırma sorusu gibi görünür ama bir duyarlılık sorusudur. Sınıflandırma iki sonuçtan birini verir: dene ya da deneme. Duyarlılık ise denemenin sonucunun ne olacağını sorar. Anahtarsız sipariş ikinci kaydı doğurdu; anahtarlı sipariş doğurmadı. Hata sınıflandırması ikisinde de aynıydı.

#### Sınırlayıcı Ölçüm

İstemci hatayı gördüğünde iki dünyadan hangisinde olduğunu bilemez: istek sunucuya ulaştı ama yanıt kayboldu, ya da istek hiç ulaşmadı. Aşağıdaki ölçüm iki dünyayı kurar ve istemcinin gördüğünü ile sunucudaki defteri yan yana basar.

```swift
// main.swift — iki dunya, ayni gozlem
func world(requestArrived: Bool) -> (observed: Failure, ledger: Ledger) {
    let ledger = requestArrived ? place("kitap", key: nil)(Ledger()) : Ledger()
    return (.timeout, ledger)
}

let lost = world(requestArrived: true)
let never = world(requestArrived: false)
print("istemcinin gordugu esit mi: \(lost.observed == never.observed)")
print("sunucudaki defter esit mi: \(lost.ledger == never.ledger)")
```

```
istemcinin gordugu esit mi: true
sunucudaki defter esit mi: false
```

İstemcinin gördüğü iki dünyada aynıdır, sunucudaki defter farklıdır. Bu yüzden istemci yeniden deneyip denememeye gözleminden karar veremez; kararı isteğin kendisi, yani anahtar verir. Kural bu belirsizliği çözmez, ölçmez de.

**AG9.** Ölçüm iki dünyayı istemci gözünden ayırt edilemez olarak kurar. Gerçek bir istemci bazı hatalarda (bağlantı hiç kurulamadı gibi) isteğin ulaşmadığını bilebilir.

#### Özet

- Ağ hataları yeniden denenebilir ve denenemez olarak saf bir işlevle sınıflandırılır; deneme sayısı bir bütçeyle sınırlanır.
- Anahtarsız bir isteği zaman aşımından sonra yeniden denemek sunucuda ikinci kaydı doğurdu.
- Aynı yeniden denemede etkisizlik anahtarlı istek tek kayıt bıraktı.
- Bütçeyi harcamak yinelemeye duyarlıdır, ama bu tasarımdır, hata değil.
- İstemci, yanıt kaybı ile isteğin ulaşmamasını gözleminden ayıramaz.
- Bu dersin duyarlılığı: **yinelemeye duyarlı**.

#### Sonraki Adım

Yeniden denenen bir istek bazen bir kimlik de taşır. Sıradaki derste Kimlik Doğrulama, iki eşzamanlı belirteç yenilemesinin yanıtlarının farklı sırayla saklanmasının eski belirteci nasıl geri getirebildiğini ölçecek.

### Kimlik Doğrulama

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/kimlik-dogrulama

Taşıyıcı belirteçlerin saklanması ve yenilenmesi; iki yenileme yanıtının farklı sırayla saklanmasının eski belirteci geri getirip getirmediğinin ölçümü.

Çoğu arka uç isteği **kimlik doğrulama** (authentication) ister. İstemci her isteğe bir **taşıyıcı belirteç** (bearer token) koyar: sunucu belirteci taşıyanı yetkili sayar. Belirteç kısa ömürlüdür; süresi dolunca istemci uzun ömürlü bir **yenileme belirteci** ile yenisini ister. Belirteçlerden kısa ömürlü olana **erişim belirteci** denir.

Bu mekanizma iki yerde duyarlıdır. Birincisi saklamadır: yenileme yanıtları ters sırayla gelirse hangisinin saklandığı önemlidir. İkincisi sunucudur: yenileme belirteci genellikle tek kullanımlıktır ve ikinci kullanımı reddedilir. Çekirdek aynıdır.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

#### Belirteç Saklama

Model, belirteci bir sayaçla damgalar: `serial` belirtecin kaçıncı yenileme olduğunu söyler. Süre gerçek saat yerine bu sayaçla modellenir; böylece ölçüm saate bağlı olmaz.

```swift
// Vault.swift — belirtec, kasa ve sunucunun yenileme durumu
struct Token: Equatable {
    let value: String
    let serial: Int
}

struct Vault: Equatable, CustomStringConvertible {
    var current: Token?
    var description: String { current.map { "\($0.value)#\($0.serial)" } ?? "bos" }
}

func store(_ token: Token) -> (Vault) -> Vault {
    { _ in Vault(current: token) }
}

func storeNewer(_ token: Token) -> (Vault) -> Vault {
    { vault in
        guard let held = vault.current, held.serial >= token.serial else {
            return Vault(current: token)
        }
        return vault
    }
}

struct Issuer: Equatable, CustomStringConvertible {
    var valid: String
    var issued: Int = 1
    var rejected: Int = 0
    var description: String { "gecerli=\(valid) verilen=\(issued) reddedilen=\(rejected)" }
}

func redeem(_ refresh: String) -> (Issuer) -> Issuer {
    { issuer in
        guard refresh == issuer.valid else {
            return Issuer(valid: issuer.valid, issued: issuer.issued, rejected: issuer.rejected + 1)
        }
        return Issuer(valid: "r\(issuer.issued + 1)", issued: issuer.issued + 1, rejected: issuer.rejected)
    }
}
```

`store` gelen belirteci koşulsuz saklar; son yazan kazanır. `storeNewer` yalnız daha büyük damgalı belirteci saklar; eşit ya da küçük damgalı gelen yok sayılır. Aşağıdaki ölçümde iki yenileme yanıtı vardır: eski olan `a1` birinci, yeni olan `a2` ikinci yenilemeden gelir. İki eşzamanlı yenileme isteği aynı anda yola çıkarsa yanıtların hangi sırayla döneceği belli değildir.

```swift
// main.swift — iki yenileme yanitinin saklanmasi
let stale = Token(value: "a1", serial: 1)
let fresh = Token(value: "a2", serial: 2)

let lastWins = replay("son-yazan-kazanir", from: Vault(), store(stale), then: store(fresh)).report()
let newerWins = replay("buyuk-damga-kazanir", from: Vault(), storeNewer(stale), then: storeNewer(fresh)).report()

countSensitivities([lastWins, newerWins])
```

```
son-yazan-kazanir | bir=a1#1 iki=a1#1 ab=a2#2 ba=a1#1 -> siraya-duyarli
buyuk-damga-kazanir | bir=a1#1 iki=a1#1 ab=a2#2 ba=a2#2 -> saglam
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

`son-yazan-kazanir` sıraya duyarlıdır. Eski yanıt sonra gelirse `ba` sütunundaki gibi kasada `a1` kalır; uygulama süresi dolmuş ya da yenisiyle değiştirilmiş eski bir belirteçle istek atmaya devam eder. `buyuk-damga-kazanir` sağlamdır: iki sırada da `a2` kalır ve aynı yanıtı iki kez saklamak bir şey değiştirmez.

**AG10.** Model sunucunun her yenilemede artan bir damga verdiğini varsayar. Damga vermeyen bir sunucuda istemci sırayı kendi başına kuramaz.

**AG11.** Eski belirteç sunucuda hâlâ geçerli olabilir ya da olmayabilir. Ölçüm yalnız istemcinin neyi sakladığına bakar.

#### İstekte Belirteç ve Tek Uçuş

Taşıyıcı belirteç isteğe `Authorization` başlığıyla eklenir.

```swift
import Foundation

func authorize(_ request: URLRequest, token: String) -> URLRequest {
    var signed = request
    signed.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    return signed
}
```

Bu parça KAYNAK kalır. Sıra sorununu saklamadan önce çözmenin başka bir yolu **tek uçuştur** (single flight): aynı anda yalnız bir yenileme yürür, öteki çağıranlar o yenilemenin sonucunu bekler. Bir **aktör** bunu doğal biçimde kurar.

```swift
// belirtec saglayici — KAYNAK
actor TokenProvider {
    private var refreshTask: Task<String, Error>?

    func validToken(refresh: @escaping @Sendable () async throws -> String) async throws -> String {
        if let task = refreshTask { return try await task.value }
        let task = Task { try await refresh() }
        refreshTask = task
        defer { refreshTask = nil }
        return try await task.value
    }
}
```

Bu parça da ölçülmez. İkinci çağıran `refreshTask`'ı dolu bulur ve yeni bir yenileme başlatmaz. Ancak aktörün durumu yalnız bu `await` noktaları arasında tutarlıdır; yenileme sürerken kasaya başka yerden yazılırsa aynı sıra sorunu geri döner.

#### Erişim ve Yenileme Belirtecinin Ömrü

İki belirteç ayrı işler görür. Erişim belirteci her istekle gider; ömrü kısadır, çünkü çalınırsa zararı da kısa sürer. Yenileme belirteci yalnız yenileme isteğinde kullanılır; ömrü uzundur, çünkü kullanıcının her yarım saatte bir yeniden giriş yapmasını önler. Uzun ömürlü olan daha az yerde, daha güvenli saklanır. Uygulamalarda yenileme belirteci genellikle sistemin anahtar deposunda durur; erişim belirteci ise bellekte tutulabilir, çünkü kaybolursa yenilemeyle yeniden alınır.

Bu ayrım ölçümün neden iki yenileme yanıtına bakmasını açıklar. Erişim belirtecinin süresi dolduğunda istemci yenileme belirteciyle yeni bir çift ister. Birden fazla istek aynı anda süresi dolmuş belirteçle yola çıkmışsa, hepsi 401 alır ve hepsi yenilemek ister. Sunucuya aynı yenileme belirteciyle iki istek ulaşır ve yanıtlardan biri ötekinden önce döner. İki yanıt iki ayrı çift taşır; istemci hangisini sakladığına göre ya taze, ya bayat belirteçle devam eder.

#### Gerçek Uygulamada Nerede Görülür

Bir ana sayfa açıldığında uygulama aynı anda profil, bildirim sayısı ve öneri listesi için üç istek gönderir. Erişim belirtecinin süresi o anda dolmuşsa üçü de 401 döner. Saf bir istemci üç ayrı yenileme isteği gönderir. Sunucu yenileme belirteçlerini bir kez kullanımlık tutuyorsa ilki başarılı olur, öteki ikisi reddedilir ve kullanıcı oturumunun kapandığını görür. Bu, ölçümdeki `belirtec-donusumu` satırının gündelik biçimidir: ilk kullanım belirteci tüketir ve ikincisi reddedilir.

Sorun kullanıcıya bir hata olarak görünmeyebilir. Uygulama sessizce giriş ekranına dönmüş olabilir ve kullanıcı "kendiliğinden çıkış yaptı" diye şikâyet eder. Günlüklerde yalnız "yenileme reddedildi" satırı görünür. Gerçek neden, yenilemenin birden fazla kez başlatılmasıdır. Bu yüzden aynı anda yalnız bir yenilemenin sürmesini sağlayan bir kural gerekir. Bu kurala **tek uçuş** (single flight) denir: yenileme sürerken gelen her istek yeni bir yenileme başlatmaz, süren yenilemenin sonucunu bekler.

#### Yanlış Kurulum ve Düzeltmesi

Yanlış kurulumda her başarısız istek kendi yenilemesini başlatır.

```swift
import Foundation

// Yanlış: üç eşzamanlı 401 üç ayrı yenileme başlatır
func refreshAccessToken(using session: URLSession, refresh: String) async throws -> String {
    var request = URLRequest(url: URL(string: "https://api.example.com/refresh")!)
    request.httpMethod = "POST"
    request.httpBody = Data(refresh.utf8)
    let (data, _) = try await session.data(for: request)
    return String(decoding: data, as: UTF8.self)
}
```

Bu parça KAYNAK kalır. Düzeltme, yenilemeyi tek bir yerde toplamaktır: bir aktör, süren yenilemeyi bir görev olarak tutar ve ona gelen her çağrıya aynı görevin sonucunu verir.

```swift
import Foundation

actor RefreshCoordinator {
    private var inFlight: Task<String, Error>?

    func freshToken(using perform: @escaping () async throws -> String) async throws -> String {
        if let inFlight { return try await inFlight.value }
        let task = Task { try await perform() }
        inFlight = task
        defer { inFlight = nil }
        return try await task.value
    }
}
```

Bu da KAYNAK kalır. Aktör, kendi durumuna aynı anda tek bir çağrının dokunmasını garanti eder; bu yüzden `inFlight` iki çağrı arasında yarışmaz. İlk çağrı görevi başlatır, sonrakiler bekler ve hepsi aynı belirteçle devam eder. Çözüm yinelemeyi sıfırlar: sunucuya tek bir yenileme ulaşır. Ölçümdeki `belirtec-donusumu` satırı bunun için bir koruma sağlamaz, çünkü model sunucunun yaptığını kurar; korumayı uygulayan taraf istemcidir.

Yine de bu çözümün sınırı vardır. Tek uçuş eşzamanlı yenilemeyi önler; iki yanıtın saklanma sırasını önlemez. Bir önceki yenilemeden kalma geç bir yanıt, yenisinden sonra saklanırsa hâlâ bayat belirteci kasaya yazabilir. Bu yüzden `storeNewer` benzeri bir damga karşılaştırması da gerekir: iki koruma ayrı iki sorunu çözer.

#### Belirteç Nerede Saklanır

Modeldeki `Vault` bir kasadır; gerçek uygulamada karşılığı iki katmanlıdır. Bellek, uygulamanın çalıştığı süre boyunca hızlı erişim sağlar ve uygulama kapanınca kaybolur. Sistemin anahtar deposu ise kapanmadan sonra da kalır ve verisini şifreli tutar. Belirteci kullanıcı tercihleri gibi düz bir depoya yazmak yaygın ve ciddi bir hatadır: düz depo, yedeklere ve dosya dökümlerine olduğu gibi girer. Sonraki konuda anahtar deposu ayrıca ele alınacak; burada önemli olan, saklamanın bir yazma işlemi olduğu ve yazma işleminin de bu kursun bütün ölçüsüne tabi olduğudur.

Saklama sırasındaki ince nokta, anahtar deposuna yazmanın eşzamansız bir iş olabilmesidir. İki yenileme yanıtı art arda gelirse iki yazma da yola çıkar ve depoya hangisinin sonra ulaştığı, hangisinin sonra çağrıldığıyla aynı olmayabilir. Ölçümdeki `son-yazan-kazanir` satırı bu yüzden gerçekçidir: model, yazmanın kendisini sıraya duyarlı bir işlem olarak kurar. Düzeltme, sıra bilgisini değerin kendisinde taşımaktır; `buyuk-damga-kazanir` satırının sağlam çıkmasının nedeni budur.

#### Çıkış Yapmak

Kullanıcı çıkış yaptığında istemci iki şey yapar: kasadaki belirteçleri siler ve sunucuya yenileme belirtecini iptal ettirir. Silme yerel bir işlemdir ve yinelemeye karşı sağlamdır: boş bir kasayı bir kez daha boşaltmak bir şey değiştirmez. İptal isteği ise sunucuya gider ve yeniden denenebilir olmalıdır; bu yüzden iptal, etkisiz bir işlem olarak tasarlanır. Çıkış sırasında hâlâ süren bir yenileme varsa, geç dönen yanıt kasayı yeniden doldurabilir. Kullanıcı çıkış yapmışken oturumun geri gelmesi, sıra sorununun en rahatsız edici biçimidir. Bunu önlemek için çıkış, süren yenilemeyi iptal eder ve dönen yanıtı saklamadan önce oturumun hâlâ açık olup olmadığına bakılır.

#### Yenilemenin Kendisi Başarısız Olursa

Yenileme isteği de bir istektir ve başarısız olabilir. Burada iki durumu ayırmak gerekir. Yenileme belirteci reddedilmişse, yani sunucu yanıt verip belirteci geçersiz saymışsa, oturum gerçekten sona ermiştir ve kullanıcının yeniden giriş yapması gerekir. Yenileme isteği ise ağ hatasıyla düşmüşse oturum sağlamdır; yalnız bağlantı geçicidir. Bu iki durumu karıştıran istemci, bağlantısı kısa süre kopan kullanıcıyı gereksiz yere oturumdan atar.

Doğru davranış üçüncü dersteki sınıflandırmayı burada da uygulamaktır. Zaman aşımı ve bağlantı yokluğu denenebilir hatalardır; yenileme bütçe dâhilinde yeniden denenir ve kasadaki belirteç silinmez. Sunucunun açıkça reddi ise kasayı boşaltır ve kullanıcıyı giriş ekranına yönlendirir. `belirtec-donusumu` satırındaki reddedilen sayacı bu ikinci durumun modelidir. Sayacın sıfırdan büyük olması, sunucunun bir yenileme belirtecini reddettiğini gösterir; istemci bunu gördüğünde yeniden denemek yerine oturumu sonlandırmalıdır.

#### Süre Dolmadan Yenilemek

Yenileme iki zamanda yapılabilir. Tepkisel yaklaşımda istek `401` alınca yenilenir; kod basittir, ama her süre dolumunda bir istek boşa gider ve aynı anda yola çıkmış tüm istekler başarısız olur. Önceden yenileme yaklaşımında erişim belirtecinin süresi dolmadan kısa süre önce yenilenir; böylece `401` hiç görülmez. Bu, sunucunun süre bilgisini yanıtta vermesini gerektirir ve istemci saatine güvenir. Saat kaymışsa belirteç gerçekte henüz geçerliyken yenilenir ya da süresi dolmuşken geçerli sanılır.

Pratikte ikisi birlikte kullanılır: önceden yenileme çoğu hatayı önler, tepkisel yenileme saat kayması gibi kaçan durumları yakalar. İki yolun da tek uçuş kuralından geçmesi gerekir; yoksa önceden yenileme ile tepkisel yenileme aynı anda yola çıkıp aynı yenileme belirtecini iki kez kullanır.

#### Sık Yapılan Hatalar

- Yenileme yanıtını koşulsuz saklamak. Geç gelen eski yanıt yeni belirteci ezer.
- Her başarısız isteğin ardından ayrı bir yenileme başlatmak. İstekler aynı anda `401` alırsa birden çok yenileme yarışır.
- Yenileme belirtecini ikinci kez kullanmak. Sunucu bunu reddedebilir ve oturumu da kapatabilir.
- Belirteci günlüğe yazmak ya da hata iletisine koymak.
- Süre dolmasını istemci saatine bağlamak. Saat kayması yanlış kararlar doğurur.

#### Ölçülen Sonucun Yorumu

İki saklama kuralı aynı iki yanıtı aldı ve farklı sonuç verdi. Fark yanıtlarda değil, kuralda: biri sıraya baktı, öteki damgaya. Damga sırayı yanıtın içine taşıdığı için varış sırası önemsizleşti. Bu, ağda sık kullanılan bir kalıptır: sırayı ağdan beklemek yerine veriye yazmak.

#### Sınırlayıcı Ölçüm

Saklama sağlam olsa da yenileme belirteci hâlâ yinelemeye karşı korumasızdır. Aşağıda aynı yenileme belirteci sunucuda iki kez kullanılır.

```swift
// main.swift — ayni yenileme belirtecinin ikinci kullanimi
let reuse = replay("belirtec-donusumu", from: Issuer(valid: "r1"), redeem("r1"), then: redeem("r1")).report()
countSensitivities([reuse])
```

```
belirtec-donusumu | bir=gecerli=r2 verilen=2 reddedilen=0 iki=gecerli=r2 verilen=2 reddedilen=1 ab=gecerli=r2 verilen=2 reddedilen=1 ba=gecerli=r2 verilen=2 reddedilen=1 -> yinelemeye-duyarli
saglam: 0
yinelemeye-duyarli: 1
siraya-duyarli: 0
cift-duyarli: 0
```

İlk kullanım `r1`'i tüketip `r2`'yi geçerli kılar; ikinci kullanım `r1`'i geçersiz bulur ve reddeder. Bu **belirteç dönüşümüdür** (token rotation) ve ölçümde yinelemeye duyarlı çıktı. İki sıra aynı sonucu verdi, çünkü iki işlem aynıydı. **Kural bu reddin bir güvenlik özelliği mi yoksa bir hata mı olduğunu söylemez.**

**AG12.** Sunucunun eski yenileme belirtecini hemen reddettiği varsayılır. Bazı sunucular kısa bir hoşgörü süresi tanır; bu davranış ölçülmedi.

#### Özet

- Erişim belirteci kısa ömürlüdür; yenileme belirteci uzun ömürlü olup yenisini almaya yarar.
- Yenileme yanıtlarını koşulsuz saklamak sıraya duyarlıdır; geç gelen eski yanıt yeni belirteci ezer.
- Damgası büyük olanı tutan saklama iki sırada da aynı belirteci bıraktı.
- Tek uçuş, aynı anda tek yenilemeyi çalıştırarak sorunu başka yerden çözer.
- Yenileme belirtecinin ikinci kullanımı reddedildi ve bu yinelemeye duyarlı çıktı.
- Bu dersin duyarlılığı: **sıraya duyarlı**.

#### Sonraki Adım

Belirteç ve yanıtlar geldikten sonra bazılarının saklanması gerekir. Sıradaki derste Görsel ve Veri Önbelleği, aynı işlem çiftinin boş bir önbellekte sağlam, dolu bir önbellekte ise sıraya duyarlı çıkıp çıkmadığını ölçecek.

### Görsel ve Veri Önbelleği

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/gorsel-ve-veri-onbellegi

Bellek ve disk önbelleğinin tahliye kuralı; aynı işlem çiftinin boş bir önbellekte sağlam, dolu bir önbellekte sıraya duyarlı çıkmasının ölçümü.

Aynı görseli her ekran açılışında yeniden indirmek hem veriyi hem pili harcar. **Önbellek** (cache) bu yüzden vardır: daha önce alınmış yanıtı yakında tutar. İki tür önbellek ayırt edilir. **Bellek önbelleği** hızlıdır ama uygulama kapanınca kaybolur. **Disk önbelleği** daha yavaştır ama uygulama kapandıktan sonra da durur. İkisi de sınırlıdır; sınır dolunca bir girdi **tahliye** edilir.

Soru şudur: önbelleğe yazmak ne zaman güvenlidir? Boş bir önbellekte neredeyse her yazma güvenlidir. Dolu bir önbellekte ise hangi girdinin atılacağı, işlemlerin sırasına bağlı olur. Çekirdek aynıdır.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

#### Kapasiteli Bir Önbellek

Model, kapasitesi sınırlı bir önbellektir ve en uzun süre kullanılmayan girdiyi atar. Girdiler "anahtar=değer" dizgileridir; listenin başındaki girdi en uzun süredir kullanılmayandır. Durum karşılaştırması ve çıktı, girdileri sıralı gösterir.

```swift
// Cache.swift — kapasiteli, en uzun suredir kullanilmayani atan onbellek
struct Cache: Equatable, CustomStringConvertible {
    var capacity: Int
    var entries: [String] = []

    static func == (lhs: Cache, rhs: Cache) -> Bool {
        lhs.capacity == rhs.capacity && lhs.entries.sorted() == rhs.entries.sorted()
    }

    var description: String { "[" + entries.sorted().joined(separator: ",") + "]" }
}

func entryKey(_ entry: String) -> String {
    String(entry.split(separator: "=", maxSplits: 1).first ?? "")
}

func put(_ key: String, _ value: String) -> (Cache) -> Cache {
    { cache in
        var next = cache
        next.entries.removeAll { entryKey($0) == key }
        next.entries.append("\(key)=\(value)")
        while next.entries.count > next.capacity { next.entries.removeFirst() }
        return next
    }
}

func get(_ key: String) -> (Cache) -> Cache {
    { cache in
        var next = cache
        if let index = next.entries.firstIndex(where: { entryKey($0) == key }) {
            let entry = next.entries.remove(at: index)
            next.entries.append(entry)
        }
        return next
    }
}
```

`put` aynı anahtarlı eski girdiyi çıkarır, yenisini sona ekler ve kapasite aşılırsa baştakini atar. `get` bulduğu girdiyi sona taşır; okumak da bir kullanımdır. **Model, işlemin duruma ne yaptığını kurar; sistemin önbelleğinin ya da diskin kendisi değildir.** `==` yalnız girdilerin içeriğini karşılaştırır; hangi girdinin ne kadar eski olduğu görünmez.

#### Boş Önbellekte Üç İşlem

```swift
// main.swift — bos onbellekte uc islem
let empty = Cache(capacity: 2)

let sameKey = replay("ayni-anahtar", from: empty, put("profil", "v1"), then: put("profil", "v1")).report()
let twoKeys = replay("iki-anahtar", from: empty, put("profil", "v1"), then: put("akis", "v1")).report()
let writeRead = replay("yaz-oku", from: empty, put("gorsel", "v1"), then: get("profil")).report()

countSensitivities([sameKey, twoKeys, writeRead])
```

```
ayni-anahtar | bir=[profil=v1] iki=[profil=v1] ab=[profil=v1] ba=[profil=v1] -> saglam
iki-anahtar | bir=[profil=v1] iki=[profil=v1] ab=[akis=v1,profil=v1] ba=[akis=v1,profil=v1] -> saglam
yaz-oku | bir=[gorsel=v1] iki=[gorsel=v1] ab=[gorsel=v1] ba=[gorsel=v1] -> saglam
saglam: 3
yinelemeye-duyarli: 0
siraya-duyarli: 0
cift-duyarli: 0
```

Üç işlem de sağlamdır. Aynı anahtara aynı veriyi iki kez yazmak içeriği değiştirmez. İki farklı anahtar, kapasiteyi aşmadığı sürece iki sırada da aynı içeriği bırakır. Olmayan bir anahtarı okumak hiçbir şey yapmaz. Bu bölümde önbellek bir sağlamlık örneğidir ve çoğu zaman da öyledir; bu yüzden önbelleğe yazmak kolay bir iş sanılır.

**AG13.** Ölçüm kapasiteyi 2 alır. Başka kapasitelerde hangi işlem çiftlerinin dolduracağı değişir.

**AG14.** Durum karşılaştırması girdilerin yaş sırasını görmez. Sırası farklı ama içeriği aynı iki önbellek eşit sayılır.

#### Sistemin Önbellekleri

Bellek önbelleği için `NSCache`, ağ yanıtlarının disk ve bellek önbelleği için `URLCache` kullanılır.

```swift
import Foundation

let memory = NSCache<NSString, NSData>()
memory.countLimit = 100
memory.setObject(Data([1, 2, 3]) as NSData, forKey: "profil" as NSString)
let entry = memory.object(forKey: "profil" as NSString)
```

```swift
import Foundation

let urlCache = URLCache(memoryCapacity: 20_000_000, diskCapacity: 100_000_000, directory: nil)
let configuration = URLSessionConfiguration.default
configuration.urlCache = urlCache
configuration.requestCachePolicy = .returnCacheDataElseLoad
```

İki parça da KAYNAK kalır. `NSCache` bellek baskısında girdileri kendisi atabilir ve hangi sırayla atacağını belgelemez; bu yüzden yukarıdaki model onun tahliye politikasını taklit etmez, dersin kendi kapasite kuralını kurar. `URLCache` yanıtları bellekte ve diskte tutar; hangi yanıtın önbelleğe alınacağını sunucunun başlıkları da etkiler. `returnCacheDataElseLoad` önbellekte bir şey varsa ağa gitmeden onu verir.

#### Önbelleğin Neden Sağlam Göründüğü

Üç işlemin üçü de boş bir önbellekte sağlam çıktı. Bu bir önbelleğin her zaman sağlam olduğu anlamına gelmez; yalnız boşken kapasite hiç devreye girmediği için tahliye yok demektir. Boş bir önbellek, aynı anahtarı iki kez yazınca tek girdi tutar ve iki farklı anahtarı hangi sırayla yazarsanız yazın aynı kümeyi verir. Kapasite dolduğunda ise yeni bir girdi eskisini atar ve atılan girdi, işlemlerin sırasına göre değişir. Dersin sınırlayıcı ölçümü tam bunu gösteriyor: aynı işlem çifti dolu önbellekte sıraya duyarlı çıkar.

Bu iki durumun farkını bilmek bir test stratejisini de belirler. Önbelleği boş bir başlangıçla sınayan bir test, sorunun kapasite aşıldığında başladığını görmez. Gerçek bir hata ise çoğunlukla uzun bir kullanımdan sonra, önbellek dolduğunda ortaya çıkar: kullanıcı yüz kadar görsel gezmiş, ilk açtığı görsel tahliye edilmiş ve geri döndüğünde yeniden indirilmiştir. Hatanın niteliği doğruluk değil, performanstır; ama sıra duyarlılığı, hangi girdinin kaybolduğunu belirler.

#### Gerçek Uygulamada Nerede Görülür

Bir akış ekranı, her satır için küçük bir görsel indirir. Kullanıcı aşağı kaydırdıkça görseller tahliye edilir; yukarı döndüğünde aynı görseller yeniden gerekir. Önbellek yoksa her kaydırma yeniden indirmedir: veri harcanır, ekran titrer. Önbellek varsa, yukarı dönüş anlıktır. Kapasite ise iki kuvvetin dengesidir. Çok büyük bir önbellek belleği şişirir ve sistem uygulamayı sonlandırır; çok küçük bir önbellek, ekranın yarısını yeniden indirir.

Bayatlık ikinci bir sorundur. Profil resmini değiştiren bir kullanıcı, eski resmin önbellekte durduğunu görürse değişikliğin kaydedilmediğini düşünür. Bunun için iki yaygın çözüm vardır: resmin adresine bir sürüm eklemek, böylece adres değişince eski girdi hiç kullanılmaz; ya da değiştirme anında ilgili girdiyi açıkça silmek. İkisi de ölçümdeki `put` işleminin yinelemeye karşı sağlam olmasına dayanır: aynı anahtarı yeniden yazmak eski girdiyi değiştirir, çoğaltmaz.

#### Yanlış Kurulum ve Düzeltmesi

Yaygın yanlış kurulum, görsel adresini önbellek anahtarı olarak kullanıp sorgu parametrelerini hesaba katmamaktır.

```swift
import Foundation

// Yanlış: anahtar yalnız yol; aynı yola giden farklı boyutlar çakışır
func cacheKey(for url: URL) -> String {
    url.path
}

// Düzeltme: sorgu da anahtara girer; boyut ya da sürüm değişince anahtar değişir
func cacheKey(for url: URL, size: Int) -> String {
    "\(url.path)?\(url.query ?? "")#\(size)"
}
```

Bu parça KAYNAK kalır. İlk işlev `avatar.png?size=64` ile `avatar.png?size=512` adreslerini aynı girdiye eşler ve küçük görsel büyük ekranda bulanık görünür. İkinci işlev sorguyu ve boyutu anahtara katar. Ölçümdeki `iki-anahtar` satırı bunun modelidir: farklı anahtarlar birbirini etkilemez ve hangi sırayla yazılırlarsa yazılsınlar önbellek aynı kümeyi tutar.

İkinci yanlış, önbelleğe ağdan gelen her yanıtı olduğu gibi koymaktır. Hata sayfası da bir yanıttır; 404 yanıtı önbelleğe girerse görsel sunucuda düzelse bile istemci hatayı göstermeye devam eder. Yalnız başarılı yanıtlar ve çözülebilen görseller saklanmalıdır.

#### Sistem Önbelleklerinin Sınırı

Sistemin iki önbelleği vardır ve ikisi de farklı bir işi yapar. Bellek önbelleği, yani `NSCache`, bellek baskısında girdileri kendiliğinden atar; hangi girdinin atılacağı ve ne zaman atılacağı sistemin kararıdır. HTTP önbelleği, yani `URLCache`, yanıtları sunucunun gönderdiği başlıklara göre saklar ve süresi dolmadan yeniden kullanır. Modelin kurduğu en uzun süredir kullanılmayanı atan kural bu ikisinin birebir karşılığı değildir. Sistemin tahliye kararı ölçülmediği için bu davranış bir varsayım bloğuna girer.

#### Tahliye Kuralının Ne Sakladığı

Modeldeki kural, en uzun süredir kullanılmayanı atmaktır ve bu kuralın iki yönü vardır. Yazma, yeni bir girdiyi listenin sonuna koyar; okuma ise var olan bir girdiyi sona taşır. Bu yüzden okuma, görünürde değişmeyen bir işlem olsa bile önbelleğin durumunu değiştirir. `yaz-oku` satırı bunu boş önbellekte göstermedi, çünkü okunan anahtar yoktu. Dolu önbellekte ise okuma, hangi girdinin sonraki tahliyeden kurtulacağını belirler. Sınırlayıcı ölçümdeki `ba` sütunu tam olarak bunu gösterir: `a` önce okunduğu için en yeni olur ve atılan `b` olur.

Bu davranışın gerçek uygulamadaki karşılığı şudur: bir görseli göstermek de onu önbellekte canlı tutar. Kullanıcının sık baktığı profil resmi, yalnız bir kez gösterilen akış görsellerinden daha uzun yaşar. Kural basittir ve çoğu iş yükünde iyi sonuç verir. Ama bir tarama gibi her girdiyi yalnız bir kez okuyan bir iş, sık kullanılan girdileri de dışarı atabilir. Yüzlerce görseli art arda gezen bir kullanıcı, başlangıçta önbellekte duran profil resmini ve simgeleri tahliye eder; geri döndüğünde hepsi yeniden indirilir.

#### Kapasitenin Seçimi

Kapasite iki kaynak arasında bir uzlaşmadır. Büyük seçilirse bellek tüketimi artar; sistem bellek baskısı altında uygulamayı sonlandırabilir ve önbelleğin sağladığı kazanç bir çöküşe dönüşür. Küçük seçilirse isabet oranı düşer ve önbellek neredeyse hiç iş görmez. İyi bir başlangıç, girdi sayısını değil toplam boyutu sınırlamaktır: yüz küçük simge ile yüz büyük fotoğraf aynı belleği harcamaz. Sistemin bellek önbelleği hem sayı hem de maliyet sınırı alır; modeldeki yalnız sayıya bağlı kapasite bunun basit bir karşılığıdır.

Disk önbelleği için de ayrı bir sınır belirlenir. Disk büyük ve ucuz görünür, ama kullanıcının telefonundaki yer ortak bir kaynaktır ve sistem diski boşaltmak için önbellek dosyalarını silebilir. Bu yüzden diskteki önbellek, kaybolması uygulamayı bozmayan veriyi tutmalıdır; kaybolunca yeniden indirilebilen görseller buna uygun, kullanıcının oluşturduğu içerik uygun değildir.

#### Bayat Girdi ve Geçerlilik

Önbelleğin en güvenilmez yanı, tuttuğu verinin sunucudaki gerçeğin gerisinde kalabilmesidir. Bunu yönetmenin üç yaygın yolu vardır. İlki ömür sınırıdır: her girdiye bir son kullanma zamanı konur ve süresi dolan girdi okunmaz. İkincisi doğrulamadır: istemci girdiyi kullanmadan önce sunucuya değişip değişmediğini sorar ve değişmediyse yanıt kısa bir onayla döner. Üçüncüsü anahtara sürüm eklemektir; içerik değişince adres de değişir ve eski girdi sahipsiz kalır, sonunda tahliyeyle silinir.

Hangisinin seçileceği verinin ne kadar sık değiştiğine bağlıdır. Hiç değişmeyen bir görsel için sürümlü adres yeterlidir. Sık değişen bir liste için kısa bir ömür sınırı ve doğrulama birlikte kullanılır. Hiçbiri tek başına yeterli değildir: ömür sınırı gereksiz yere veri indirtir, doğrulama her seferinde bir istek gönderir, sürümlü adres ise sunucunun adresleri üretmesini gerektirir.

#### Önbelleği Sınamak

Önbellekli kodu sınamak, boş önbellekle yetinmemeyi gerektirir. Sağlam çıkan bir ölçümün buradaki uyarısı budur: boş durumda her işlem güvenlidir. İyi bir sınama önbelleği kapasitesine kadar doldurur, sonra tahliyeyi tetikleyen bir işlem ekler ve hangi girdinin kaldığını denetler. Okumanın girdiyi canlı tuttuğunu ve yazmanın en eskiyi attığını ayrı ayrı doğrulamak gerekir. Bu iki davranışı ayrı sınamadan bırakan bir ekip, tahliye sırasındaki bir hatayı ancak üretimde, kullanıcı şikâyet ettiğinde öğrenir.

Zamana bağlı kurallar için gerçek saat yerine sahte bir saat kullanılır; ömür sınırı dolmuş bir girdiyi sınamak için testin saniyelerce beklemesi gerekmez. Dersin modelinin süreyi sayaçla ya da sıralamayla kurması, bu bakımdan da bir sınama kolaylığıdır.

#### Sık Yapılan Hatalar

- Önbelleği sınırsız bırakmak. Bellek önbelleği şişer ve sistem uygulamayı sonlandırabilir.
- Önbellekteki değeri tek doğruluk kaynağı saymak. Önbellek her zaman bayat olabilir.
- Disk önbelleğindeki dosyaları kullanıcının silemeyeceği önemli veri gibi saklamak. Sistem diski boşaltmak için onları siler.
- Sistem önbelleğinin tahliye sırasına güvenmek. Sıra belgelenmemiştir.
- Anahtara yalnız adresi koymak ve sürümü ya da boyutu katmamak. Aynı adresin iki boyutu karışır.

#### Ölçülen Sonucun Yorumu

Üç işlem de sağlam çıktı. Bu bir sonuç değil, bir başlangıç noktasıdır: işlemlerin hiçbiri önbelleğin sınırına dokunmadı. Önbelleğin ayırt edici özelliği sınırıdır ve sınır dolduğunda yazma artık yalnız bir ekleme olmaz, bir çıkarma da olur.

#### Sınırlayıcı Ölçüm

Aşağıda aynı iki işlem, `c` anahtarını yazmak ve `a` anahtarını okumak, iki önbellekte uygulanır. Biri boştur, öteki kapasitesine kadar doludur ve `a` en uzun süredir kullanılmayan girdidir.

```swift
// main.swift — ayni islem cifti, bos ve dolu onbellek
let full = put("b", "2")(put("a", "1")(Cache(capacity: 2)))

let onEmpty = replay("bos-onbellek", from: Cache(capacity: 2), put("c", "3"), then: get("a")).report()
let onFull = replay("dolu-onbellek", from: full, put("c", "3"), then: get("a")).report()

countSensitivities([onEmpty, onFull])
```

```
bos-onbellek | bir=[c=3] iki=[c=3] ab=[c=3] ba=[c=3] -> saglam
dolu-onbellek | bir=[b=2,c=3] iki=[b=2,c=3] ab=[b=2,c=3] ba=[a=1,c=3] -> siraya-duyarli
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

Boş önbellekte `a` yoktur; okuma bir şey yapmaz ve iki sıra aynı sonucu verir. Dolu önbellekte sıra belirleyicidir. Önce yazılırsa `c` en eski girdi olan `a`'yı atar ve okuma boşa gider. Önce okunursa `a` en yeni olur ve yazma `b`'yi atar. İki sıra farklı içerik bırakır. **Kural önbelleğin dolu olduğunu bilmez, yalnız iki sonucun farklı olduğunu görür.**

**AG15.** Sistemin bellek önbelleği için bu sıra davranışı varsayılmaz. Gerçek bir `NSCache` girdileri kendi politikasıyla atar ve ölçülen sıra yalnız dersin modeli için geçerlidir.

#### Özet

- Bellek önbelleği hızlı ve geçicidir; disk önbelleği daha yavaş ama kalıcıdır; ikisi de sınırlıdır ve dolunca tahliye eder.
- Aynı anahtarla aynı veriyi yazmak ve iki farklı anahtar yazmak boş önbellekte sağlam çıktı.
- Dolu önbellekte yazma ile okumanın sırası hangi girdinin atılacağını belirledi.
- Aynı işlem çifti boş önbellekte sağlam, dolu önbellekte sıraya duyarlıdır.
- Sistem önbelleğinin tahliye sırası belgelenmemiştir; model dersin kendi kapasite kuralıdır.
- Bu dersin duyarlılığı: **sağlam**.

#### Sonraki Adım

Önbellek, uygulama açıkken çalışan bir mekanizmadır. Sıradaki derste Arka Plan Aktarımı, uygulama arka plandayken sistemin sürdürdüğü indirmelerin yeniden kuyruğa alınmasının aynı dosyayı neden iki kez indirebildiğini ölçecek.

### Arka Plan Aktarımı

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/arka-plan-aktarimi

Uygulama arka plandayken sistemin sürdürdüğü indirmeler; yeniden açılışta listeyi denetlemeden yeniden kuyruğa almanın aynı dosyayı iki kez indirmesinin ölçümü.

Kullanıcı büyük bir dosya indirmeye başlayıp uygulamadan çıkabilir. Normal bir oturumda uygulama arka plana geçince aktarım kesilir. **Arka plan aktarımı** (background transfer) bunu değiştirir: aktarımı sistem yürütür ve uygulama sistem tarafından sonlandırılsa bile sürdürür. Bitince ya da gerektiğinde uygulamayı uyandırır.

Bu düzenin bir sonucu var: uygulama yeniden açıldığında bazı aktarımlar zaten sürüyordur. Uygulamanın kendi listesi bu durumu bilmez. Listeyi olduğu gibi yeniden kuyruğa almak, sistemin zaten yürüttüğü bir aktarımı ikinci kez başlatmak olabilir. Çekirdek aynıdır.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

#### Aktarım Kaydı

Model, uygulamanın bildiği aktarım kaydını kurar. Her aktarım bir tanımlayıcıyla anılır ve kayıt sıralı tutulur. İki kuyruğa alma biçimi karşılaştırılır: tanımlayıcıya bakmadan ekleyen ve önce bakan.

```swift
// Transfers.swift — aktarim kaydi ve iki kuyruga alma bicimi
struct Registry: Equatable, CustomStringConvertible {
    var tasks: [String] = []
    var description: String { "[" + tasks.joined(separator: ",") + "]" }
}

func enqueueAll(_ files: [String]) -> (Registry) -> Registry {
    { registry in Registry(tasks: (registry.tasks + files).sorted()) }
}

func enqueueIfMissing(_ files: [String]) -> (Registry) -> Registry {
    { registry in
        let missing = files.filter { !registry.tasks.contains($0) }
        return Registry(tasks: (registry.tasks + missing).sorted())
    }
}

func finish(_ file: String) -> (Registry) -> Registry {
    { registry in Registry(tasks: registry.tasks.filter { $0 != file }) }
}
```

`enqueueAll` listedeki her dosya için yeni bir aktarım ekler. `enqueueIfMissing` yalnız kayıtta olmayanları ekler. `finish` bir aktarımın tamamlandığını kurar. **Model yalnız uygulamanın kayıt defterini kurar; sistemin aktarım yürütmesi ya da diskin kendisi değildir.** Ölçümde başlangıç durumu, uygulama kapanmışken sistemin `a.zip` indirmesini sürdürdüğü bir yeniden açılıştır.

```swift
// main.swift — yeniden acilista iki kuyruga alma bicimi
let reopened = Registry(tasks: ["a.zip"])
let wanted = ["a.zip"]

let blind = replay("denetlemeden-kuyruga-al", from: reopened, enqueueAll(wanted), then: enqueueAll(wanted)).report()
let byIdentifier = replay("tanimlayiciya-gore", from: reopened, enqueueIfMissing(wanted), then: enqueueIfMissing(wanted)).report()

countSensitivities([blind, byIdentifier])
```

```
denetlemeden-kuyruga-al | bir=[a.zip,a.zip] iki=[a.zip,a.zip,a.zip] ab=[a.zip,a.zip,a.zip] ba=[a.zip,a.zip,a.zip] -> yinelemeye-duyarli
tanimlayiciya-gore | bir=[a.zip] iki=[a.zip] ab=[a.zip] ba=[a.zip] -> saglam
saglam: 1
yinelemeye-duyarli: 1
siraya-duyarli: 0
cift-duyarli: 0
```

`denetlemeden-kuyruga-al` yinelemeye duyarlıdır. Sistem `a.zip`'i zaten yürütüyordu; liste yeniden kuyruğa alınınca ikinci bir `a.zip` kaydı oluştu ve bir daha alınınca üçüncüsü. Aynı dosya art arda iki kez indirilir. `tanimlayiciya-gore` sağlamdır: tanımlayıcı kayıtta olduğu için kuyruğa alma hiçbir şey eklemez, kaç kez yapılırsa yapılsın.

**AG16.** Model sistemin sürdürdüğü aktarımı uygulamanın kaydında bulunur sayar. Gerçekte uygulama kaydı kendisi sistemden sorgulayarak kurar.

**AG17.** Model aynı dosyanın iki kaydını iki ayrı indirme sayar. İki kayıt gerçek bir uygulamada aynı hedef dosyaya yazılırsa biri ötekinin üstüne yazabilir.

#### Arka Plan Oturumu

Arka plan aktarımı, tanımlayıcılı özel bir yapılandırmayla kurulur.

```swift
import Foundation

let configuration = URLSessionConfiguration.background(withIdentifier: "downloads")
configuration.sessionSendsLaunchEvents = true
let session = URLSession(configuration: configuration, delegate: downloader, delegateQueue: nil)

let task = session.downloadTask(with: URL(string: "https://files.example.com/a.zip")!)
task.taskDescription = "a.zip"
task.resume()
```

Bu parça KAYNAK kalır. Yapılandırmanın tanımlayıcısı, uygulama yeniden açıldığında aynı oturuma yeniden bağlanmayı sağlar. Yeniden bağlanan oturum, sürmekte olan görevleri sorgulayabilir.

```swift
import Foundation

func pendingFiles(of session: URLSession) async -> Set<String> {
    let tasks = await session.allTasks
    return Set(tasks.compactMap(\.taskDescription))
}
```

Bu da KAYNAK kalır. Model bu sorgunun sonucunu `Registry` olarak temsil eder; `enqueueIfMissing` bu kümeyle kuyruğa almadan önce karşılaştırır. Aktarım bitince sonucu bir **temsilci** alır ve dosya geçici konumdan kalıcı bir yere taşınmalıdır.

```swift
import UIKit

final class AppDelegate: UIResponder, UIApplicationDelegate {
    var completionHandlers: [String: () -> Void] = [:]

    func application(_ application: UIApplication,
                     handleEventsForBackgroundURLSession identifier: String,
                     completionHandler: @escaping () -> Void) {
        completionHandlers[identifier] = completionHandler
    }
}
```

Sistem uygulamayı arka planda uyandırırsa bu **uygulama temsilcisi** işleyicisini çağırır. Uygulama, olaylar bitince saklanan **kapanışı** çağırarak sisteme işini bitirdiğini bildirir.

#### Aktarımı Sistemin Yürütmesi

Arka plan oturumunun temel fikri, aktarımın uygulamanın ömrüne bağlı olmamasıdır. Sıradan bir oturumda bir indirme, uygulama arka plana geçtikten kısa süre sonra kesilir; çünkü sistem uygulamayı askıya alır. Arka plan oturumunda ise indirmeyi uygulamadan ayrı bir sistem süreci yürütür. Uygulama askıya alınsa, hatta kullanıcı tarafından sonlandırılmasa bile sistem tarafından kapatılsa, aktarım sürebilir. İş bitince sistem uygulamayı gerektiğinde uyandırır ve sonucu teslim eder.

Bu düzenin bedeli, uygulamanın artık aktarımın tek sahibi olmamasıdır. Aktarımın durumunu sistem tutar; uygulamanın elindeki liste yalnız bir kopyadır. Uygulama kapalıyken aktarım tamamlanmış, başarısız olmuş ya da hâlâ sürüyor olabilir ve uygulama yeniden açıldığında bu üç durumu sistemden sormadan bilemez. Modeldeki `Registry` uygulamanın bu kopyasını kurar; `denetlemeden-kuyruga-al` satırı, kopyanın gerçeğe uymadığı bir yeniden açılışta ne olduğunu gösterir.

#### Gerçek Uygulamada Nerede Görülür

Bir podcast uygulamasında kullanıcı on bölüm indirmek üzere kuyruğa alır ve telefonu cebine koyar. Uygulama arka plana geçer, sistem indirmeleri sürdürür. Kullanıcı bir saat sonra uygulamayı açtığında bölümlerin bir kısmı inmiş, bir kısmı sürüyordur. Uygulamanın açılışta yaptığı ilk iş, "indirilecekler" listesini yeniden kuyruğa almaksa, sürmekte olan bölümler ikinci kez başlatılır. Aynı dosya iki kez indirilir; veri tarifesi harcanır, pil tükenir ve iki aktarım aynı hedef dosyaya yazmaya çalışırsa dosya bozulabilir.

Bir ikinci durum kullanıcının uygulamayı kendisinin kapatmasıdır. Kullanıcı uygulamayı çoklu görev ekranından sonlandırırsa sistem oturumun arka plan aktarımlarını iptal eder ve uygulamayı kullanıcı yeniden açana kadar kendiliğinden başlatmaz. Bu davranış sistemde gerçekleştiği için burada ölçülmez; uygulama yeniden açıldığında hangi aktarımların hâlâ sürdüğünü sormak, bu iki durumu ayırmanın tek güvenli yoludur.

#### Yanlış Kurulum ve Düzeltmesi

Yanlış kurulum, açılışta kayıtlı listeyi doğrudan yeniden kuyruğa almaktır.

```swift
import Foundation

// Yanlış: açılışta liste olduğu gibi yeniden kuyruğa alınır
func resumeDownloads(_ files: [URL], using session: URLSession) {
    for file in files {
        let task = session.downloadTask(with: file)
        task.taskDescription = file.lastPathComponent
        task.resume()
    }
}
```

Bu parça KAYNAK kalır. Düzeltme, önce sistemden sürmekte olan görevleri sormak ve yalnız eksik olanları kuyruğa almaktır.

```swift
import Foundation

// Düzeltme: sürenleri sor, eksik olanları kuyruğa al
func resumeDownloads(_ files: [URL], using session: URLSession) async {
    let running = Set(await session.allTasks.compactMap(\.taskDescription))
    for file in files where !running.contains(file.lastPathComponent) {
        let task = session.downloadTask(with: file)
        task.taskDescription = file.lastPathComponent
        task.resume()
    }
}
```

Bu da KAYNAK kalır. `taskDescription` burada görev tanımlayıcısı olarak kullanılır: kuyruğa alınırken yazılır ve açılışta okunur. Ölçümdeki `tanimlayiciya-gore` satırı bunun modelidir: tanımlayıcı kayıtta varsa kuyruğa alma bir şey eklemez ve işlem kaç kez tekrarlanırsa tekrarlansın kayıt aynı kalır. Düzeltilmiş işlevin sağlamlığı yinelemeye karşıdır; sıraya karşı değildir. Bir aktarım biterken aynı anda kuyruğa alınırsa sonuç, ölçümde gördüğümüz gibi, işlemlerin sırasına bağlıdır.

#### Oturumun Yaşam Döngüsü

Arka plan oturumu tanımlayıcısıyla anılır ve bu tanımlayıcı oturumun kimliğidir. Uygulama aynı tanımlayıcıyla yeni bir oturum kurduğunda sistem, o tanımlayıcıya ait sürmekte olan görevleri yeni oturuma bağlar. Tanımlayıcı her açılışta farklı üretilirse bu bağ kurulamaz; eski görevler sahipsiz kalır ve uygulama yeni bir görev kümesiyle başlar. Ölçümdeki `tanimlayiciya-gore` satırı bu yüzden bir koşula bağlıdır: kayıt defterinin sistemin gerçek durumuyla uyumlu olması ve tanımlayıcının kalıcı olması.

Yaşam döngüsü şöyle işler. Uygulama görevi kuyruğa alır ve arka plana geçer. Sistem görevi yürütür. Görev biterse ya da kimlik doğrulama gibi bir olay gerekirse sistem uygulamayı arka planda başlatır ve oturumun temsilcisine olayları iletir. Temsilci olayları işledikten sonra, sistemin verdiği kapanışı çağırır. Bundan sonra sistem uygulamayı yeniden askıya alabilir. Kullanıcı uygulamayı kendisi açtığında ise aynı tanımlayıcıyla oturum kurulur ve bekleyen olaylar teslim edilir.

#### Yeniden Açılışta Denetim

Yeniden açılışın ilk işi, sistemin ne yaptığını sormaktır. Sürmekte olan görevlerin listesi alınır, tamamlanmış ancak henüz işlenmemiş sonuçlar temsilciye teslim edilir ve kayıt defteri bu bilgilerle güncellenir. Ancak bundan sonra eksik kalan dosyalar için yeni görevler kuyruğa alınır. Bu sıra `denetlemeden-kuyruga-al` ile `tanimlayiciya-gore` arasındaki farkın pratikteki karşılığıdır: birincisi listeyi kendisi doğru sanır, ikincisi sistemin gerçeğini sorar.

Denetim bir kez yapıldığında yeterli değildir. Uygulama açık kalırken de görevler tamamlanır, başarısız olur ya da iptal edilir. Kayıt defteri bu olaylarla güncel tutulmazsa bir sonraki açılışta eski bilgiyle denetim yapılır. `finish` işleminin modele konma nedeni budur: bitmiş bir aktarımın kayıttan çıkması gerekir, yoksa kayıt gerçekte olmayan bir aktarımı var sayar. Sınırlayıcı ölçümde gördüğümüz sıra duyarlılığı bu güncellemenin zamanlamasından doğar: bitiş bildirimi kuyruğa almadan önce ya da sonra gelebilir ve sonucu değiştirir.

#### Hata ve İptal

Arka plan aktarımı da başarısız olabilir: sunucu yanıt vermeyebilir, bağlantı kopabilir ya da dosya yarıda kesilebilir. Başarısız görev temsilciye bir hatayla bildirilir. Bazı hatalarda sistem, kesilen yerden devam etmek için gereken veriyi hatanın içinde verir. Uygulama bu veriyi saklayıp görevi kaldığı yerden başlatırsa aynı baytlar yeniden indirilmez. Bunu yapmayan uygulama, yarım kalan büyük bir dosyayı baştan indirir.

Kullanıcı bir aktarımı iptal ettiğinde kayıt defterinden de çıkarılması gerekir. İptal edilen görev kayıtta kalırsa bir sonraki açılışta denetim onu sürüyor sanar ve yeniden kuyruğa almaz; kullanıcı iptal ettiği dosyanın ne zaman inmeye başladığını sorar ya da dosyanın hiç inmediğini görür. Bu, kaydın sistemin durumunu yansıtması gerektiği ilkesinin bir başka yüzüdür.

#### Aktarılan Dosyanın Doğrulanması

İndirme bitti demek, dosyanın doğru olduğu demek değildir. Sunucu bir hata sayfasını başarılı bir yanıtla döndürmüş olabilir ya da dosya yarım kalmış olabilir. Taşımadan önce yanıtın durum kodu denetlenir, dosya boyutu beklenenle karşılaştırılır ve mümkünse bir özet değeri doğrulanır. Doğrulanmayan bir dosya kalıcı konuma alınırsa bozuk bir veri uzun süre fark edilmeden kalır. Kullanıcı bir video açtığında oynatılamadığını görür ve uygulamanın dosyayı neden bozuk indirdiğini anlayamaz.

#### İlerlemeyi Göstermek

Arka plan aktarımı sürerken kullanıcı uygulamayı açabilir ve aktarımın durumunu görmek ister. Temsilci, indirilen bayt sayısını düzenli aralıklarla bildirir; uygulama bu bilgiyi bir ilerleme çubuğuna bağlar. Burada da kayıt defterinin tek doğruluk kaynağı olmadığı akılda tutulmalıdır: uygulama yeniden açıldığında ilerleme bilgisi sistemden yeniden sorulmalıdır, çünkü uygulama kapalıyken bildirilen baytlar kayıt defterine hiç ulaşmamıştır.

İlerleme bilgisi her zaman kesin değildir. Sunucu toplam boyutu bildirmiyorsa yüzde hesaplanamaz ve çubuk belirsiz bir göstergeye döner. Bu durumda uydurma bir yüzde göstermek kullanıcıyı yanıltır; "indiriliyor" demek daha dürüsttür. Aktarım tamamlandığında bildirim göndermek de yaygındır, ama bildirim izni kullanıcıya aittir ve uygulama bildirim gösteremeyeceği bir durumda da çalışmaya devam etmelidir.

#### Ağ Koşullarına Bağlı Kısıtlar

Arka plan oturumunun yapılandırması, aktarımın hangi koşullarda yürüyeceğini de belirler. Yalnız kablosuz ağda indirmeyi, ucuz olmayan bağlantıda indirmeyi ya da şarj olurken indirmeyi seçmek mümkündür. Kısıt ne kadar sıkıysa aktarım o kadar geç başlar. Kullanıcıya büyük bir dosya vaat eden bir uygulama, bu koşulları açıkça bildirmelidir; aksi hâlde kullanıcı aktarımın neden başlamadığını anlayamaz.

Kısıtın sonucu ölçüme de yansır: aktarımın ne zaman biteceği bilinmediği için kayıt defteri uzun süre "sürüyor" durumunda kalabilir. Bu, hatalı bir kayıt değil, beklenen bir durumdur. Kaydın hata olarak okunması, kullanıcının aynı dosyayı yeniden kuyruğa almasına ve yinelenen aktarıma yol açar.

#### Sık Yapılan Hatalar

- Yeniden açılışta listeyi sormadan kuyruğa almak. Sistemin sürdürdüğü aktarım çoğalır.
- İndirilen dosyayı temsilci yöntemi dönmeden taşımamak. Geçici konum silinir.
- Tanımlayıcıyı her açılışta farklı üretmek. Uygulama eski aktarımlara yeniden bağlanamaz.
- Arka plan oturumunda kapanışta tutulan değerlere güvenmek. Uygulama sonlandırılmış olabilir.
- Aktarımın ne zaman biteceğini kestirmek. Bu karar sistemindir.

#### Ölçülen Sonucun Yorumu

Aynı iki yaklaşım aynı yeniden açılışta karşılaştırıldı ve farkı tek bir denetim yarattı: tanımlayıcıya bakmak. Dikkat edilirse kuyruğa alma işlemi hiçbir yerde hata vermedi; üç aktarım kaydı yasal bir durumdur. Yineleme sorunu, ağ derslerinde sık gördüğümüz gibi, sessizdir. Kayıt defterini tanımlayıcıyla tutmak, yinelemeyi çözen tek satırlık bir kural olarak göründü; ama bu kuralın çalışması için kaydın sistemin gerçek durumuyla uyumlu tutulması gerekir.

#### Sınırlayıcı Ölçüm

Model hiçbir yerde sistemin aktarımı ne zaman bitireceğini ya da uygulamayı ne zaman uyandıracağını hesaplamaz. Aşağıdaki ölçümde bitme ve kuyruğa alma işlemleri iki sırada uygulanır; sıra modele dışarıdan verilir.

```swift
// main.swift — bitme ve kuyruga alma, iki sira
let running = Registry(tasks: ["a.zip"])
let race = replay("bitme-ve-kuyruk", from: running, finish("a.zip"), then: enqueueIfMissing(["a.zip"])).report()
countSensitivities([race])
```

```
bitme-ve-kuyruk | bir=[] iki=[] ab=[a.zip] ba=[] -> siraya-duyarli
saglam: 0
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

Sıra duyarlıdır: aktarım önce bitip sonra kuyruğa alınırsa kayıt dolu kalır; önce kuyruğa alınıp sonra biterse kayıt boşalır. Hangi sıranın gerçekleşeceği ise ölçülmez; modelde sıra bir girdidir, bir hesap değildir. **Kural işlemlerin sırasını görür ama sırayı kimin belirlediğini görmez.**

**AG18.** Sistemin aktarımı ne zaman sürdüreceği, uygulamayı ne zaman uyandıracağı ve hangi koşullarda erteleyeceği ölçülmedi. Bu davranış sistem koşullarına bağlıdır.

#### Özet

- Arka plan oturumunda aktarımı sistem yürütür; uygulama sistem tarafından sonlandırılsa bile sürer.
- Yeniden açılışta listeyi denetlemeden yeniden kuyruğa almak sürmekte olan aktarımı çoğalttı.
- Görev tanımlayıcısına göre kayıt tutmak aynı yeniden açılışta sağlam kaldı.
- Bitme ve kuyruğa alma işlemlerinin sırası sonucu belirledi; sırayı belirleyen sistemdir ve ölçülmedi.
- Aktarım bitince sonucu bir temsilci alır ve dosya geçici konumdan taşınmalıdır.
- Bu dersin duyarlılığı: **yinelemeye duyarlı**.

#### Sonraki Adım

Ağ katmanının altı dersi burada bitiyor. Sıradaki konuda ölçü, eşzamanlılık katmanına geçecek: aynı işin iki kez çalışmasının ve iki işin birbirine karışan sırasının, paylaşılan bir durumda neyi değiştirdiği ölçülecek.

## Eşzamanlılık

Eşzamansız fonksiyonlar, yapılandırılmış eşzamanlılıkta görevler ve görev grupları, paylaşılan durumu yalıtan aktörler, arayüz güncellemelerinin ana iş parçacığında güvenliği, kuyruk tabanlı eski model ve tepkisel akışlar.

### Eşzamansız Fonksiyonlar

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/eszamansiz-fonksiyonlar

Duraklayabilen iki işlemin sırayla beklendiğinde stoğun hangi sırada ayrıldığına bağlı kaldığının, salt okuma işleminin ise sırayı görmediğinin ölçümü.

Önceki derste, Arka Plan Aktarımı, uygulama yeniden açılınca indirme listesinin denetlenmeden yeniden kuyruğa alınmasının aynı dosyayı iki kez indirttiğini gördük. Ağ katmanının açılış ölçümü altı işlemi dört sınıfa dağıtmıştı: bir sağlam, iki yinelemeye duyarlı, iki sıraya duyarlı, bir çift duyarlı. Bu konu aynı soruyu eşzamanlı koda taşır: iki işlem yinelenirse ya da sırası değişirse sonuç değişir mi? Sıra artık yalnız programcının yazdığı sıra değildir; bir işlem beklerken başka bir iş koşabilir.

Ölçüm çekirdek dosyayla yapılır. Bu dosya konunun her dersinde aynıdır.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

Çekirdek bir işlemi dört kez koşturur: bir kez, iki kez, önce `a` sonra `b`, önce `b` sonra `a`. Bir ve iki arasındaki fark yinelemeye duyarlılığı, iki sıra arasındaki fark sıraya duyarlılığı gösterir. Eşzamansız işlemde `replay` kullanılmaz, çünkü ona verilen işlevler `await` edemez. Dört koşum `await` ile elle kurulur ve sonuçlar doğrudan `Trial`'a verilir. Model, işlemin duruma ne yaptığını kurar; ağın ya da çalışma zamanının kendisi değildir.

#### Duraklayabilen İşlev

Bir **eşzamansız fonksiyon** (`async` fonksiyon), çalışırken bir noktada durup sonucu beklemeyi göze alan işlevdir. Çağıran onu `await` ile çağırır. `await` yazılan her yer bir **duraklama noktasıdır** (suspension point): işlev orada durabilir, kontrolü bırakabilir ve sonuç hazır olunca kaldığı yerden sürer. Duraklarken iş parçacığı bloke olmaz; sistem o sürede başka işi koşturabilir.

KAYNAK tarafta duraklama noktası tipik olarak ağdadır. Aşağıdaki parça ölçülmez, çünkü gerçek bir istek ağa çıkar.

```swift
import Foundation

func fetchStock(for sku: String) async throws -> Int {
    let url = URL(string: "https://example.com/stock/\(sku)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return Int(String(decoding: data, as: UTF8.self)) ?? 0
}
```

Parçadaki tek `await` isteğin sonucunu bekler; `try` ise hatanın çağırana yayılabileceğini söyler. Ölçülen katman bu çağrıyı kurmaz. Onun yerine duruma ne olduğuna bakar: bir raf, iki ayırma isteği ve ayırmanın yetersiz stokta yapılmaması.

```swift
// Stock.swift — raf stoku: yetersiz stokta ayirma yapilmaz
actor StockShelf {
    private(set) var quantity: Int
    private(set) var accepted: [Int] = []

    init(_ quantity: Int) { self.quantity = quantity }

    func reserve(_ amount: Int) -> Int {
        if quantity >= amount {
            quantity -= amount
            accepted.append(amount)
        }
        return quantity
    }

    func remaining() -> Int { quantity }
}

func runReserve(_ amounts: [Int]) async -> Int {
    let shelf = StockShelf(10)
    var left = 10
    for amount in amounts { left = await shelf.reserve(amount) }
    return left
}

func runReads(_ times: Int) async -> Int {
    let shelf = StockShelf(10)
    var seen = 0
    for _ in 0..<times { seen = await shelf.remaining() }
    return seen
}
```

`StockShelf` bir **aktör**dür; aktörler sonraki derslerin konusudur, burada yalnız duraklayabilen bir durum sahibi olarak kullanılır. Rafta on birim vardır. `runReserve`, her çağrıda taze bir raftan başlar ve verilen miktarları sırayla, her birini `await` ederek ayırır. Dönen sayı rafta kalan stoktur.

#### Çağrı Zinciri

`await` yalnız `async` bir bağlamda yazılabilir. Bir `async` işlevi çağıran işlevin kendisi de `async` olmalıdır; bu zincir yukarı doğru uzar ve en tepede sıradan, eşzamanlı bir kodla karşılaşır. Oradaki köprü bir `Task`'tır: eşzamanlı bağlamdan eşzamansız işi başlatır ve kendisi beklemez. Bir düğmenin dokunma işleyicisi bunun tipik örneğidir.

```swift
// Eszamanli baglamdan eszamansiz ise gecis: dokunma islemcisi
func refreshTapped() {
    Task {
        do {
            let left = try await fetchStock(for: "sku-1")
            print("kalan: \(left)")
        } catch {
            print("hata: \(error)")
        }
    }
}
```

`Task` gövdesi hemen döner; `refreshTapped` sonucu beklemez. Bu yüzden iki dokunma iki bağımsız iş başlatır ve ikisi de aynı rafa ulaşabilir. Ölçümün iki sırasının gerçek olmasının nedeni budur: hangi isteğin sunucuya önce varacağını ya da hangi yanıtın önce döneceğini işlevi yazan bilmez. Hatanın yayılması da burada kesilir. `Task` içinde yakalanmayan bir hatayı kimse görmez, çünkü görevin sonucunu bekleyen yoktur. Hata yönetimini görevin içinde kurmak bu yüzden gerekir.

#### Ayırma Sırası

İki istek aynı rafa gelir: altı birim ve beş birim. Birlikte on birimden fazladır, yani ikisi sığmaz. Hangisinin önce ayrıldığı sonucu belirler. Dört koşum ayrı ayrı kurulur: yalnız altı, altı iki kez, önce altı sonra beş, önce beş sonra altı.

```swift
// main.swift — iki ayirma iki sirada, bir okuma
let single = await runReserve([6])
let repeated = await runReserve([6, 6])
let sixThenFive = await runReserve([6, 5])
let fiveThenSix = await runReserve([5, 6])
let reservation = Trial(label: "stok-ayirma", once: single, twice: repeated, forward: sixThenFive, backward: fiveThenSix).report()

let readOnce = await runReads(1)
let readTwice = await runReads(2)
let reading = Trial(label: "stok-okuma", once: readOnce, twice: readTwice, forward: readTwice, backward: readTwice).report()

countSensitivities([reservation, reading])
```

```
stok-ayirma | bir=4 iki=4 ab=4 ba=5 -> siraya-duyarli
stok-okuma | bir=10 iki=10 ab=10 ba=10 -> saglam
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

Ayırma satırında `bir=4`: altı birim ayrıldı, dört kaldı. `iki=4`: ikinci altı sığmadı ve reddedildi, yani yineleme stoğu değiştirmedi. `ab=4` ve `ba=5`: önce altı ayrılınca beş sığmadı ve dört kaldı; önce beş ayrılınca altı sığmadı ve beş kaldı. İki sıra iki farklı stok bıraktı, bu yüzden işlem **sıraya duyarlıdır**. Okuma satırında dört değer de on: okuma duruma dokunmadığı için ne yineleme ne sıra bir şey değiştirir ve işlem **sağlamdır**. Sayım bir sağlam, bir sıraya duyarlı.

**ES1.** Bu ölçüm iki `await` arasına başka bir işin girmediğini varsayar. Her koşum kendi rafından başlar ve istekler yazıldığı sırayla işlenir; çalışma zamanının sırayı değiştirip değiştirmeyeceği ölçülmedi.

#### Ayrı Raflar

Sıraya duyarlılık yalnız işlemin değil, işlemlerin paylaştığı durumun özelliğidir. Bunu görmek için aynı iki isteği, altı ve beş birimi, bu kez iki ayrı rafa gönderiyoruz. Her isteğin hangi rafa gittiği çiftle verilir.

```swift
// main.swift — ayni iki istek, iki ayri raf
func runShelves(_ requests: [(shelf: Int, amount: Int)]) async -> [Int] {
    let shelves = [StockShelf(10), StockShelf(10)]
    for request in requests { _ = await shelves[request.shelf].reserve(request.amount) }
    let firstLeft = await shelves[0].remaining()
    let secondLeft = await shelves[1].remaining()
    return [firstLeft, secondLeft]
}

let apartOnce = await runShelves([(0, 6)])
let apartTwice = await runShelves([(0, 6), (0, 6)])
let apartForward = await runShelves([(0, 6), (1, 5)])
let apartBackward = await runShelves([(1, 5), (0, 6)])
let apart = Trial(label: "ayri-raflar", once: apartOnce, twice: apartTwice, forward: apartForward, backward: apartBackward).report()
countSensitivities([apart])
```

```
ayri-raflar | bir=[4, 10] iki=[4, 10] ab=[4, 5] ba=[4, 5] -> saglam
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 0
cift-duyarli: 0
```

İkinci altı yine ilk rafa sığmadığı için reddedilir ve `bir` ile `iki` aynı kalır. `ab` ile `ba` ise aynı iki rafı aynı durumda bırakır, çünkü iki istek birbirinin göreceği bir şeyi değiştirmez. İşlem **sağlamdır**. Aynı altı ve beş birimlik çift tek rafa gittiğinde sıraya duyarlıydı; fark isteklerde değil, onların ortak bir duruma dokunup dokunmamasındadır. Pratikte bu, sırayı sabitlemenin maliyetini nereye ödeyeceğinizi söyler: yalnız gerçekten paylaşılan durum için. Sayım bir sağlam.

**ES2.** Ayrı raflar birbirinden tamamen bağımsız sayıldı. İki rafın arkasında ortak bir depo ya da toplam bir stok sınırı varsa ayrılık yanıltıcı olur; böyle bir ortak sınır ölçülmedi.

#### Duraklama Noktasında Ne Olur

Ölçümün görmediği şey `await` içinde olur. İşlev durduğunda aynı aktöre ya da aynı veriye başka bir işin ulaşması mümkündür. Önce koşan iş, sonra koşan işin gördüğü durumu değiştirmiş olabilir; durumu `await`'ten önce okuyup `await`'ten sonra kullanan kod bu yüzden dikkat ister. Bu araya girmenin **hangi sırayla** olacağı sistemin zamanlamasına bağlıdır ve her koşumda aynı olmak zorunda değildir. Bu derste bunu koşturmuyoruz: sıralı iki çağrı, araya girme olmadan, deterministik bir deneydir.

Pratik kural şudur: durumu okuyan ve ona göre karar veren kod, karar ile yazma arasında `await` bırakmamalıdır. Aksi hâlde karar, yazıldığı anda artık doğru olmayabilir. Raftaki örnekte `reserve` işlevi kontrol ile yazmayı aynı adımda yapar ve aradaki hiçbir `await` yoktur; bu yüzden tek başına tutarlıdır. Tutarlı olması, iki isteğin sırasının sonucu etkilemediği anlamına gelmez; ölçüm tam bunu gösterdi.

#### Karar ile Yazma Arasındaki Boşluk

Ölçülmeyen ama sık görülen bir kurulum, kontrolü ve yazmayı iki ayrı `await` ile yapmaktır. Aşağıdaki işlev önce rafa kaç birim kaldığını sorar, sonra ayırmayı ister.

```swift
// Yanlis kurulum: karar ile yazma arasinda await var
func reserveCarelessly(_ amount: Int, on shelf: StockShelf) async -> Bool {
    let available = await shelf.remaining()
    guard available >= amount else { return false }
    _ = await shelf.reserve(amount)
    return true
}
```

İki `await` arasında raf başka bir istekle değişmiş olabilir. O zaman `available` artık doğru değildir, `reserve` isteği sessizce reddeder ve işlev yine de `true` döner. Çağıran ayırmanın yapıldığını sanır, oysa stok hiç düşmemiştir. Düzeltme, kararı ve yazmayı aktörün tek bir yordamında toplamak ve sonucu o yordamdan döndürmektir.

```swift
// StockShelf'e eklenen yordam: karar ve yazma tek adimda
func tryReserve(_ amount: Int) -> Bool {
    guard quantity >= amount else { return false }
    quantity -= amount
    accepted.append(amount)
    return true
}
```

Yordam `async` değildir ve içinde `await` yoktur; aktör onu başka bir işe bölmeden bitirir. Dönen değer, ayırmanın gerçekten yapıldığını söyler. Ardışık koşumlarda iki kurulum aynı sonucu verir, bu yüzden fark ölçümde görünmez; yalnız araya girme olduğunda görünür. Ölçülmeyen bir farkı kodun şeklinden okumak bu yüzden önemlidir.

#### Gerçek Uygulamada

Bu kalıp sepet ve rezervasyon ekranlarında sürekli karşılaşılır. Kullanıcı "ayır" düğmesine iki kez dokunur, iki ekran aynı ürüne bakar ya da sunucudan gelen bir yenileme, kullanıcının az önce yaptığı ayırmayla yarışır. Her birinde işlemlerin kendisi doğrudur; sorun, hangisinin önce sonuçlandığıdır. Ağ yanıtlarının dönüş sırası istek sırasıyla aynı olmak zorunda değildir ve eşzamansız kod bu sıranın üstüne bir şey koymaz.

Bu yüzden iki soru her zaman birlikte sorulur. İşlem tekrarlanırsa ne olur? İki işlem yer değiştirirse ne olur? Birinci sorunun yanıtı yinelemeye duyarlılığı, ikincisininki sıraya duyarlılığı verir. Okuma gibi durumu değiştirmeyen işlemler ikisinden de bağışıktır; bu yüzden ekranların çoğu okumayı serbestçe paylaşır, yazmayı ise tek bir yere toplar.

#### Sık Yapılan Hatalar

- `await` yazmanın işlemin sırasını güvenceye aldığını sanmak. `await` yalnız çağırıp beklemeyi söyler; başka çağrıların sırasını belirlemez.
- Okuma ile yazmayı tek bir adım saymak. Aralarında bir duraklama noktası varsa durum o sürede değişmiş olabilir.
- Hata yayılmasını unutmak. `throws` işaretli bir `async` işlev, `await`'in yanında `try` da ister; `try` atlanırsa kod derlenmez.
- Ölçümün sırasını gerçek sıra sanmak. Dört koşum elle kurulmuş iki sıradır; çalışma zamanının seçtiği sıra değildir.

#### Sınırlayıcı Ölçüm

Kural, durumda ne varsa onu karşılaştırır. Yalnız kalan stoğa bakan bir kural, hangi isteklerin kabul edildiğini görmez. Aşağıda altı ve dört birim ayrılır; ikisi de sığar ve rafı tamamen boşaltır.

```swift
// main.swift — ayni iki istek, iki farkli durum secimi
func runLedger(_ amounts: [Int]) async -> [Int] {
    let shelf = StockShelf(10)
    for amount in amounts { _ = await shelf.reserve(amount) }
    return await shelf.accepted
}

let leftSingle = await runReserve([6])
let leftRepeated = await runReserve([6, 6])
let leftForward = await runReserve([6, 4])
let leftBackward = await runReserve([4, 6])
let leftover = Trial(label: "kalan-stok", once: leftSingle, twice: leftRepeated, forward: leftForward, backward: leftBackward).report()

let ledgerSingle = await runLedger([6])
let ledgerRepeated = await runLedger([6, 6])
let ledgerForward = await runLedger([6, 4])
let ledgerBackward = await runLedger([4, 6])
let ledger = Trial(label: "kabul-defteri", once: ledgerSingle, twice: ledgerRepeated, forward: ledgerForward, backward: ledgerBackward).report()

countSensitivities([leftover, ledger])
```

```
kalan-stok | bir=4 iki=4 ab=0 ba=0 -> saglam
kabul-defteri | bir=[6] iki=[6] ab=[6, 4] ba=[4, 6] -> siraya-duyarli
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

Kalan stok iki sırada da sıfırdır; kural fark görmez ve işlem **sağlam** çıkar. Kabul defteri ise iki sırada `[6, 4]` ve `[4, 6]` tutar; aynı iki istek bu kez **sıraya duyarlıdır**. İki istek de aynıydı, değişen karşılaştırılan durumdur. Sayım bir sağlam, bir sıraya duyarlı.

**ES3.** Bu ölçüm kabul defterinin sırasını durumun parçası sayar. Sıranın ayrıca anlam taşıyıp taşımadığı, örneğin bir faturanın satır sırası olup olmadığı ölçülmedi.

#### Okuma Sırası ve Beklenti

Bu dersteki ölçümleri okurken tek bir soruyu akılda tutmak yeter: aynı istek iki kez, ya da iki istek iki sırada çalışırsa son durum değişir mi? Raf örneğinde farklı raflara yapılan istekler birbirini etkilemediği için sıra ve yineleme önemsizdir. Aynı rafa gelen iki istek ise ortak bir durumu değiştirir ve sonuç çağrıların sırasına bağlıdır. Eşzamansız işlev bu ayrımı ortadan kaldırmaz; yalnız iki çağrının birbirine karışabileceği aralığı uzatır. Her `await`, işlevin durdurulup başka işin araya girebileceği bir noktadır ve ortak duruma dokunan kodda bu noktaların nerede olduğunu bilmek, ölçümün gösterdiği duyarlılığı önceden tahmin etmenin en kısa yoludur.

#### Özet

- Eşzamansız fonksiyon duraklama noktasında bekleyebilen işlevdir; çağıran onu `await` ile çağırır.
- Yetersiz stokta ayırma yapılmayan iki istek iki sırada farklı stok bıraktı; işlem sıraya duyarlıdır.
- Durumu değiştirmeyen okuma işlemi iki sırada da aynı sonucu verdi ve sağlam çıktı.
- Kural yalnız durumda olanı görür: kalan stok sağlam, kabul defteri sıraya duyarlı çıktı.
- Araya girme ölçülmedi; sıra, dersin kurduğu sıradır.
- Bu dersin duyarlılığı: **sıraya duyarlı**.

#### Sonraki Adım

Bir işlev beklerken başka işler de koşabiliyorsa, birden çok işi birlikte başlatmanın bir yolu gerekir. Sıradaki derste görev grubunun sonuçları toplanırken toplamın sağlam kaldığı, sonuçları geliş sırasıyla dizmenin ise sıraya duyarlı olduğu ölçülecek.

### Görevler ve Görev Grupları

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/gorevler-ve-gorev-gruplari

Birlikte başlatılan işlerin sonuçlarının küme olarak toplandığında geliş sırasından bağımsız kaldığının, dizi olarak toplandığında ise sıraya bağlı olduğunun ölçümü.

Önceki derste iki ayırma isteği iki sırada farklı stok bıraktı; kalan stok ise iki sırada aynı çıktı ve ancak durumu değiştiren işlem sıraya duyarlıydı. Şimdi aynı soruyu birden çok işi birlikte başlatan koda soruyoruz: işler hangi sırayla biterse bitsin, topladığımız sonuç aynı mı kalır?

Çekirdek dosya konunun her dersinde aynıdır.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

Bu derste iki işlem ölçülür. Birincisi eşzamansız olduğu için dört koşumu elle kurulur ve `Trial`'a doğrudan verilir. İkincisi duruma yalnız bir dizi ekler, bu yüzden `replay` ile ölçülür. Model, işlemin duruma ne yaptığını kurar; ağın ya da çalışma zamanının kendisi değildir.

#### Üç Görev Biçimi

Eşzamansız işi başlatmanın üç yolu vardır ve fark, işin kimin kapsamına bağlı olduğundadır. **`async let`**, sayısı kod yazılırken belli olan çocuk görevleri başlatır: her `async let` bir çocuk doğurur ve sonuç `await` edildiğinde alınır. **Görev grubu**, sayısı çalışırken belli olan çocuklar içindir; bir listenin her elemanı için bir çocuk eklenir. **Yapılandırılmamış görev** (`Task { }`) ise çağıranın kapsamından bağımsızdır; çağıran bitse de görev sürer ve iptali elle yönetilir.

```swift
// async let: sayisi bilinen iki cocuk gorev birlikte baslar
func loadDashboard() async throws -> (stock: Int, orders: Int) {
    async let stock = fetchStockCount()
    async let orders = fetchOrderCount()
    return try await (stock, orders)
}
func fetchStockCount() async throws -> Int { 12 }
func fetchOrderCount() async throws -> Int { 7 }
```

İki çocuk aynı anda başlar; `return` satırı ikisini de bekler. İşlev bitmeden çocuklardan hiçbiri geride kalmaz: kapsam bitince hepsi ya bitmiştir ya da iptal edilmiştir. Yapılandırılmış eşzamanlılığın güvencesi budur ve `Task { }` ile kurulan görevde yoktur. Başıboş bırakılmış bir görev, ekran kapandıktan sonra bile ağ isteği göndermeyi sürdürebilir.

#### Görev ve Görev Grubu

Bir **görev** (`Task`), eşzamansız işi başlatan birimdir. **Yapılandırılmış eşzamanlılıkta** (structured concurrency) bir görev kendi kapsamından uzun yaşamaz: kapsam bitmeden içindeki tüm çocuk görevler bitmiş olur. Bunun en yaygın aracı **görev grubudur**: kapsamın içinde istediğiniz kadar çocuk görev eklenir ve sonuçlar bir döngüde toplanır. Çocuklar aynı anda koşabilir, bu yüzden hangisinin önce bittiği belli değildir.

Bu belirsizliği ölçüme sokmuyoruz. Hangi çocuğun önce bittiği her koşumda değişebilir ve çıktıyı onun üzerine kurmak ölçümü kararsız yapar. Ölçülen şey, toplanan sonucun geliş sırasına bağlı olup olmadığıdır.

```swift
// Groups.swift — gorev grubu sonuclarini kumede toplar, geliste dizer
func collectNames(_ names: [String]) async -> [String] {
    await withTaskGroup(of: String.self) { group in
        for name in names {
            group.addTask { name }
        }
        var collected = Set<String>()
        for await name in group {
            collected.insert(name)
        }
        return collected.sorted()
    }
}

func record(_ name: String) -> ([String]) -> [String] {
    { list in list.contains(name) ? list : list + [name] }
}
```

`collectNames` her adı bir çocuk görevde döndürür ve `for await` ile geliş sırasıyla alır; ama sonucu bir `Set`'e koyar ve sıralanmış olarak verir. Küme sırayı tutmaz, tekrarı da tutmaz. `record` ise duruma bir ad ekleyen işlemdir: ad listede yoksa sona ekler, varsa dokunmaz.

#### Kümede Toplamak ve Dizide Toplamak

Önce kümeye toplayan işlem dört koşumda ölçülür: tek ad, aynı ad iki kez, `alfa` sonra `beta`, `beta` sonra `alfa`. Ardından aynı iki ad `record` ile dizide toplanır.

```swift
// main.swift — ayni iki ad, iki toplama bicimi
let setOnce = await collectNames(["alfa"])
let setTwice = await collectNames(["alfa", "alfa"])
let setForward = await collectNames(["alfa", "beta"])
let setBackward = await collectNames(["beta", "alfa"])
let grouped = Trial(label: "grup-kumesi", once: setOnce, twice: setTwice, forward: setForward, backward: setBackward).report()

let arrival = replay("gelis-sirasi", from: [], record("alfa"), then: record("beta")).report()

countSensitivities([grouped, arrival])
```

```
grup-kumesi | bir=["alfa"] iki=["alfa"] ab=["alfa", "beta"] ba=["alfa", "beta"] -> saglam
gelis-sirasi | bir=["alfa"] iki=["alfa"] ab=["alfa", "beta"] ba=["beta", "alfa"] -> siraya-duyarli
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

Küme satırında dört değerin her biri `["alfa"]` ya da `["alfa", "beta"]`: tekrarlanan ad kümede erir, iki sıra aynı sıralı listeyi verir. İşlem **sağlamdır**. Geliş sırası satırında `bir` ve `iki` aynıdır, çünkü `record` var olan adı yeniden eklemez; ama `ab` ile `ba` farklıdır. Önce `alfa` gelirse liste `["alfa", "beta"]`, önce `beta` gelirse `["beta", "alfa"]` olur. İşlem **sıraya duyarlıdır**. Sayım bir sağlam, bir sıraya duyarlı.

**ES4.** Bu ölçüm çocukların hangi sırayla bitebileceğini iki sıradan ibaret sayar. Gerçek bir grupta çocuk sayısı büyüdükçe olası sıra sayısı da büyür; hepsi ölçülmedi.

**ES5.** Ölçüm çocuk görevlerin hepsinin başarıyla döndüğünü varsayar. Bir çocuğun hata fırlattığı ya da iptal edildiği durum ölçülen modelde yoktur.

#### Toplamak Yerine Saymak

Küme tekrarı siler; bu, toplamı sağlam yapan şeyin bir parçasıdır. Çocukların döndürdüğü sayıları bir kümeye değil, bir toplama yazarsak tekrar artık silinmez. Aynı ölçüm bu kez toplamla yapılır: bir değer, aynı değer iki kez, iki farklı değer iki sırada.

```swift
// main.swift — grup sonuclarini toplamak
func sumValues(_ values: [Int]) async -> Int {
    await withTaskGroup(of: Int.self) { group in
        for value in values { group.addTask { value } }
        var total = 0
        for await value in group { total += value }
        return total
    }
}

let sumOnce = await sumValues([4])
let sumTwice = await sumValues([4, 4])
let sumForward = await sumValues([4, 5])
let sumBackward = await sumValues([5, 4])
let summed = Trial(label: "grup-toplami", once: sumOnce, twice: sumTwice, forward: sumForward, backward: sumBackward).report()
countSensitivities([summed])
```

```
grup-toplami | bir=4 iki=8 ab=9 ba=9 -> yinelemeye-duyarli
saglam: 0
yinelemeye-duyarli: 1
siraya-duyarli: 0
cift-duyarli: 0
```

Toplamada sıra önemsizdir: `ab` ve `ba` ikisi de dokuz. Ama `bir` dört, `iki` sekizdir; aynı değeri iki kez eklemek toplamı değiştirir. İşlem **yinelemeye duyarlıdır**. Bir önceki kümede aynı yineleme erimişti. Yani iki toplama biçimi aynı çocuk görevlerden çıkar ve biri sağlam, öteki yinelemeye duyarlıdır. Hangisinin doğru olduğunu görev grubu söylemez; toplamın anlamı söyler. Eşsiz kullanıcıları sayarken küme, sipariş tutarlarını toplarken toplam gerekir ve yanlış seçim sessizce yanlış sonuç üretir.

**ES6.** Toplam ölçümünde tamsayı toplamı kullanıldı; tamsayı toplamı değişmeli ve birleşmelidir. Kayan noktalı sayıların toplamı sıraya göre son basamakta farklanabilir, bu ölçülmedi.

#### İptal

Görev gruplarında **iptal** yapılandırmanın parçasıdır. Bir çocuk hata fırlatır ve grup kapsamı bunu yayarsa kalan çocuklar iptal edilir; `group.cancelAll()` ile bu elle de yapılır. İptal bir görevi öldürmez, ona istenmediğini bildirir. Görevin kendisi iptali yoklar ve temiz biçimde çıkar.

```swift
// Iptal: grup iptal edilince cocuklar yarida kesilebilir
func fetchAll(_ ids: [Int]) async throws -> [Int] {
    try await withThrowingTaskGroup(of: Int.self) { group in
        for id in ids {
            group.addTask {
                try Task.checkCancellation()
                return id * 2
            }
        }
        var results: [Int] = []
        for try await value in group {
            results.append(value)
            if results.count == 2 { group.cancelAll() }
        }
        return results
    }
}
```

Parçadaki `Task.checkCancellation()` iptal edilmiş bir görevde hata fırlatır. İptal, sonuçların kaçının toplandığını belirleyebilir; hangi çocukların kaçıncı sırada yetiştiği ise zamanlamaya bağlıdır. Bu yüzden parça yalnız okunur, koşturulmaz.

#### İptal Nasıl Çalışır

İptal **işbirlikçidir**: görev iptal edildi işaretini taşır, ama kendiliğinden durmaz. Görevin kodu bu işareti iki yoldan yoklar. `Task.checkCancellation()` iptal edilmişse hata fırlatır ve işlevi sonlandırır. `Task.isCancelled` ise yalnız bir `Bool` döndürür; böylece görev kısmi sonucu bırakıp temiz biçimde çıkabilir. Bir ağ isteği gibi sistemin kendi eşzamansız işlevleri iptali zaten yoklar ve iptal edilince hata fırlatır.

İptal aşağı doğru yayılır. Üst görev iptal edilince çocukları da iptal edilir; bir grup kapsamı hata fırlatarak çıkarsa, henüz bitmemiş çocuklar iptal edilir. Yukarı doğru yayılmaz: bir çocuğun iptali kardeşlerini ya da ebeveynini durdurmaz. Bu asimetri bilinmezse iki tür hata çıkar. Birincisi, uzun bir çocuğa iptal yoklaması koymamaktır; işaret taşınır, kimse bakmaz. İkincisi, iptali hata sanmaktır; iptal edilmiş bir istek kullanıcıya hata olarak gösterilmemelidir, çünkü kullanıcı zaten başka bir şeye geçmiştir.

#### Sık Yapılan Hatalar

- Çocuk görevlerin bitiş sırasını gelen sonucun sırası sanmak. Sıra, görevlerin eklendiği sıra değildir.
- Sonucu dizi olarak toplayıp sıralamayı unutmak. Sıra anlam taşımıyorsa sıralanmış küme güvenli bir toplamadır; taşıyorsa sıranın kaynağı ayrıca belirlenmelidir.
- İptali yoklamayan uzun bir çocuk yazmak. İptal istenmiş olsa da çocuk bitene kadar sürer.
- Görev grubunu kapsamın dışına taşımaya çalışmak. Grup yalnız kapsamı boyunca yaşar.

#### Sıra Gerekiyorsa

Geliş sırası anlam taşıyorsa, örneğin sonuçlar bir listede girdi sırasıyla gösterilecekse, çözüm sonuca sıra numarasını taşımaktır. Çocuklar yine istediği sırada biter, ama her biri nereye ait olduğunu bilir.

```swift
// Sirayi korumak gerekiyorsa sira numarasi sonuca tasinir
func loadInOrder(_ ids: [Int]) async -> [Int] {
    await withTaskGroup(of: (Int, Int).self) { group in
        for (index, id) in ids.enumerated() {
            group.addTask { (index, id * 2) }
        }
        var slots = [Int?](repeating: nil, count: ids.count)
        for await (index, value) in group { slots[index] = value }
        return slots.compactMap { $0 }
    }
}
```

Burada sonuç artık varış sırasına değil girdi sırasına bağlıdır. Girdi listesi sabitse çıktı da her koşumda aynıdır. Sıraya duyarlılık ortadan kalkmaz, kaynağı değişir: sonucun sırası zamanlamadan çıkar ve girdinin kendisine taşınır. Ölçtüğümüz `gelis-sirasi` satırı varış sırasını sınıflıyordu; sıra numarasıyla yazılan sürümde sınıflanan şey artık girdi sırasıdır. Bu ölçülmedi, yalnız kodun yapısından çıkarılan bir sonuçtur.

Yaygın yanlış kurulum, sıra numarası taşımadan sonuçları gelişine göre bir diziye eklemek ve ekranda bu diziyi girdi sırasıymış gibi göstermektir. Geliştirici makinesinde ağ hızlı ve dengelidir, çocuklar çoğunlukla başlatıldıkları sırayla biter ve hata fark edilmez. Yavaş bir bağlantıda üçüncü istek birinciden önce döner ve liste kullanıcının gözü önünde yer değiştirir. Düzeltme, yukarıdaki gibi sonuca konumu taşımaktır; konum bir kez yazıldığı için dizi her koşumda aynı çıkar.

#### Gerçek Uygulamada

Bir ürün sayfası açılırken fiyat, stok ve yorumlar aynı anda istenir; ekran üçü de gelince çizilir ya da gelen her parçayı hemen gösterir. İlk durumda `async let` yeterlidir. İkincisinde her parça için ayrı görev gerekir ve ekranın hangi sırayla güncellendiği artık tasarım kararıdır. Kullanıcı sayfadan ayrılırsa bu görevlerin hepsi iptal edilmelidir; görünüme bağlı bir görev (SwiftUI'daki `task` değiştiricisi bunun bir örneğidir) görünüm kaybolunca iptal edilir, başıboş bir `Task` ise edilmez.

Toplama yönteminin seçimi de buradan çıkar. Sayaçlar toplamla, eşsiz kimlikler kümeyle, görüntülenecek satırlar girdi sırasına bağlı dizi ile toplanır. Üçü de aynı görev grubunun üstüne kurulur; fark yalnız sonucu toplayan satırdadır ve ölçüm bu farkı görünür kılar.

#### Sınırlayıcı Ölçüm

Aynı işlem iki farklı durumla karşılaştırılırsa iki farklı sınıfa düşebilir. Geliş sırası işlemini yeniden ele alalım: aynı `alfa` ve `beta` eklemeleri, ama bu kez durum olarak listeyi değil, listenin yalnız eleman sayısını karşılaştırıyoruz.

```swift
// main.swift — ayni islem, iki durum secimi
let listTrial = replay("liste-gorunumu", from: [], record("alfa"), then: record("beta"))
let listView = listTrial.report()

let countTrial = Trial(label: "sayi-gorunumu", once: listTrial.once.count, twice: listTrial.twice.count, forward: listTrial.forward.count, backward: listTrial.backward.count)
let countView = countTrial.report()

countSensitivities([listView, countView])
```

```
liste-gorunumu | bir=["alfa"] iki=["alfa"] ab=["alfa", "beta"] ba=["beta", "alfa"] -> siraya-duyarli
sayi-gorunumu | bir=1 iki=1 ab=2 ba=2 -> saglam
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

Liste görünümünde `ab` ile `ba` farklıdır ve işlem **sıraya duyarlıdır**. Sayı görünümünde ikisi de iki; kural farkı göremez ve işlem **sağlam** çıkar. Kural nedeni görmez, eşitsizliği görür; karşılaştırılan durumun seçimi sınıfı değiştirir. Bir toplamanın sağlam olduğunu söylemeden önce neyin karşılaştırıldığı yazılmalıdır. Sayım bir sağlam, bir sıraya duyarlı.

#### Hangi Biçimi Seçmeli

Seçim üç soruya iner. Çocuk sayısı kod yazılırken biliniyorsa `async let` en az gürültüyle aynı işi görür. Sayı çalışırken belirleniyorsa görev grubu gerekir. İş, çağıranın ömrüne bağlı olmamalıysa, örneğin bir kullanıcı eylemi sonrasında arka planda sürecek bir yükleme için, yapılandırılmamış görev kullanılır; ama bu durumda iptal ve hata yönetimi artık çağıranın değil sizin sorumluluğunuzdadır. Yanlış seçimin tipik belirtisi, her şey için `Task { }` açıp sonuçları paylaşılan bir değişkene yazmaktır. Kapsam güvencesi yitirilir; hangi görevin ne zaman biteceği, hata verirse kimin duyacağı ve ekran kapanınca kimin iptal edeceği belirsiz kalır; bu dersteki üç duyarlılık sorusu, her görev için ayrı ayrı yeniden açılır.

Özetle, görev grubunun verdiği güvence yapısaldır: hiçbir çocuk grubun dışına sızmaz. Sonucun anlamlı olup olmadığı ise toplama satırında verilen karardır.

#### Özet

- Görev grubu, çocuk görevleri kapsam içinde başlatır ve kapsam bitmeden hepsinin bitmesini bekler.
- Sonuçlar kümede toplanıp sıralanınca işlem geliş sırasından bağımsızdır ve sağlam çıkar.
- Aynı sonuçlar geliş sırasıyla dizilince işlem sıraya duyarlıdır.
- Aynı işlem, yalnız eleman sayısı karşılaştırılırsa sağlam görünür; durum seçimi sınıfı değiştirir.
- İptal yapılandırmanın parçasıdır; çocuk iptali yoklamazsa sürer.
- Bu dersin duyarlılığı: **sağlam** (kümede) ve **sıraya duyarlı** (dizide).

#### Sonraki Adım

Bir toplamanın sırayı gizlemesi, durumu paylaşan işlerin güvenli olduğu anlamına gelmez. Sıradaki derste aynı veriye birden çok yerden ulaşan işleri aktör yalıtımının nasıl sıraya soktuğunu ve iki istek arasındaki sıranın yine ölçülebilir kaldığını göreceğiz.

### Aktörler

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/aktorler

Aktör yalıtımının tek seferde tek işe izin verdiği, ama iki isteğin sırasının ve bir sayacın yinelenmesinin sonucu yine değiştirdiğinin ölçümü.

Önceki derste görev grubunun sonuçlarını kümede toplamak geliş sırasını sildi, diziye dökmek ise sırayı sonuca yazdı. Orada sorun toplamanın biçimiydi. Bu derste sorun durumun kendisidir: birden çok iş aynı veriye ulaşırsa yalıtımın verdiği güvence neyi kapsar, neyi kapsamaz?

Çekirdek dosya konunun her dersinde aynıdır.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

Aktör işlemleri eşzamansızdır; çağrılar `await` gerektirir. Bu yüzden `replay` yerine her koşum elle kurulur ve `Trial`'a doğrudan verilir. Model, işlemin duruma ne yaptığını kurar; ağın ya da çalışma zamanının kendisi değildir.

#### Aktör ve Yalıtım

Bir **aktör** (`actor`), kendi değişebilir durumunu koruyan bir referans tipidir. Aktörün durumuna dışarıdan doğrudan dokunulmaz; erişim `await` ile aktörün bir işlevine girerek yapılır ve aktör aynı anda yalnız bir işi yürütür. Buna **aktör yalıtımı** denir. Yalıtımın kazandırdığı şey **veri yarışının** (data race) önlenmesidir: iki iş aynı bellek alanını aynı anda okuyup yazamaz.

Bununla bir **yarış koşulu** (race condition) arasındaki fark önemlidir. Veri yarışı bellek düzeyinde bir hatadır ve yalıtım onu ortadan kaldırır. Yarış koşulu ise sonucun işlerin sırasına bağlı olmasıdır ve yalıtım bunu çözmez: iki iş sırayla, tek tek, güvenle çalışır; ama hangisinin önce çalıştığı yine sonucu değiştirir.

```swift
// Seats.swift — koltuk haritasi ve bilet sayaci
actor SeatMap {
    private(set) var owner = "-"

    func claim(_ name: String) -> Bool {
        if owner == "-" {
            owner = name
            return true
        }
        return false
    }

    func currentOwner() -> String { owner }
}

actor TicketCounter {
    private(set) var sold = 0

    func sell() { sold += 1 }
    func total() -> Int { sold }
}

func runClaims(_ names: [String]) async -> String {
    let map = SeatMap()
    for name in names { _ = await map.claim(name) }
    return await map.currentOwner()
}

func runSales(_ times: Int) async -> Int {
    let counter = TicketCounter()
    for _ in 0..<times { await counter.sell() }
    return await counter.total()
}
```

`SeatMap` tek bir koltuğun sahibini tutar. İlk talep eden kazanır; sonraki talepler reddedilir. `TicketCounter` her çağrıda satış sayısını bir artırır. İkisi de aktördür; ikisinde de yalıtım tamdır.

#### İlk Talep Kazanır ve Sayaç Artar

Koltuk haritası iki ad için dört koşumda ölçülür: tek talep, aynı adın iki talebi, `ayse` sonra `burak`, `burak` sonra `ayse`. Sayaç ise bir ve iki satışla ölçülür; iki farklı işlem olmadığı için ileri ve geri sıra iki satıştır.

```swift
// main.swift — koltuk talepleri ve bilet satislari
let claimOnce = await runClaims(["ayse"])
let claimTwice = await runClaims(["ayse", "ayse"])
let claimForward = await runClaims(["ayse", "burak"])
let claimBackward = await runClaims(["burak", "ayse"])
let seat = Trial(label: "koltuk-talebi", once: claimOnce, twice: claimTwice, forward: claimForward, backward: claimBackward).report()

let saleOnce = await runSales(1)
let saleTwice = await runSales(2)
let sales = Trial(label: "bilet-satisi", once: saleOnce, twice: saleTwice, forward: saleTwice, backward: saleTwice).report()

countSensitivities([seat, sales])
```

```
koltuk-talebi | bir=ayse iki=ayse ab=ayse ba=burak -> siraya-duyarli
bilet-satisi | bir=1 iki=2 ab=2 ba=2 -> yinelemeye-duyarli
saglam: 0
yinelemeye-duyarli: 1
siraya-duyarli: 1
cift-duyarli: 0
```

Koltuk satırında aynı adın ikinci talebi `ayse`'yi değiştirmez, çünkü ikinci talep reddedilir; `bir` ile `iki` aynıdır. Ama `ab` `ayse`, `ba` `burak`: ilk gelen kazanır ve kimin ilk geleceği sonuca yazılır. İşlem **sıraya duyarlıdır**. Sayaç satırında `bir=1` ve `iki=2`: her çağrı sayacı artırır, yineleme sonucu değiştirir. Sıra ise sayacı etkilemez; bilet satışları değişmeli olduğu için `ab` ile `ba` aynıdır. İşlem **yinelemeye duyarlıdır**. Sayım bir yinelemeye, bir sıraya duyarlı.

**ES7.** Bu ölçüm koltuk için tek bir sahip alanını modeller. Birden çok koltuğun ya da bir bekleme listesinin aynı kuralı nasıl etkilediği ölçülmedi.

**ES8.** Sayaç ölçümünde sıra, iki özdeş `sell` çağrısının sırası sayılır. Farklı türde iki işlemin birlikte kullanıldığı durum ölçülmedi.

#### Aktör Neden Var

Aktörden önce paylaşılan durumu korumanın yolu kilitti: durumu okuyan ya da yazan her kod, önce bir kilit alır, işi bitirince bırakırdı. Yöntem çalışır, ama koruma programcının disiplinine bağlıdır. Kilidi almayı unutan tek bir satır, bütün korumayı geçersiz kılar ve derleyici bunu fark etmez. Hata da nadiren görünür: iki iş aynı anda o satıra denk geldiğinde, çoğu zaman yalnız yük altında, bazen yalnız bir müşterinin aygıtında.

Aktör korumayı dilin kendisine taşır. Durum aktörün içindedir ve ona erişmenin tek yolu aktörün işlevleridir; derleyici dışarıdan doğrudan erişimi reddeder. Kilidi unutmak mümkün olmaz, çünkü kilit alma işini aktör kendisi yapar. Karşılığında bir bedel vardır: aktöre her giriş bir `await` noktasıdır ve çağıran, aktör meşgulse sırada bekler. Bu bekleyiş çağıranı bloke etmez; çağıran durdurulur, iş parçacığı başka işe verilir. Aktör bu yüzden uzun ve yavaş işler için değil, kısa ve sık yapılan durum değişiklikleri için uygundur.

Tipik yanlış kurulum, aynı korumayı sınıf ile yapmaya çalışıp korumayı atlamaktır.

```swift
// Yanlis: degisebilir bir sinif, koruma yok
final class UnsafeTicketCounter {
    var sold = 0
    func sell() { sold += 1 }
}
```

Bu sınıf tek bir akıştan kullanıldığında doğru çalışır. İki görev aynı örneğe aynı anda `sell` çağırdığında ise `sold += 1` okuma, artırma ve yazma adımlarından oluştuğu için iki adım iç içe geçebilir ve bir satış kaybolur. Sınıf aktarılabilir olmadığı için Swift 6 dil kipi, bu örneği iki göreve paylaştırmanıza izin vermemelidir; bu derleyici davranışı burada ölçülmedi. Düzeltme, sınıfı `actor` yapmaktır; yukarıdaki `TicketCounter` tam olarak budur ve gövdesi değişmez.

#### Aktarılabilir Tipler

Aktörün içindeki durum yalıtılmıştır, ama aktöre giren ve çıkan değerler sınırı geçer. Bir değerin eşzamanlı sınırlar arasında güvenle gönderilebilmesi için tipi **aktarılabilir** (`Sendable`) olmalıdır. Değer tipleri ve değişmez değerler genellikle bu şartı kendiliğinden karşılar; değişebilir referans tipleri karşılamaz ve derleyici bunu hata olarak bildirir.

```swift
// Aktarilabilir tip: aktorler arasinda gonderilebilen degisemez deger
struct Reservation: Sendable {
    let seat: Int
    let owner: String
}

func describe(_ reservation: Reservation) -> String {
    "koltuk \(reservation.seat) -> \(reservation.owner)"
}
```

`Reservation` yalnız sabit alanlar taşır, bu yüzden güvenle gönderilir. Değişmez değerler sınırı geçerken kopyalanır ya da paylaşılır; kimse onları aynı anda değiştiremez. Bu parça ölçülmez, çünkü hiçbir işlemin duruma ne yaptığını göstermez; yalnız tip kuralını anlatır.

#### Koltuğu Bırakmak

Şimdiye dek koltuk yalnız kazanılıyordu. Gerçek bir rezervasyonda koltuk geri de bırakılır ve bırakma, kazanma ile birlikte sıraya duyarlılığın ikinci bir biçimini getirir. Burada bir kural var: yalnız koltuğun mevcut sahibi onu bırakabilir; başkasının bırakma isteği sessizce reddedilir.

```swift
// Release.swift — birakilabilir koltuk
actor ReleasableSeat {
    private(set) var owner = "-"

    func claim(_ name: String) {
        if owner == "-" { owner = name }
    }

    func release(_ name: String) {
        if owner == name { owner = "-" }
    }
}

func runSeatOperations(_ operations: [(isRelease: Bool, name: String)]) async -> String {
    let seat = ReleasableSeat()
    for operation in operations {
        if operation.isRelease {
            await seat.release(operation.name)
        } else {
            await seat.claim(operation.name)
        }
    }
    return await seat.owner
}
```

Ölçülen çift, `ayse` için kazanma ile bırakmadır. Dört koşum: tek kazanma, iki kazanma, kazanıp bırakma, bırakıp kazanma.

```swift
// main.swift — kazanma ve birakma
let releaseOnce = await runSeatOperations([(false, "ayse")])
let releaseTwice = await runSeatOperations([(false, "ayse"), (false, "ayse")])
let releaseForward = await runSeatOperations([(false, "ayse"), (true, "ayse")])
let releaseBackward = await runSeatOperations([(true, "ayse"), (false, "ayse")])
let released = Trial(label: "birakma", once: releaseOnce, twice: releaseTwice, forward: releaseForward, backward: releaseBackward).report()

countSensitivities([released])
```

```
birakma | bir=ayse iki=ayse ab=- ba=ayse -> siraya-duyarli
saglam: 0
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

Aynı kazanma ikinci kez yapılınca sonuç değişmez: `bir` ve `iki` ikisi de `ayse`. Ama kazanıp bırakmak koltuğu boşaltır (`ab` için `-`), bırakıp kazanmak ise koltuğu `ayse`'ye verir (`ba`). Bırakma, kazanmadan önce geldiğinde etkisizdir, çünkü bırakılacak bir sahiplik yoktur. İşlem çifti **sıraya duyarlıdır**. Bu, rezervasyon ekranlarında tanıdık bir hatanın modelidir: kullanıcı koltuğu seçer ve hemen vazgeçer; iki istek ağda yer değiştirirse koltuk, kullanıcı vazgeçtiği hâlde ona ait görünür.

**ES9.** Bu ölçümde bırakma, yalnız mevcut sahibin adıyla yapılan istek olarak kuruldu. Başka bir kullanıcının bırakma isteği ve süre aşımıyla otomatik bırakma ölçülmedi.

#### Gerçek Uygulamada

Bir etkinlik uygulamasında koltuk seçimi ekranı düşünün. Kullanıcı bir koltuğa dokunur, uygulama sunucuya istek atar ve sonucu ekranda gösterir. İstemci tarafında koltukların durumunu bir aktör tutar: hangi koltuk boş, hangisi seçili, hangisi sunucudan henüz yanıt beklemede. Kullanıcı hızlı davranır, bir koltuğu seçip bırakır ve ardından başka birini seçer; yani birkaç istek kısa sürede art arda gönderilir. Aktör, durumun bu isteklerin ortasında bozulmadan kalmasını sağlar. Hangi isteğin önce işleneceğini ise aktör değil, isteklerin aktöre ulaşma sırası belirler.

Bu ayrım ekranda iki ayrı hata olarak görünür. Veri yarışı olsaydı uygulama bellek bozulmasıyla çökerdi; aktör bunu önler. Sıra hatasında ise uygulama çökmez, yalnız yanlış koltuğu seçili gösterir; aktör bunu önlemez, çünkü iki istek de ayrı ayrı geçerlidir. Bu yüzden sıra gerektiren akışlarda iki yoldan biri seçilir: istekler tek bir görevden, art arda `await` ile gönderilir, ya da her istek bir sıra numarası taşır ve aktör eski numaralı isteği reddeder.

Yaygın bir kurulum hatası, kontrolü aktörün dışında yapıp eylemi içinde yapmaktır.

```swift
// Yanlis: kontrol ile eylem arasinda baska is girebilir
func claimIfFree(_ map: SeatMap, _ name: String) async {
    if await map.currentOwner() == "-" {
        _ = await map.claim(name)
    }
}
```

İki `await` arasında aktöre başka bir görev girebilir: iki çağrı da koltuğu boş görür, ikisi de `claim` çağırır. `SeatMap.claim` kendi içinde kontrol edip yazdığı için ikinci çağrı reddedilir ve sonuç bu örnekte doğru kalır; ama kural aktörün dışına taşındığında, örneğin "boşsa bir ödeme isteği gönder" gibi yan etkili bir eylem eklendiğinde, ödeme iki kez gönderilebilir. Düzeltme, kontrolü ve eylemi aktörün tek bir işlevinde birleştirmektir; `claim` bunu zaten yapar. Bu parça ölçülmedi, çünkü iki çağrının araya girdiği bir yürütmeyi bu dersin ardışık koşumu kuramaz.

#### Sık Yapılan Hatalar

- Aktörü yarış koşuluna karşı bağışık sanmak. Aktör veri yarışını önler; iki işin sırasını sabitlemez.
- Aktörün içinde durumu okuyup `await`'ten sonra yazmak. Duraklama noktasında başka bir iş araya girmiş olabilir.
- Aktör durumuna dışarıdan doğrudan erişmeye çalışmak. Erişim `await` ile yapılır; derleyici bunu zorlar.
- Aktarılabilir olmayan bir referans tipini aktöre vermek. Sınır geçiren her değerin tipi aktarılabilir olmalıdır.

#### Sınırlayıcı Ölçüm

Yeniden girilebilirlik (reentrancy) aktörün bir `await` noktasında durduğunda başka bir işin aynı aktöre girebilmesidir. Aşağıdaki koltuk haritası talebi bir duraklama noktasından sonra yazar. Ardışık koşumda aynı çağrılar sırayla çalışır ve araya hiçbir iş girmez; ölçüm bunu görmez.

```swift
// main.swift — duraklayan koltuk haritasi, ayni dort kosum
actor SlowSeatMap {
    private(set) var owner = "-"

    func pause() async {}

    func claim(_ name: String) async -> Bool {
        if owner == "-" {
            await pause()
            owner = name
            return true
        }
        return false
    }
}

func runSlowClaims(_ names: [String]) async -> String {
    let map = SlowSeatMap()
    for name in names { _ = await map.claim(name) }
    return await map.owner
}

let slowOnce = await runSlowClaims(["ayse"])
let slowTwice = await runSlowClaims(["ayse", "ayse"])
let slowForward = await runSlowClaims(["ayse", "burak"])
let slowBackward = await runSlowClaims(["burak", "ayse"])
let slow = Trial(label: "yavas-koltuk", once: slowOnce, twice: slowTwice, forward: slowForward, backward: slowBackward).report()

countSensitivities([slow])
```

```
yavas-koltuk | bir=ayse iki=ayse ab=ayse ba=burak -> siraya-duyarli
saglam: 0
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

Duraklama noktası eklendi ve satır `koltuk-talebi` ile aynı çıktı. Kural, bir `await` arasına başka iş girmediğini görmez; yalnız dört koşumun sonunda durumun eşit olup olmadığına bakar. Üstelik aktör veri yarışından yoksundur ve bu, sonucu sağlam yapmaz: aynı satır **sıraya duyarlı** kalır. Kural nedeni görmez, eşitsizliği görür. İki isteğin aynı anda duraklayıp ikisinin de boş koltuğu gördüğü durum burada kurulmadı ve ölçülmedi; o durumda koltuk iki kez kazanılmış olurdu. Sayım bir sıraya duyarlı.

**ES10.** Duraklama noktasında araya başka bir iş girmesi bu derste hiçbir ölçümde kurulmadı. Yeniden girilebilirlik burada yalnız anlatıldı.

Bir kural olarak, aktörün işlevi tek adımda karar verip yazıyorsa durum tutarlı kalır; kararı dışarıda verip aktöre yalnız yazdırıyorsanız tutarlılık sizin sorumluluğunuza geri döner. Bu ayrım küçük görünür, ama rezervasyon, stok ve bakiye gibi alanlarda hatanın nerede çıkacağını belirler.

#### Özet

- Aktör, durumunu yalıtır ve aynı anda tek işe izin verir; bu veri yarışını önler.
- Yalıtım, iki işin sırasını sabitlemez: ilk talebi kabul eden koltuk haritası sıraya duyarlıdır.
- Her çağrıda artan sayaç yinelemeye duyarlıdır ve sırayı görmez.
- Aktarılabilir tipler sınırı güvenle geçer; değişebilir referanslar geçemez.
- Duraklama noktası eklemek ardışık ölçümde sınıfı değiştirmez; araya girme ölçülmedi.
- Bu dersin duyarlılığı: **sıraya duyarlı** (koltuk) ve **yinelemeye duyarlı** (sayaç).

#### Sonraki Adım

Aktörlerin içinde durumu korumak bir şey, arayüzün durumunu korumak başka bir şeydir. Sıradaki derste ana iş parçacığına bağlı çalışan kodu ve arama sonuçlarının geliş sırasına göre ekrana yazıldığı bir modeli ölçeceğiz.

### Ana İş Parçacığı Yalıtımı

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/ana-is-parcacigi-yalitimi

Arayüzün gösterdiği listeyi güncelleyen işlemin, sürüm numarası olmadan geliş sırasına bağlı kaldığının, sürümle ise sağlamlaştığının ölçümü.

Önceki derste aktör yalıtımı veri yarışını önledi ama iki isteğin sırasını sabitlemedi. Bir ekranın gösterdiği liste de bu tür bir paylaşılan durumdur: arama kutusuna yazdıkça yeni istekler gider ve yanıtlar hangi sırayla gelirse gelsin ekrana yazılır. Bu derste bu durumu ölçüyoruz.

Çekirdek dosya konunun her dersinde aynıdır.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

Bu derste işlemler duruma yalnız bir liste yazar ve eşzamansız değildir; bu yüzden `replay` ile ölçülür. Model, işlemin duruma ne yaptığını kurar; ağın ya da ekranın kendisi değildir. Hangi iş parçacığının hangi işi koşturduğu ölçülmez, çünkü ölçüm bunu göremez.

#### Ana İş Parçacığı ve Ana Aktör

Arayüz yalnız **ana iş parçacığında** güncellenir. Eşzamanlı kodda bu kural **ana aktörle** (`@MainActor`) ifade edilir: ana aktöre bağlı bir tipin durumuna yalnız ana aktörden erişilir ve derleyici bunu denetler. Arka plandaki bir iş sonucunu ekrana yazmak istediğinde `await` ile ana aktöre geçer.

Bu, arayüzün tutarlılığını korur ama yarışı çözmez. Arka planda iki arama isteği koşuyorsa ikisinin de sonucu sonunda ana aktöre gelir ve sonuç hangisi geç gelirse ekranda o kalır. Yanıt sırası istek sırasından farklı olabilir.

```swift
// Shown.swift — ekranda gosterilen liste ve iki guncelleme bicimi
struct ShownList: Equatable, CustomStringConvertible {
    var version: Int
    var items: [String]

    var description: String { "v\(version) \(items)" }

    func overwriting(items newItems: [String]) -> ShownList {
        ShownList(version: version, items: newItems)
    }

    func applying(version newVersion: Int, items newItems: [String]) -> ShownList {
        newVersion > version ? ShownList(version: newVersion, items: newItems) : self
    }
}
```

`overwriting` gelen listeyi sürüme bakmadan ekrana yazar. `applying` ise yalnız gelen sürüm gösterilen sürümden büyükse yazar; eski sürümlü bir yanıt gelirse yok sayar.

#### Sürümsüz ve Sürümlü Güncelleme

İki yanıt var: eski sorgunun sonucu (sürüm 1, `eski`) ve yeni sorgunun sonucu (sürüm 2, `yeni`). `a` eski yanıtı, `b` yeni yanıtı yazar. İki sırada, sürümsüz ve sürümlü yazım ayrı ayrı ölçülür.

```swift
// main.swift — ayni iki yanit, surumsuz ve surumlu yazim
let start = ShownList(version: 0, items: [])

let blind = replay("surumsuz-yazim", from: start,
                   { $0.overwriting(items: ["eski"]) },
                   then: { $0.overwriting(items: ["yeni"]) }).report()

let checked = replay("surumlu-yazim", from: start,
                     { $0.applying(version: 1, items: ["eski"]) },
                     then: { $0.applying(version: 2, items: ["yeni"]) }).report()

countSensitivities([blind, checked])
```

```
surumsuz-yazim | bir=v0 ["eski"] iki=v0 ["eski"] ab=v0 ["yeni"] ba=v0 ["eski"] -> siraya-duyarli
surumlu-yazim | bir=v1 ["eski"] iki=v1 ["eski"] ab=v2 ["yeni"] ba=v2 ["yeni"] -> saglam
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

Sürümsüz yazımda yinelemek bir şey değiştirmez: `bir` ile `iki` aynı. Ama `ab` `yeni`, `ba` `eski`: ekranda son gelen kalır. Eski sorgunun yanıtı geç gelirse yeni sonucu ezer. İşlem **sıraya duyarlıdır**. Sürümlü yazımda eski yanıt, yeni yanıttan sonra gelse de yok sayılır; iki sıra da `v2 ["yeni"]` verir ve yinelemek de bir şey değiştirmez. İşlem **sağlamdır**. Sayım bir sağlam, bir sıraya duyarlı.

**ES11.** Ölçüm yanıtların kaybolmadığını, yalnız sıralarının değişebildiğini varsayar. Bir yanıtın hiç gelmediği ya da hata olduğu durum ölçülmedi.

**ES12.** Sürüm numaralarının 1 ve 2 olarak istek sırasıyla verildiği varsayılır. Numaranın kimin elinde üretildiği aşağıdaki sınırlayıcı ölçümde ele alınır.

#### Ekran Neden Tek Yerde Güncellenir

Ekranı çizen sistem tek bir akış üzerinde çalışır: bir karede neyin nerede görüneceğine karar verirken, gösterilen verinin o sırada değişmemesi gerekir. Liste çizilirken başka bir iş listeye eleman eklese, çizim ya eski ya yeni ya da yarım bir görüntü üretirdi. Bu yüzden arayüze dokunan her işlem tek bir yerde, ana iş parçacığında toplanır ve sırayla yürür. Ana aktör bu kuralın derleyici tarafından denetlenen biçimidir: `@MainActor` işaretli bir tipe başka yerden `await` olmadan dokunmak derleme hatasıdır.

Ana aktörün kazandırdığı şey kısaca şudur: ekranın durumunu yazan kod her zaman aynı yerde, tek tek çalışır. Kaybettirdiği şey de aynı kuraldan çıkar: ana aktörde yapılan her uzun iş ekranı bekletir. Bu yüzden iş bölünür. Ağ isteği, ayrıştırma ve sıralama arka planda yapılır; yalnız son adım, yani hazır listenin ekran durumuna yazılması ana aktörde olur. Bu dersin ölçümleri o son adımı modeller.

#### Ana Aktörde Güncelleme

Aynı kuralın gerçek koddaki karşılığı şudur. Parça ölçülmez, çünkü ana aktöre bağlı ekran durumu sandbox'ta anlamlı bir çıktı vermez.

```swift
import Foundation

@MainActor
final class SearchResults {
    private(set) var items: [String] = []
    private var shownVersion = 0

    func apply(version: Int, items newItems: [String]) {
        guard version > shownVersion else { return }
        shownVersion = version
        items = newItems
    }
}
```

`apply` yalnız daha yeni bir sürümü kabul eder. Sürüm numarası, isteği başlatan kodda üretilir: arama her başladığında sayaç bir artar ve numara isteğe bağlanır. Yanıt geldiğinde numarasını taşır.

#### Yükleniyor Göstergesi

Arama ekranlarında bir de dönen gösterge bulunur: istek giderken görünür, yanıt gelince kaybolur. Göstergeyi tek bir `Bool` ile tutmak akla en yakın kurulumdur ve tek istek varken doğru çalışır. İki istek üst üste bindiğinde ise ilk yanıt göstergeyi kapatır, ikinci istek hâlâ sürerken ekran bitmiş görünür. Alternatif, süren isteklerin sayısını tutmaktır: başlangıçta bir artar, yanıtta bir azalır, gösterge sayı sıfırdan büyük olduğu sürece görünür.

İki kurulum aynı çifte, yani başlatma ve bitirme işlemlerine ayrı ayrı uygulanır. Ölçüm tek bir başlatma ile tek bir bitirmeyi sınar; üst üste binen iki isteği doğrudan kurmaz.

```swift
// main.swift — yukleniyor gostergesi: bayrak ve sayac
let flag = replay("bayrak", from: false,
                  { _ in true },
                  then: { _ in false }).report()

let counter = replay("sayac", from: 0,
                     { $0 + 1 },
                     then: { max($0 - 1, 0) }).report()

countSensitivities([flag, counter])
```

```
bayrak | bir=true iki=true ab=false ba=true -> siraya-duyarli
sayac | bir=1 iki=2 ab=0 ba=1 -> cift-duyarli
saglam: 0
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 1
```

Bayrak satırında yinelemek bir şey değiştirmez, çünkü ikinci başlatma göstergeyi zaten açık bulur. Ama `ab` ile `ba` ayrılır: bitirme başlatmadan önce gelirse gösterge açık kalır. Bayrak **sıraya duyarlıdır**. Sayaç satırı ise **çift duyarlı** çıktı, ve bu bir kusur değil, sayacın ne olduğunun doğru sonucudur: her başlatma gerçekten bir istek daha demektir, bu yüzden iki başlatma bir başlatmadan farklıdır. Duyarlılığın doğru okunuşu şudur: bir sınıf "iyi" ya da "kötü" değildir, işlemin ne iddia ettiğini gösterir. Sayaç iki başlatmayı iki istek olarak sayar ve bunu yapması gerekir; hata, aynı başlatma bildirimi yinelenerek iki kez uygulandığında ortaya çıkar. Yinelenen bildirim ağ katmanlarında ve olay yayımcılarında görülür; sayaçla çalışan bir gösterge bu durumda sonsuza dek açık kalır.

**ES13.** Bayrak ve sayaç ölçümü, tek başlatma ile tek bitirmeyi kurar. Üst üste binen iki isteğin dört işlemlik sıralamaları (başlat, başlat, bitir, bitir) ölçülmedi.

#### Gerçek Uygulamada

Bir arama ekranında kullanıcı her harfe bastığında istek gider. En sık görülen kurulum, her değişiklikte bağımsız bir görev başlatıp sonucu doğrudan ekran durumuna yazmaktır.

```swift
// Yanlis: her harf yeni gorev acar, eski gorev iptal edilmez
@MainActor
final class SearchModel {
    private(set) var results: [String] = []

    func queryChanged(_ text: String) {
        Task {
            let found = await search(text)
            results = found
        }
    }
}
```

Bu kod sürümsüz yazımın ta kendisidir. Hızlı yazan kullanıcı için `a`, `ab` ve `abc` istekleri art arda gider; yavaş ağda `ab` yanıtı `abc` yanıtından sonra gelirse ekran `abc` yazarken `ab` sonuçlarını gösterir. Geliştirici makinesinde ağ hızlı ve düzenlidir, hata görünmez; üretimde bir mobil bağlantıda görünür.

Düzeltmenin iki yolu vardır ve çoğu zaman ikisi birlikte kullanılır. Birincisi önceki görevi iptal etmektir: görevin tutamacı saklanır, yeni harf gelince eskisi iptal edilir ve eski görev sonucu yazmadan önce iptal edilip edilmediğine bakar. İkincisi, dersin ölçtüğü gibi sürüm numarasıdır: numara istek başlarken, yani ana aktörde ve sırayla verilir, yanıtla birlikte döner ve `apply` daha eski numarayı elemeye devam eder. İptal gereksiz ağ işini durdurur; sürüm ise iptale rağmen ekrana ulaşmış bir yanıtı eler. İptal yoklaması yapılmayan bir görev işaretini taşıyıp işini bitirebilir, bu yüzden iptali tek başına yeterli saymak da bir kurulum hatasıdır.

Bu parça ölçülmedi: gerçek `Task` zamanlaması, ağ gecikmesi ve iptalin görevin hangi noktasında göründüğü ardışık bir koşumda kurulmaz. Ölçülen, yazım kuralının kendisidir.

#### Sık Yapılan Hatalar

- Ekran durumunu arka plan işinden doğrudan değiştirmeye çalışmak. Ana aktöre `await` ile geçmek gerekir.
- Sonucun ekrana yazılma anını isteğin başlama anıyla karıştırmak. Yazım, yanıt geldiğinde olur.
- Eski bir yanıtı yeni olanın üstüne yazmak. Sürüm ya da iptal olmadan bunu engelleyen bir şey yoktur.
- Ana aktörü uzun bir hesabın işyeri yapmak. Ekran o sürede tepkisiz kalır.

#### Sınırlayıcı Ölçüm

Sürümün işe yaraması, numarayı kimin verdiğine bağlıdır. Numarayı isteği başlatan kod verirse eski yanıt eski numarayı taşır ve elenir. Numarayı yanıt geldiğinde ekran verirse her yanıt öncekinden yeni görünür.

```swift
// main.swift — surum numarasi gelisinde verilirse
let stampedStart = ShownList(version: 0, items: [])

let stamped = replay("gelisinde-damga", from: stampedStart,
                     { $0.applying(version: $0.version + 1, items: ["eski"]) },
                     then: { $0.applying(version: $0.version + 1, items: ["yeni"]) }).report()

countSensitivities([stamped])
```

```
gelisinde-damga | bir=v1 ["eski"] iki=v2 ["eski"] ab=v2 ["yeni"] ba=v2 ["eski"] -> cift-duyarli
saglam: 0
yinelemeye-duyarli: 0
siraya-duyarli: 0
cift-duyarli: 1
```

Aynı iki yanıt, aynı `applying` kuralı; yalnız numara değişti. Her yanıt gelişinde bir üst numara aldığı için hiçbiri elenmedi. `bir=v1` ile `iki=v2` farklıdır: ikinci uygulama sürümü yine artırdı ve işlem yinelemeye duyarlı oldu. `ab` ile `ba` da farklıdır. İşlem **çift duyarlıdır**. Kural nedeni görmez, eşitsizliği görür; sürüm kontrolü vardı ama numarayı yanlış yerde veren kod onu etkisiz kıldı. Sayım bir çift duyarlı.

**ES14.** Bu ölçüm sürümü yanıt gelişinde tek bir adımla artıran bir modeldir. Numaranın bir sayaçtan değil de bir zaman damgasından üretildiği durum ölçülmedi.

#### Yanıt Gelmediğinde

Ölçümler her yanıtın sonunda geldiğini varsayar. Gerçek ağda bir yanıt hiç gelmeyebilir ya da hata ile dönebilir ve bu durum ekran için ayrı bir karardır. En yaygın kurulum hatası, hatayı yutup gösterge açık bırakmaktır: kullanıcı sonsuza dek dönen bir göstergeye bakar. Hata durumu da bir sürümü taşımalıdır; çünkü eski bir isteğin geç gelen hatası, yeni isteğin başarılı sonucunun üstüne hata mesajı yazmamalıdır. Kural aynıdır: ekran durumuna yazan her yol, yazmadan önce kendi isteğinin hâlâ güncel olup olmadığını sorar.

Bunun pratik sonucu, ekran durumunu tek bir yazma kapısından geçirmektir. Başarı, hata ve iptal aynı işleve gelir ve işlev sürümü bir kez denetler. Üç ayrı yerde üç ayrı denetim yazan kod, bir gün bu denetimlerden birini unutur; unutulan yer genellikle en nadir görülen yoldur, yani hata yoludur ve kimse onu geliştirme sırasında denemez. Ölçümlerimiz de bu yüzden iki yanıtlı basit bir modelle başlar: önce temel kuralı görünür kılar, sonra kuralın hangi yollara uygulanacağı sorusunu okura bırakır.

Ekranın dayanıklılığını sınamanın yolu, yanıtları kasıtlı olarak ters sırada ve geciktirerek göndermektir. Ağı yavaşlatan bir geliştirici ayarı ya da yanıt sırasını değiştiren bir sahte sunucu, sürümsüz yazımı dakikalar içinde ortaya çıkarır. Bu sınama ölçümün yerini almaz; ölçüm kuralın neden çalıştığını, sınama ise uygulamanın kuralı gerçekten uyguladığını gösterir.

#### Ana Aktörü Hafif Tutmak

Ana aktör tek bir yoldur ve üzerinde ne kadar iş yapılırsa ekran o kadar geç yanıt verir. Pratik bir ölçü, bir işin ana aktörde geçirdiği sürenin bir kare süresinden kısa kalmasıdır; bu süre ekranın yenileme hızına bağlıdır. Bunun üstüne çıkan her iş, kaydırma sırasında takılma olarak görünür. Büyük bir listeyi sıralamak, bir görseli çözmek ya da bir yanıtı ayrıştırmak bu eşiği hızla aşar; bu işler arka planda yapılır ve ana aktöre yalnız hazır sonuç taşınır.

Burada ince bir nokta var: sonuç ana aktöre taşınırken sürüm de taşınmalıdır. Arka plandaki iş başladığında sürüm numarasını alır, bitince ana aktöre bu numarayla döner ve ana aktör numarayı güncel numarayla karşılaştırır. Numarayı arka planın bitiş anında üretmek, dersin son ölçümündeki hatanın aynısıdır. Bu yüzden numaranın kaynağı her zaman işin başladığı yerdir. Numara başlangıçta bir kez alınır, iş boyunca değişmez ve sonuçla birlikte geri döner; böylece ekrana yazan kod, hangi isteğin yanıtını taşıdığını her zaman bilir. Bu bilgi olmadan hiçbir denetim güvenilir olmaz.

#### Özet

- Arayüz durumu ana iş parçacığına bağlıdır; eşzamanlı kodda bu ana aktörle ifade edilir.
- Sürümsüz yazım geliş sırasına duyarlıdır; eski yanıt yeniyi ezebilir.
- Sürüm kontrolü eski yanıtı eler ve yazımı sağlam yapar.
- Numarayı yanıt gelişinde vermek kontrolü etkisiz bırakır ve işlemi çift duyarlı yapar.
- Hangi iş parçacığının hangi işi koşturduğu ölçülmedi.
- Bu dersin duyarlılığı: **sıraya duyarlı** (sürümsüz) ve **sağlam** (sürümlü).

#### Sonraki Adım

Ana aktör ve aktörler yeni kodun yapı taşlarıdır; ama eski kod hâlâ dağıtım kuyruklarıyla yazılmıştır. Sıradaki derste seri bir kuyruğa iş ekleme ve değer atama bloklarını ölçeceğiz ve kuyruğun neyi sıraya soktuğuna bakacağız.

### Kuyruk Tabanlı Eşzamanlılık

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/kuyruk-tabanli-eszamanlilik

Seri bir kuyruğa eklenen işlerin sırayı koruduğunun, ama sona ekleme ile değer atamanın yineleme ve sıra karşısında farklı sınıflara düştüğünün ölçümü.

Önceki derste sürüm kontrolü, geç gelen eski bir yanıtın yeni sonucu ezmesini önledi; numarayı yanlış yerde vermek ise kontrolü etkisiz bıraktı. Eşzamanlı Swift yazılmadan önce işler **dağıtım kuyruklarıyla** yürütülüyordu ve çok sayıda kod hâlâ öyle yazılıdır. Bu derste kuyruğa eklenen iki tür işi ölçüyoruz.

Çekirdek dosya konunun her dersinde aynıdır.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

Bu derste işlemler kuyruğa eklenir ve kuyruk boşaltılıp sonuç okunur. Dört koşum her seferinde yeni bir kuyrukta kurulur ve `Trial`'a doğrudan verilir. Model, işlemin duruma ne yaptığını kurar; ağın ya da çalışma zamanının kendisi değildir.

#### Seri ve Eşzamanlı Kuyruk

Bir **dağıtım kuyruğu**, kendisine eklenen işleri bir iş parçacığı havuzunda çalıştırır. **Seri kuyruk** işleri eklendikleri sırayla ve birer birer çalıştırır: bir iş bitmeden sıradaki başlamaz. **Eşzamanlı kuyruk** ise işleri aynı anda başlatabilir; yalnız başlama sırası eklenme sırasıdır, bitiş sırası belli değildir.

Seri kuyruğun ürettiği sonuç, ekleme sırası belliyse deterministiktir ve bu yüzden ölçülebilir. Eşzamanlı kuyrukta bitiş sırası zamanlamaya bağlı olduğu için ölçüme girmez. Bu derste yalnız seri kuyruk ölçülür.

```swift
// Queued.swift — seri kuyrukta sirayla calisan ekleme ve atama islemleri
import Dispatch

final class Cell: @unchecked Sendable {
    var lines: [String] = []
    var current = "-"
}

func makeSerialQueue() -> DispatchQueue {
    .init(label: "serial")
}

func runAppends(_ labels: [String]) -> [String] {
    let cell = Cell()
    let queue = makeSerialQueue()
    for label in labels {
        queue.async { cell.lines.append(label) }
    }
    queue.sync {}
    return cell.lines
}

func runAssigns(_ labels: [String]) -> String {
    let cell = Cell()
    let queue = makeSerialQueue()
    for label in labels {
        queue.async { cell.current = label }
    }
    queue.sync {}
    return cell.current
}
```

`Cell`, kuyruğun işlediği paylaşılan durumdur; derleyiciye "bunu ben koruyorum" demek için `@unchecked Sendable` taşır, yani güvence kuyruktan gelir, derleyiciden gelmez. `runAppends` her etiketi bir iş olarak kuyruğa ekler. `runAssigns` ise her etiketi tek bir alana yazar. Sonda boş bir `sync {}` bloğu kuyruğu boşaltır: seri kuyrukta bu blok eklenen tüm işlerden sonra çalışır, dolayısıyla dönüşte hepsi bitmiştir.

#### Sona Ekleme ve Değer Atama

`a` etiketi `a`, `b` etiketi `b` olan iki işi iki ayrı işlemle ölçüyoruz: sona ekleme ve değer atama.

```swift
// main.swift — kuyruga sona ekleme ve deger atama
let appendOnce = runAppends(["a"])
let appendTwice = runAppends(["a", "a"])
let appendForward = runAppends(["a", "b"])
let appendBackward = runAppends(["b", "a"])
let appending = Trial(label: "sona-ekleme", once: appendOnce, twice: appendTwice, forward: appendForward, backward: appendBackward).report()

let assignOnce = runAssigns(["a"])
let assignTwice = runAssigns(["a", "a"])
let assignForward = runAssigns(["a", "b"])
let assignBackward = runAssigns(["b", "a"])
let assigning = Trial(label: "deger-atama", once: assignOnce, twice: assignTwice, forward: assignForward, backward: assignBackward).report()

countSensitivities([appending, assigning])
```

```
sona-ekleme | bir=["a"] iki=["a", "a"] ab=["a", "b"] ba=["b", "a"] -> cift-duyarli
deger-atama | bir=a iki=a ab=b ba=a -> siraya-duyarli
saglam: 0
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 1
```

Sona ekleme satırında `bir` ile `iki` farklıdır: ikinci ekleme listeye bir satır daha yazar. `ab` ile `ba` da farklıdır: liste ekleme sırasını taşır. İşlem **çift duyarlıdır**. Değer atama satırında `bir` ile `iki` aynıdır, çünkü aynı değeri yeniden atamak bir şey değiştirmez. Ama `ab` `b`, `ba` `a`: son atanan kazanır. İşlem **sıraya duyarlıdır**. Seri kuyruk sırayı korudu; ama sıra korunduğu için sonuç sıraya bağlı oldu. Sayım bir sıraya duyarlı, bir çift duyarlı.

**ES15.** Bu ölçüm eklenen işlerin tek bir kuyrukta, yazıldıkları sırayla çalıştığını varsayar. Birden çok kuyruğun aynı `Cell`'e eriştiği durum ölçülmedi.

**ES16.** İki ekleme işi aynı etiketle, aynı iş sayılır. İki işin kendi aralarında ayırt edilebildiği ve farklı etiket taşıdığı durum yalnız `ab` ile `ba` satırında vardır.

#### Tekrarı Elemek

Çift duyarlılığın bir kısmı tekrardan gelir: aynı etiket iki kez eklenince liste uzar. Pratikte bu çoğu zaman istenmez; bir bildirimin gönderildiğini kaydeden liste, aynı bildirimi iki kez içermemelidir. Çare, eklemeden önce etiketin listede olup olmadığına bakmaktır. Önemli olan, bakma ile ekleme işinin kuyruğun aynı işinde yapılmasıdır; ikisi ayrı işler olursa aralarına başka bir iş girebilir.

```swift
// Unique.swift — yalniz yeni etiketi ekleyen kuyruk islemi
import Dispatch

func runUniqueAppends(_ labels: [String]) -> [String] {
    let cell = Cell()
    let queue = makeSerialQueue()
    for label in labels {
        queue.async {
            if !cell.lines.contains(label) { cell.lines.append(label) }
        }
    }
    queue.sync {}
    return cell.lines
}
```

```swift
// main.swift — tekrari eleyen ekleme
let uniqueOnce = runUniqueAppends(["a"])
let uniqueTwice = runUniqueAppends(["a", "a"])
let uniqueForward = runUniqueAppends(["a", "b"])
let uniqueBackward = runUniqueAppends(["b", "a"])
let unique = Trial(label: "benzersiz-ekleme", once: uniqueOnce, twice: uniqueTwice, forward: uniqueForward, backward: uniqueBackward).report()

countSensitivities([unique])
```

```
benzersiz-ekleme | bir=["a"] iki=["a"] ab=["a", "b"] ba=["b", "a"] -> siraya-duyarli
saglam: 0
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

Aynı etiketin ikinci eklenişi artık listeyi değiştirmez; `bir` ile `iki` eşittir. Ama `ab` ile `ba` hâlâ ayrıdır, çünkü liste eklenme sırasını taşır. İşlem **sıraya duyarlıdır**. Önceki satırın çift duyarlılığından yinelemeye duyarlılık çıkmış, sıra duyarlılığı kalmıştır. Bu, tekrarı elemenin neyi çözdüğünü ve neyi çözmediğini gösterir: tekrar bir kopyaya yol açmaz, ama hangi etiketin önce geleceği sorusunu da yanıtlamaz.

**ES17.** Tekrar eleme, etiketlerin tam eşitliğine dayanır. Aynı anlama gelen ama farklı yazılmış etiketler (örneğin büyük küçük harf farkı) ölçülmedi.

#### Eşzamanlı Kuyruk ve Bariyer

Eşzamanlı kuyrukta okumalar birlikte koşabilir, ama yazma tek başına koşmalıdır. Bunun için yazma işi **bariyer** işareti taşır: bariyerli iş, kuyrukta önüne ve arkasına geçen hiçbir işle aynı anda çalışmaz.

```swift
// Es zamanli kuyruk ve bariyer: okumalar birlikte, yazma tek basina
let shared = DispatchQueue(label: "shared", attributes: .concurrent)
nonisolated(unsafe) var values: [String] = []

func read() -> Int {
    shared.sync { values.count }
}

func write(_ value: String) {
    shared.async(flags: .barrier) { values.append(value) }
}
```

Bariyer, yazmanın okumalarla çakışmasını önler. Hangi okumanın hangi yazmadan önce ya da sonra çalışacağı yine ekleme sırasına ve zamanlamaya bağlıdır; bu yüzden parça ölçülmez.

#### Gerçek Uygulamada

Eski kodda kuyruk çoğunlukla bir kilit yerine kullanılır. Bir sınıfın paylaşılan bir sözlüğü vardır; her okuma ve yazma aynı seri kuyruğa gönderilir ve sözlüğe yalnız o kuyruktan dokunulur. Bu kurulum, önbellek ve oturum bilgisi gibi yapılarda onlarca yıl çalışmıştır. Kuyruk sırayı korur ve aynı anda yalnız bir iş çalıştırır; yani tam olarak önceki iki dersin ölçtüğü şeyi, aktörden önceki biçimiyle yapar.

Kuyruğun asıl tuzağı, korumanın disipline bağlı olmasıdır. Sözlüğe kuyruk dışından tek bir satırla dokunan kod, bütün korumayı geçersiz kılar ve bunu derleyici söylemez. Bir diğer sık hata, kuyruğa yazıp sonucu aynı anda okumaya çalışmaktır.

```swift
// Yanlis: yazma kuyrukta, okuma kuyrugun disinda
func record(_ label: String, in cell: Cell, on queue: DispatchQueue) -> Int {
    queue.async { cell.lines.append(label) }
    return cell.lines.count
}
```

`async` çağrısı işi kuyruğa bırakır ve hemen döner; iş henüz çalışmamış olabilir. Dönen sayı ekleme öncesinin sayısıdır, bazen sonrasınındır, ve hangisi olduğu zamanlamaya bağlıdır. Üstelik okuma kuyruk dışında yapıldığı için `cell.lines`'a yazan iş ile aynı anda çalışabilir; bu bir veri yarışıdır. Düzeltme, okumayı da aynı kuyruğa vermek ve sonucu `sync` ile beklemektir. Ama ana iş parçacığından bir kuyruğa `sync` ile girmek ekranı o kuyruk kadar bekletir; bu yüzden yeni kodda aynı iş aktörle yazılır ve okuma `await` ile yapılır.

Başka bir tuzak, kuyruğun içinden aynı kuyruğa `sync` ile girmektir: kuyruk, kendi çalışan işinin bitmesini bekler ve iş de kuyruğun boşalmasını bekler. Bu yüzden çağıranın hangi kuyrukta olduğunu bilmeyen yardımcı işlevler, `sync` yerine `async` ile geri çağrı almalıdır.

Ölçümlerin yeniden okunuşu şudur: seri kuyruk sıra güvencesi verir, ama bu güvence sonucu sıradan bağımsız kılmaz. Sıranın önemli olduğu yerde kuyruk yeterli olabilir; önemli olmadığı yerde sonucu sıradan bağımsız kılacak olan işlemin kendisidir, yani değer atama yerine birleştirme, sona ekleme yerine küme gibi. Kuyruk korur, işlemin biçimi sağlamlaştırır.

#### Sık Yapılan Hatalar

- Kuyruğun sırayı korumasını sonucun sağlam olması sanmak. Sıra korunur, ama sonuç yine sıraya bağlı olabilir.
- Aynı kuyruğa kendi içinden `sync` ile iş eklemek. Kuyruk kendini bekler ve kilitlenir.
- Eşzamanlı kuyruğa bariyersiz yazmak. Okumalar yazmayla çakışır.
- Kuyruğu boşaltmadan sonucu okumak. Ekleme işleri henüz bitmemiş olabilir.

#### Sınırlayıcı Ölçüm

Aynı ekleme işlemini bu kez eklenen satırların sıralanmış hâli ve satır sayısıyla karşılaştırıyoruz. İşlem değişmedi; yalnız durumun görünümü değişti.

```swift
// main.swift — ayni ekleme, uc durum secimi
let rawTrial = Trial(label: "ham-liste", once: runAppends(["a"]), twice: runAppends(["a", "a"]), forward: runAppends(["a", "b"]), backward: runAppends(["b", "a"]))
let raw = rawTrial.report()

let sortedTrial = Trial(label: "sirali-liste", once: rawTrial.once.sorted(), twice: rawTrial.twice.sorted(), forward: rawTrial.forward.sorted(), backward: rawTrial.backward.sorted())
let sortedView = sortedTrial.report()

let sizeTrial = Trial(label: "satir-sayisi", once: rawTrial.once.count, twice: rawTrial.twice.count, forward: rawTrial.forward.count, backward: rawTrial.backward.count)
let sizeView = sizeTrial.report()

countSensitivities([raw, sortedView, sizeView])
```

```
ham-liste | bir=["a"] iki=["a", "a"] ab=["a", "b"] ba=["b", "a"] -> cift-duyarli
sirali-liste | bir=["a"] iki=["a", "a"] ab=["a", "b"] ba=["a", "b"] -> yinelemeye-duyarli
satir-sayisi | bir=1 iki=2 ab=2 ba=2 -> yinelemeye-duyarli
saglam: 0
yinelemeye-duyarli: 2
siraya-duyarli: 0
cift-duyarli: 1
```

Ham listede işlem **çift duyarlıdır**. Liste sıralanınca `ab` ile `ba` eşitlenir ve geriye yalnız yineleme farkı kalır; işlem **yinelemeye duyarlı** çıkar. Satır sayısında da aynı sonuç. Kural nedeni görmez, eşitsizliği görür; durumu sıralamak sıra farkını gizledi, ama yineleme farkını gizleyemedi çünkü yineleme satır sayısını gerçekten artırır. Sayım iki yinelemeye duyarlı, bir çift duyarlı.

**ES18.** Sıralanmış görünümde elemanlar karşılaştırılabilir kabul edilir. Sırasının anlamı olan elemanlarda, örneğin zaman damgalı satırlarda, sıralamanın hangi bilgiyi sildiği ölçülmedi.

#### Kuyruktan Aktöre Geçerken

Eski bir kuyruk tabanlı sınıfı aktöre çevirmek çoğu zaman mekanik görünür: sınıfı `actor` yapmak, kuyruğu kaldırmak, çağrılara `await` eklemek. Asıl iş çağıranlardadır. Eski kodda `sync` ile beklenen yerlerde artık `await` gerekir ve bu, çağıranın kendisinin eşzamansız bir bağlamda olmasını şart koşar. Geri çağrıyla çalışan çağıranlar, ya bir `Task` içine alınır ya da aktör çağrısı geri çağrıya sarılır; her iki durumda da çağrının bitiş zamanı artık çağrının dönüş zamanı değildir ve kod buna göre yeniden düşünülmelidir.

İkinci fark sıradır. Seri kuyruk, işleri `async` ile eklendikleri sırayla çalıştırırdı; aktör ise çağrıları geliş sırasıyla işlemeyi garanti etmez, çünkü iki çağıranın aktöre ulaşma sırası aralarındaki zamanlamaya bağlıdır. Eski kodun sessizce dayandığı sıra güvencesi, taşıma sırasında kaybolabilir. Bu yüzden taşımadan önce sıranın gerçekten gerektiği yerler belirlenir. Sıra gerekiyorsa çağrılar tek bir görevden art arda yapılır; gerekmiyorsa taşıma sorunsuzdur. Bu dersin ölçümleri, sıranın gerektiği yerlerin hangileri olduğunu bulmak için bir araçtır: sıraya duyarlı çıkan her işlem, taşımada dikkat isteyen bir işlemdir.

Son olarak, sınama. Kuyruk tabanlı kodu sınamak, kuyruğu boşaltan bir bekleme çağrısı gerektirir; aktör tabanlı kodda bu ihtiyaç ortadan kalkar çünkü her çağrı kendi sonucunu `await` ile döndürür. Sınamalar sadeleşir, ama sıra duyarlılığı olan işlemlerde sınamanın hâlâ iki sıralı bir senaryo içermesi gerekir.

#### Kuyruk Etiketleri ve Hata Ayıklama

Kuyruklara ad vermek küçük bir alışkanlıktır ve hata ayıklamada büyük fark yaratır. Bir çökme izinde ya da bir takılma raporunda kuyruğun adı görünür; adı `serial` olan bir kuyruktan hangi alt sistemin sorumlu olduğunu çıkarmak mümkün değildir, ama `reservations.state` gibi bir ad doğrudan yol gösterir. Bu yüzden ölçümlerdeki `serial` adı yalnız örnek amaçlıdır; gerçek kodda kuyruğun adı, koruduğu durumu anlatmalıdır.

Aynı sebeple bir kuyruğun yalnız bir durumu korumasına dikkat edilir. Birkaç ilgisiz durumu tek kuyruğa toplamak, birinin yavaş işinin diğerlerini beklettiği bir kurulum doğurur. Her durum için ayrı bir kuyruk, bağımsız sorunların bağımsız kalmasını sağlar; ama birden çok kuyruğa dokunan bir işlem artık sıra konusunda tek bir güvence vermez ve ölçümdeki ilk uyarı burada geçerli olur: birden çok kuyruğun aynı duruma eriştiği durum ölçülmedi. Kuyruk adlarını, kodu okuyan bir sonraki kişinin ilk bakışta anlayacağı biçimde seçmek, en ucuz hata ayıklama yatırımıdır; çünkü takılma raporlarında yalnız bu ad görünür ve aranacak yeri o daraltır. Aynı şey sınama çıktıları için de geçerlidir: başarısız bir sınama, hangi kuyruğun hangi durumu bozduğunu adından söyleyebiliyorsa, hatayı bulmak dakikalar yerine saniyeler sürer. Ad vermeyi kodun sonunda eklenecek bir süs saymamak, ilk yazılışta yapılması gereken küçük bir karardır ve sonradan eklenmesi çoğu zaman unutulur.

#### Özet

- Seri kuyruk işleri eklendikleri sırayla ve birer birer çalıştırır; eşzamanlı kuyruk işleri aynı anda başlatabilir.
- Kuyruğa sona ekleme hem yinelemeye hem sıraya duyarlıdır.
- Kuyruğa değer atama yalnız sıraya duyarlıdır; aynı değeri yeniden atamak etkisizdir.
- Bariyer, eşzamanlı kuyrukta yazmanın okumalarla çakışmasını önler.
- Durumu sıralayarak ölçmek sıra farkını gizler.
- Bu dersin duyarlılığı: **çift duyarlı** (ekleme) ve **sıraya duyarlı** (atama).

#### Sonraki Adım

Kuyruklar işleri sıraya soktu; ama bir değerin zaman içinde akıp birden çok aboneye ulaşması başka bir modeldir. Sıradaki derste tepkisel akışlarda aynı yayımcıya iki kez abone olmanın ve ardışık değerleri düşüren işleçlerin ne yaptığını ölçeceğiz.

### Tepkisel Akışlar

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/tepkisel-akislar

Bir yayımcıya yinelenen abonelikte isteğin tekrarlandığının, paylaşımlı akışta ise sağlam kaldığının, ardışık tekrarı düşüren işlecin ise değer sırasına bağlı olduğunun ölçümü.

Önceki derste kuyruğa ekleme hem yinelemeye hem sıraya duyarlı çıktı, değer atama ise yalnız sıraya. Bu derste eşzamanlı kodun bir üçüncü biçimine bakıyoruz: değerlerin zaman içinde aktığı ve birden çok yerin onları dinlediği tepkisel akışlara.

Çekirdek dosya konunun her dersinde aynıdır.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

Bu derste işlemler duruma yalnız bir akışın bilgisini yazar ve eşzamansız değildir; bu yüzden `replay` ile ölçülür. Model, işlemin duruma ne yaptığını kurar; ağın ya da akışı çalıştıran çerçevenin kendisi değildir.

#### Yayımcı, Abone ve İşleç

**Tepkisel akışta** bir **yayımcı** değer üretir, bir **abone** o değerleri alır ve aradaki **işleçler** değerleri dönüştürür ya da eler. Bir `map` işleci her değeri dönüştürür, bir `removeDuplicates` işleci ardışık tekrarı düşürür. Yayımcı soğuk olabilir: abone olunduğunda işi başlatır, örneğin ağ isteğini gönderir. Yayımcı paylaşımlı ise iş yalnız ilk abonede başlar ve sonraki aboneler aynı sonucu alır.

Bu ayrım ölçülebilir bir sonuç doğurur. Paylaşımsız bir akışa her abone olunduğunda istek yeniden gider. Paylaşımlı akışta ise ikinci abone isteği tekrarlamaz.

```swift
// Feed.swift — akisa abone olma ve ardisik tekrari dusuren islec
struct Delivery: Equatable, CustomStringConvertible {
    var requests: Int
    var listeners: [String]

    var description: String { "istek=\(requests) dinleyen=\(listeners)" }
}

func subscribing(_ name: String) -> (Delivery) -> Delivery {
    { state in
        var next = state
        next.requests += 1
        if !next.listeners.contains(name) { next.listeners = (next.listeners + [name]).sorted() }
        return next
    }
}

func subscribingShared(_ name: String) -> (Delivery) -> Delivery {
    { state in
        var next = state
        if next.listeners.isEmpty { next.requests += 1 }
        if !next.listeners.contains(name) { next.listeners = (next.listeners + [name]).sorted() }
        return next
    }
}

struct Output: Equatable, CustomStringConvertible {
    var emitted: [Int]

    var description: String { "\(emitted)" }
}

func emitting(_ value: Int) -> (Output) -> Output {
    { state in
        state.emitted.last == value ? state : Output(emitted: state.emitted + [value])
    }
}
```

`subscribing` paylaşımsız aboneliği kurar: her çağrı isteği bir artırır ve adı dinleyenlere ekler. `subscribingShared` paylaşımlı olanı kurar: istek yalnız dinleyen yokken artar. Dinleyenler sıralı tutulur, çünkü abone sırası ölçüme girmemeli; bu bir tasarım seçimidir ve ES19'da anılır. `emitting` ardışık tekrarı düşüren işleci kurar: yeni değer son değere eşitse çıktıya dokunmaz.

#### Akış Neden Var

Bir değer tek seferde hesaplanıp dönüyorsa eşzamansız işlev yeter. Akış, değerin zaman içinde birden çok kez geldiği durumlar içindir: arama kutusundaki her harf, konum güncellemeleri, bir sunucudan gelen olaylar. Bu durumlarda "değeri al" yerine "değer geldikçe şunu yap" demek gerekir ve akış bu ikinci cümlenin kurulma biçimidir. Yayımcı ile abone arasında bir sözleşme vardır: yayımcı değerleri sırayla verir, abone onları aldığı sırayla işler, aradaki işleçler her değeri geçirir, dönüştürür ya da düşürür.

Bu sözleşme iki şeyi aboneye bırakır. Birincisi aboneliğin ömrüdür: abonelik tutulduğu sürece değer gelir, bırakıldığında akış sonlanır. İkincisi aboneliğin maliyetidir: her abonelik, yayımcı soğuksa, arkasındaki işi yeniden başlatır. Önceki derslerdeki sorular burada aynen sürer; yineleme ve sıra duyarlılığı, değerlerin geliş biçimine taşınmıştır.

#### Abonelik ve Paylaşım

Önce aynı ad iki kez abone olur. Sonra iki farklı ad, hem paylaşımsız hem paylaşımlı akışa abone olur.

```swift
// main.swift — paylasimsiz ve paylasimli abonelik
let fresh = Delivery(requests: 0, listeners: [])

let cold = replay("paylasimsiz-ayni-ad", from: fresh, subscribing("ayse"), then: subscribing("ayse")).report()
let sharedSame = replay("paylasimli-ayni-ad", from: fresh, subscribingShared("ayse"), then: subscribingShared("ayse")).report()
let sharedPair = replay("paylasimli-iki-ad", from: fresh, subscribingShared("ayse"), then: subscribingShared("burak")).report()

countSensitivities([cold, sharedSame, sharedPair])
```

```
paylasimsiz-ayni-ad | bir=istek=1 dinleyen=["ayse"] iki=istek=2 dinleyen=["ayse"] ab=istek=2 dinleyen=["ayse"] ba=istek=2 dinleyen=["ayse"] -> yinelemeye-duyarli
paylasimli-ayni-ad | bir=istek=1 dinleyen=["ayse"] iki=istek=1 dinleyen=["ayse"] ab=istek=1 dinleyen=["ayse"] ba=istek=1 dinleyen=["ayse"] -> saglam
paylasimli-iki-ad | bir=istek=1 dinleyen=["ayse"] iki=istek=1 dinleyen=["ayse"] ab=istek=1 dinleyen=["ayse", "burak"] ba=istek=1 dinleyen=["ayse", "burak"] -> saglam
saglam: 2
yinelemeye-duyarli: 1
siraya-duyarli: 0
cift-duyarli: 0
```

Paylaşımsız akışta ikinci abonelik isteği yeniden gönderir: `bir` 1, `iki` 2. Dinleyen listesi aynı kalır, ama istek sayısı artar. İşlem **yinelemeye duyarlıdır**. Paylaşımlı akışta istek yalnız ilk abonede artar; aynı adla yeniden abone olmak ya da başka bir ad eklemek isteği değiştirmez. Dinleyenler sıralı tutulduğu için iki sıra aynı listeyi verir ve her iki paylaşımlı işlem de **sağlamdır**. Sayım iki sağlam, bir yinelemeye duyarlı.

**ES19.** Dinleyen listesi sıralı tutuldu; abone sırasının sonucu değiştirdiği bir akış ölçülmedi. Paylaşımlı akışın ilk abone ayrıldıktan sonra yeniden başlayıp başlamadığı da ölçülmedi.

#### Ardışık Tekrarı Düşürmek

`removeDuplicates` işleci, bir değer son değerle aynıysa onu geçirmez. İşleci iki değerle ölçüyoruz: 3 ve 5.

```swift
// main.swift — ardisik tekrari dusuren islec
let fresh = Output(emitted: [])

let distinct = replay("tekrar-dusurme", from: fresh, emitting(3), then: emitting(5)).report()

countSensitivities([distinct])
```

```
tekrar-dusurme | bir=[3] iki=[3] ab=[3, 5] ba=[5, 3] -> siraya-duyarli
saglam: 0
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

İkinci 3 düşürülür, bu yüzden `bir` ile `iki` aynıdır. Ama değerlerin geliş sırası çıktının sırasıdır: `ab` `[3, 5]`, `ba` `[5, 3]`. İşlem **sıraya duyarlıdır**. Sayım bir sıraya duyarlı.

**ES20.** Ölçüm yalnız ardışık tekrarı düşüren işleci modeller. Gecikmeli tetikleme gibi zamana bağlı işleçler sonuç zamanlamaya bağlı olduğu için ölçülmedi.

#### Toplayan ve Son Değeri Tutan İşleçler

Akışlarda iki işleç çok kullanılır. Biri gelen değerleri toplayıp her adımda toplamı yayımlayan işleçtir; sayaçlar, sepet tutarları ve okunmamış mesaj sayıları böyle kurulur. Diğeri yalnız son değeri tutan işleçtir; ekranda gösterilen alanın değeri, bir ayarın geçerli hâli gibi. İkisi aynı iki değerle (3 ve 5) ve aynı dört koşumla ölçülür. Durum burada düz bir tamsayıdır.

```swift
// main.swift — toplayan ve son degeri tutan islec
let running = replay("toplama", from: 0, { $0 + 3 }, then: { $0 + 5 }).report()
let latest = replay("son-deger", from: 0, { _ in 3 }, then: { _ in 5 }).report()

countSensitivities([running, latest])
```

```
toplama | bir=3 iki=6 ab=8 ba=8 -> yinelemeye-duyarli
son-deger | bir=3 iki=3 ab=5 ba=3 -> siraya-duyarli
saglam: 0
yinelemeye-duyarli: 1
siraya-duyarli: 1
cift-duyarli: 0
```

Toplama satırında `bir` 3, `iki` 6'dır: aynı değer iki kez gelirse toplam iki katı artar. Sıra önemli değildir; 3 sonra 5 de, 5 sonra 3 de sekiz verir. İşlem **yinelemeye duyarlıdır**. Son değer satırında aynı değerin ikinci gelişi bir şey değiştirmez, ama son gelen kazandığı için `ab` 5, `ba` 3'tür. İşlem **sıraya duyarlıdır**. Aynı akış iki farklı iş görebilir ve hangi işleçle kurulduğu duyarlılığı belirler.

Gerçek uygulamada bu fark şöyle görünür. Bir yayımcı ağ katmanında yeniden bağlanırken aynı olayı iki kez iletirse, toplayan bir gösterge (okunmamış sayısı) iki kez artar ve yanlış kalır; son değeri tutan bir alan ise aynı değeri iki kez yazar ve ekranda fark edilmez. Yinelenen olayın tehlikesi, işlecin türüne göre ölçülür. Sayaç gibi yinelemeye duyarlı bir işleç kullanıyorsanız, olayları ayırt eden bir kimlik taşımak ve işleçten önce tekrarı düşürmek gerekir.

**ES21.** Toplama ve son değer ölçümü tamsayı durumu kullanır. Toplamın taşması ve boş akışta ilk değerin ne olacağı ölçülmedi.

#### Gerçek Akış

Gerçek akış bir çerçeveden gelir ve ölçülen model onun yerine geçmez. Aşağıdaki parça yalnız modelin neyi temsil ettiğini gösterir.

```swift
import Combine

let subject = PassthroughSubject<Int, Never>()
let cancellable = subject
    .removeDuplicates()
    .sink { value in print(value) }

subject.send(3)
subject.send(3)
subject.send(5)
```

Burada `PassthroughSubject` bir yayımcıdır, `removeDuplicates` işleçtir, `sink` aboneliktir. Ardışık iki 3'ten yalnız biri aboneye ulaşır. Abonelik saklanmazsa akış hemen biter; bu yüzden `cancellable` bir değişkende tutulur.

#### Gerçek Uygulamada

Bir arama ekranında akış tipik olarak şöyle kurulur: kutudaki metin bir yayımcıdır; ardışık aynı metinleri düşüren işleç, kullanıcı aynı metni yeniden yazdığında isteği önler; bir gecikmeli tetikleme işleci, kullanıcı yazmayı bırakana dek bekler; son olarak bir abone isteği gönderir ve sonucu ekrana yazar. Her halka bu dersin bir ölçümüne karşılık gelir. Tekrar düşürme sıraya duyarlıdır, çünkü metinlerin geliş sırası önemlidir. Abonelik yinelemeye duyarlıdır, çünkü her abonelik bir isteği yeniden başlatabilir. Ekrana yazım ise önceki dersteki sürüm sorusunu yeniden açar.

En sık görülen kurulum hatası aboneliği her görünüşte yeniden kurmaktır.

```swift
// Yanlis: ekran her gorundugunde yeni abonelik, eskisi birakilmiyor
@MainActor
final class ListScreen {
    private var subscriptions: [AnyCancellable] = []

    func screenAppeared(_ feed: AnyPublisher<[String], Never>) {
        feed
            .sink { items in self.show(items) }
            .store(in: &subscriptions)
    }

    func show(_ items: [String]) {}
}
```

Kullanıcı ekrana her döndüğünde `screenAppeared` yeni bir abonelik ekler ve eskileri bırakmaz. Paylaşımsız bir yayımcıda her abonelik isteği yeniden gönderir; üç kez döndükten sonra aynı değer ekrana üç kez yazılır ve ağa üç istek gider. Bu, ölçülen `paylasimsiz-ayni-ad` satırının ekran karşılığıdır: aynı adla ikinci abone olmak isteği artırdı. Düzeltmenin iki biçimi vardır. Abonelik bir kez, ekranın oluşturulduğu yerde kurulur; ya da her görünüşte kurulacaksa önce eskisi bırakılır. Paylaşımlı bir yayımcı kullanmak da isteği tek yapar, ama iki ekranın aynı akışı ayrı ömürlerle dinlemesi durumunda ilk abonenin ayrılmasıyla akışın ne olacağı bu derste ölçülmedi.

Bu parça ölçülmedi: gerçek aboneliğin ömrü, bırakma zamanı ve yayımcının soğuk ya da paylaşımlı oluşu çerçevenin davranışına bağlıdır.

#### Sık Yapılan Hatalar

- Aboneliği bir değişkende tutmamak. Abonelik serbest kalınca akış iptal olur.
- Paylaşımsız akışa birden çok kez abone olup isteğin bir kez gittiğini sanmak.
- Ardışık tekrarı düşürmenin sıra bağımsız olduğunu sanmak. İşleç sırayı değil, ardışıklığı bozar.
- Ekranı güncelleyen aboneyi ana aktör dışında çalıştırmak.

#### Sınırlayıcı Ölçüm

Aynı işleç, değer çiftine göre farklı sınıfa düşer. Çift 3 ve 3 ise iki değer de aynıdır; çift 3 ve 5 ise farklıdır.

```swift
// main.swift — ayni islec, iki deger cifti
let fresh = Output(emitted: [])

let equalPair = replay("cift-3-3", from: fresh, emitting(3), then: emitting(3)).report()
let unequalPair = replay("cift-3-5", from: fresh, emitting(3), then: emitting(5)).report()

countSensitivities([equalPair, unequalPair])
```

```
cift-3-3 | bir=[3] iki=[3] ab=[3] ba=[3] -> saglam
cift-3-5 | bir=[3] iki=[3] ab=[3, 5] ba=[5, 3] -> siraya-duyarli
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

Aynı değerler ardışık gelince işleç ikincisini düşürür ve dört durum eşit çıkar: işlem **sağlam**. Farklı değerlerde sıra çıktıya yazılır ve işlem **sıraya duyarlı** olur. Kural nedeni görmez, eşitsizliği görür; işleci değil, işleçle birlikte gelen değer çiftini sınıflar. Bir işlecin sağlam olduğunu söylemeden önce hangi değerlerle denendiği yazılmalıdır. Sayım bir sağlam, bir sıraya duyarlı.

#### Akış mı, Eşzamansız İşlev mi

Akış her sorunun yanıtı değildir. Tek seferlik bir istek için eşzamansız işlev daha az kavram ve daha okunur bir hata yolu sunar. Akış, değerin tekrar tekrar geldiği ve birden çok yerin aynı değerleri dinlediği durumlarda yerini hak eder. Bir ayarın değişimlerini birkaç ekrana dağıtmak buna örnektir; bir profil bilgisini bir kez çekmek değildir.

Karışık kullanımda en sık hata, tek seferlik bir sonucu akışa sarıp ardından ilk değeri beklemektir. Bu hem bir abonelik ömrü sorunu getirir hem de hata yolunu iki katına çıkarır. Ters yönde de hata vardır: değerler sürekli geliyorsa ve kod bunları bir `for` döngüsüyle ilk değerden sonra bırakıyorsa, akışın asıl kazanımı kaybolur.

Bu dersin ölçümlerinin ortak sonucu şudur: akış zamanı kodun içine taşır, ama yineleme ve sıra soruları değişmez. Her işleç için aynı iki soru sorulur: aynı değer iki kez gelirse sonuç değişir mi, iki değer yer değiştirirse sonuç değişir mi. Cevap işleçten işlece farklıdır ve işleç seçmeden önce bu iki soruyu sormak, sonradan ortaya çıkacak hataların çoğunu önler.

Son olarak bir not: akışları sınamak, zamanı denetleyen bir zamanlayıcı gerektirir. Gecikmeli tetikleme gibi zamana bağlı işleçleri gerçek saatle sınamak yavaş ve kırılgandır. Sınama ortamında zamanlayıcıyı elle ilerleten bir düzenek kullanılır; böylece beş saniyelik bekleme, sınamada anlık bir adım olur. Zamanı elle ilerletmek, ayrıca yarış benzeri hataları da yeniden üretilebilir kılar: yanıtın hangi anda geldiği sınamanın içinde yazılı olduğu için hata her koşumda aynı yerde çıkar. Hatayı yeniden üretebilmek, onu düzeltmenin yarısıdır; ikinci yarısı, düzeltmenin gerçekten çalıştığını aynı senaryoyla göstermektir. Bu dersin modeli zamansızdır ve işleçlerin zamana bağlı yönlerini bilerek dışarıda bırakır.

#### Özet

- Tepkisel akışta yayımcı değer üretir, abone alır, işleçler dönüştürür ya da eler.
- Paylaşımsız akışa her abonelik isteği yineler; işlem yinelemeye duyarlıdır.
- Paylaşımlı akışta istek yalnız ilk abonede gider ve işlem sağlamdır.
- Ardışık tekrarı düşüren işleç, farklı değerlerde sıraya duyarlıdır.
- Aynı işleç eşit değer çiftinde sağlam çıkar; sınıfı işleç değil değer çifti belirler.
- Bu dersin duyarlılığı: **yinelemeye duyarlı** (paylaşımsız abonelik), **sıraya duyarlı** (farklı değerler) ve **sağlam** (paylaşımlı akış).

#### Sonraki Adım

Eşzamanlı kodun sonuçları ekranda kalıcı olmayabilir; uygulama kapanınca kaybolur. Sonraki konuda ilk ders, Basit Tercih Saklama, iki ekranın aynı tercihi yazdığı bir modeli ölçecek ve kalıcı durumun yinelemeye ve sıraya nasıl tepki verdiğine bakacak.

## Kalıcılık

Küçük ayarların kaydı, gizli değerlerin anahtarlıkta saklanması, dizin türleri ve yedekleme kuralları, ilişkisel yerel veritabanı, nesne kalıcılık çerçeveleri, aygıtlar arası bulut eşitleme ve uzak bildirimlerin kayıt ve izin akışı.

### Basit Tercih Saklama

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/basit-tercih-saklama

Küçük bir değerin kalıcı saklanmasında iki ekranın aynı anahtara yazma sırasının, açılış sayacının yinelenmesinin ve varsayılan değerin sonucu nasıl değiştirdiğinin ölçümü.

Önceki derste, Tepkisel Akışlar, bir değerin zaman içinde nasıl aktığını gördük. Akış bitince değer de gider. Uygulama kapanıp yeniden açıldığında yaşaması gereken küçük değerler için ise başka bir yer gerekir: tema seçimi, son açılan sekme, bir ipucunun gösterilip gösterilmediği. Bu konu o yeri anlatır; adı **kalıcılaştırma**dır. Kursun sorusu burada da değişmez: **bu işlem yinelenirse ya da sırası değişirse sonuç değişir mi?** Dört yanıt vardır ve kurs boyunca adları sabittir: **sağlam**, **yinelemeye duyarlı**, **sıraya duyarlı** ve **çift duyarlı**. Üst ad **duyarlılık**tır ve kural bunu etiketten değil, işlemin durumda bıraktığı sonuçtan okur.

Ölçüm çekirdek dosyayla yapılır. Her ders onu aynen verir.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

`bir` işlemin başlangıç durumuna bir kez, `iki` iki kez uygulanmasıdır; `ab` önce `a` sonra `b`, `ba` ise tersi sıradır. `bir` ile `iki` ayrışırsa işlem yinelemeye duyarlıdır; `ab` ile `ba` ayrışırsa sıraya duyarlıdır. Bu derste ölçülen işlemler bir tercih deposunun saf Swift modeline uygulanır. Model, işlemin duruma ne yaptığını kurar; deponun ya da diskin kendisi değildir.

#### Tercih Deposu Ne İçindir

Platformun tercih deposu anahtar-değer çiftleri saklar. Değerler dizgi, sayı, mantıksal değer, tarih ve bunlardan kurulu diziler ile sözlükler gibi basit tiplerdir. Bir `UserDefaults.standard` nesnesi uygulamanın tamamı için ortaktır: bir ekranın yazdığı değeri başka bir ekran aynı süreçte hemen okur. Bu kolaylık ölçümün asıl konusudur, çünkü ortak bir anahtara yazan iki ekran birbirinin değerini fark etmeden ezebilir.

Depo küçük ve gizli olmayan değerler içindir. Büyük veriler dosya sistemine ya da veritabanına gider; parola ve belirteç gibi gizli değerler bir sonraki dersin konusu olan anahtarlığa gider. Tercih deposuna yazılan bir değer okunabilir biçimde durur.

```swift
import Foundation

UserDefaults.standard.register(defaults: ["theme": "sistem", "launchCount": 0])
UserDefaults.standard.set("koyu", forKey: "theme")

let count = UserDefaults.standard.integer(forKey: "launchCount")
UserDefaults.standard.set(count + 1, forKey: "launchCount")

UserDefaults.standard.removeObject(forKey: "theme")
```

Bu parça KAYNAK'tır; platformun kendi sınıfını çağırdığı için burada koşmaz. Üç şey okunur: `set(_:forKey:)` değeri yazar, `integer(forKey:)` anahtar yoksa `0` döner, `removeObject(forKey:)` yazılmış değeri siler. Aşağıdaki model bu adları aynen taşır.

#### Tercih Deposunun Modeli

Model iki ayrı sözlük tutar: açıkça yazılan değerler ve kaydedilmiş varsayılan değerler. Okuma önce yazılanı, yoksa varsayılanı verir. Durumun karşılaştırılması ve basılması da bu **çözülmüş** değere göre yapılır.

```swift
// PreferenceModel.swift — tercih deposunun saf Swift modeli
struct PreferenceBox: Equatable, CustomStringConvertible {
    private(set) var stored: [String: String] = [:]
    private(set) var registered: [String: String] = [:]

    var resolved: [String: String] {
        registered.merging(stored) { _, explicit in explicit }
    }

    var description: String {
        let pairs = resolved.keys.sorted().map { "\($0)=\(resolved[$0] ?? "-")" }
        return pairs.isEmpty ? "-" : pairs.joined(separator: ",")
    }

    static func == (lhs: PreferenceBox, rhs: PreferenceBox) -> Bool {
        lhs.resolved == rhs.resolved
    }

    mutating func register(defaults: [String: String]) {
        registered.merge(defaults) { _, new in new }
    }

    mutating func set(_ value: String, forKey key: String) { stored[key] = value }
    mutating func set(_ value: Int, forKey key: String) { stored[key] = String(value) }
    mutating func removeObject(forKey key: String) { stored[key] = nil }

    func string(forKey key: String) -> String? { resolved[key] }
    func integer(forKey key: String) -> Int { Int(resolved[key] ?? "") ?? 0 }
}

func registeredBox(_ defaults: [String: String]) -> PreferenceBox {
    var box = PreferenceBox()
    box.register(defaults: defaults)
    return box
}
```

Sözlüklerin sırası belirsiz olduğu için `description` anahtarları sıralayarak basar. Boş durum `-` olarak görünür. Bu da bir seçimdir: kural çözülmüş değerleri karşılaştırır, deponun içinde yazılmış mı yoksa varsayılan mı olduğuna bakmaz.

**KL1.** Model, bir yazmanın aynı süreçteki sonraki okumalarda hemen görüldüğünü varsayar. Değerin diske ne zaman yazıldığı ölçülmedi.

**KL2.** Model iki ekranın tek bir depoyu paylaştığını varsayar. Her ekranın ayrı bir depo nesnesi kullandığı durum kurulmadı.

#### Üç İşlemin Ölçümü

Üç işlem kurulur. Birincisi iki ekranın aynı `theme` anahtarına farklı değer yazmasıdır. İkincisi iki ekranın farklı anahtarlara yazmasıdır. Üçüncüsü uygulama her açıldığında sayacı bir artırmaktır.

```swift
// main.swift — iki ekran ve bir acilis sayaci
func writeValue(_ value: String, forKey key: String) -> (PreferenceBox) -> PreferenceBox {
    { box in
        var next = box
        next.set(value, forKey: key)
        return next
    }
}

func incrementLaunchCount(_ box: PreferenceBox) -> PreferenceBox {
    var next = box
    next.set(next.integer(forKey: "launchCount") + 1, forKey: "launchCount")
    return next
}

let sameKey = replay("iki-ekran-ayni-anahtar", from: PreferenceBox(), writeValue("koyu", forKey: "theme"), then: writeValue("acik", forKey: "theme")).report()
let otherKeys = replay("iki-ekran-ayri-anahtar", from: PreferenceBox(), writeValue("koyu", forKey: "theme"), then: writeValue("tr", forKey: "language")).report()
let launches = replay("acilis-sayaci", from: PreferenceBox(), incrementLaunchCount, then: incrementLaunchCount).report()

countSensitivities([sameKey, otherKeys, launches])
```

```
iki-ekran-ayni-anahtar | bir=theme=koyu iki=theme=koyu ab=theme=acik ba=theme=koyu -> siraya-duyarli
iki-ekran-ayri-anahtar | bir=theme=koyu iki=theme=koyu ab=language=tr,theme=koyu ba=language=tr,theme=koyu -> saglam
acilis-sayaci | bir=launchCount=1 iki=launchCount=2 ab=launchCount=2 ba=launchCount=2 -> yinelemeye-duyarli
saglam: 1
yinelemeye-duyarli: 1
siraya-duyarli: 1
cift-duyarli: 0
```

İlk satırda aynı değeri iki kez yazmak sonucu değiştirmedi: `bir` ile `iki` aynı. Ama iki farklı değerin sırası belirleyici oldu: `ab` `acik`, `ba` `koyu`. Son yazan kazanır; kazananı yazma sırası belirler. Bu yüzden sınıf **sıraya duyarlı**dır.

İkinci satırda iki ekran farklı anahtarlara yazıyor. Yazma sırası sonucu değiştirmiyor ve aynı yazmayı yinelemek de değiştirmiyor. Sınıf **sağlam**dır. Bu satır, sorunun yazmanın kendisinde değil, iki yazmanın aynı anahtarı paylaşmasında olduğunu gösterir.

Üçüncü satırda sayaç bir kez uygulandığında `1`, iki kez uygulandığında `2` oldu. Sıra bu işlem için anlamsızdır, çünkü iki işlem aynıdır; yineleme ise sonucu değiştirir. Sınıf **yinelemeye duyarlı**dır. Sayacın artması bir okumayı ve ardından bir yazmayı gerektirir; yazılan değer okunan değere bağlıdır. Aynı anahtara sabit bir değer yazan işlem ise yinelendiğinde değişmez. Fark budur: `set` sabit değerle sağlamdır, okuyup artıran işlem değildir.

Sayım bir sağlam, bir yinelemeye duyarlı, bir sıraya duyarlıdır.

#### Varsayılan Değerler

Bir anahtar hiç yazılmadıysa okuma `nil` ya da `0` verir. Her yerde bu durumu ayrı ayrı karşılamak yerine uygulama başlarken varsayılan değerleri kaydeder: `register(defaults:)`. Kaydedilen varsayılanlar yazılmış değer sayılmaz; yazılı değer yoksa devreye giren geri dönüş değeridir. Kayıt her açılışta yeniden yapılır, çünkü varsayılan değerler kalıcı olarak saklanmaz.

Varsayılan değer, anahtar silinince de okunan değerdir. `removeObject(forKey:)` yazılmış değeri kaldırır ve okuma varsayılana düşer. Bu iki işlemin birlikte nasıl davrandığı, varsayılan değere göre değişir ve bu dersin sınırlayıcı ölçümünün konusudur.

#### Sık Yapılan Hatalar

- Aynı anahtarı iki ekranda farklı amaçlarla kullanmak. Son yazan kazanır ve birbirinin değerini sessizce ezer.
- Sayacı okuyup artırmak ve yinelemeyi hesaba katmamak. Düğmeye iki kez basılması ya da açılış kodunun iki kez koşması sayacı iki artırır.
- Gizli bir değeri tercih deposuna yazmak. Depo bir gizlilik sınırı sağlamaz; gizli değerler anahtarlığa aittir.
- Varsayılan değeri her okuma noktasında ayrı yazmak. Değer iki yerde ayrışır ve hangisinin geçerli olduğu belirsizleşir.
- Büyük bir veriyi tercih deposuna koymak. Depo küçük değerler için tasarlanmıştır.
- Anahtar adını dizgi olarak çoğaltmak. Yazım hatası yeni bir anahtar açar ve eski değer okunmaya devam eder.

#### Ölçülen Sonucun Yorumu

İki ekran aynı anahtara yazınca sonucu yazma sırası belirledi; aynı değeri yinelemek zarar vermedi. Farklı anahtarlara yazınca iki ekran birbirini etkilemedi. Açılış sayacı ise yinelendiğinde değişti. Üç satır, tercih deposunun tek bir davranışı olmadığını gösterir: aynı `set` çağrısı, anahtarın kim tarafından paylaşıldığına ve değerin sabit mi hesaplanmış mı olduğuna göre başka bir sınıfa düşer.

Pratikte iki alışkanlık yeterlidir. Birincisi her anahtarın tek bir sahibi olmasıdır: değeri yazan ekran bilinir ve başka bir ekran aynı anahtara yazmaz. İkincisi sayaç ve benzeri hesaplanmış değerlerin yinelemeye karşı korunmasıdır: işlem bir kez koşmuş sayılıyorsa bunun ayrıca kaydı tutulur. Bu iki alışkanlık yukarıdaki ilk ve üçüncü satırın kaynağını yazım aşamasında ortadan kaldırır. Ölçüm bunun gerekli olduğunu gösterir, ama iki ekranın gerçekten ne zaman yazacağını göstermez.

**KL3.** Bu ölçüm artırmayı bir okuma ve ardından bir yazma olarak, kesintisiz kurar. İki artırmanın aynı anda koştuğu durum kurulmadı.

#### Sınırlayıcı Ölçüm

Kural tek bir başlangıç durumundan ölçer. Aynı işlem çifti farklı başlangıçta farklı sınıfa düşebilir. Burada işlem çifti şudur: `a` anahtarı siler, `b` aynı anahtara `koyu` yazar. Başlangıç, varsayılan değerin ne olduğuna göre üç biçimde kurulur.

```swift
// main.swift — ayni islem cifti uc farkli baslangicta
func removeValue(forKey key: String) -> (PreferenceBox) -> PreferenceBox {
    { box in
        var next = box
        next.removeObject(forKey: key)
        return next
    }
}

func writeDark(_ box: PreferenceBox) -> PreferenceBox {
    writeValue("koyu", forKey: "theme")(box)
}

func writeValue(_ value: String, forKey key: String) -> (PreferenceBox) -> PreferenceBox {
    { box in
        var next = box
        next.set(value, forKey: key)
        return next
    }
}

let noDefault = replay("varsayilan-yok", from: PreferenceBox(), removeValue(forKey: "theme"), then: writeDark).report()
let sameDefault = replay("varsayilan-koyu", from: registeredBox(["theme": "koyu"]), removeValue(forKey: "theme"), then: writeDark).report()
let otherDefault = replay("varsayilan-sistem", from: registeredBox(["theme": "sistem"]), removeValue(forKey: "theme"), then: writeDark).report()

countSensitivities([noDefault, sameDefault, otherDefault])
```

```
varsayilan-yok | bir=- iki=- ab=theme=koyu ba=- -> siraya-duyarli
varsayilan-koyu | bir=theme=koyu iki=theme=koyu ab=theme=koyu ba=theme=koyu -> saglam
varsayilan-sistem | bir=theme=sistem iki=theme=sistem ab=theme=koyu ba=theme=sistem -> siraya-duyarli
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 2
cift-duyarli: 0
```

Aynı iki işlem üç başlangıçta iki sınıf verdi. Varsayılan değer yokken ve varsayılan `sistem` iken sıra belirleyicidir: sil sonra yaz `koyu`, yaz sonra sil ise varsayılana ya da boşa döner. Varsayılan `koyu` iken iki sıra da `koyu` verir, çünkü silmenin düşeceği değer zaten yazılacak değerdir. Sonuç sağlam çıkar. Bu, işlemin her durumda sağlam olduğunu göstermez; yalnız o başlangıçta sıranın görünmediğini gösterir. Varsayılan değeri değiştirmek, hiçbir işlem koduna dokunmadan sınıfı değiştirdi.

Bu ölçümün de sınırı vardır. Kural, değerin deponun içinde yazılmış mı yoksa varsayılandan mı geldiğine bakmaz; yalnız çözülmüş değeri karşılaştırır. Yazılmış `koyu` ile varsayılan `koyu` aynı sayılır.

#### Gerçek Uygulamada Nerede Görülür

Ölçülen sıra duyarlılığı yapay bir laboratuvar durumu değildir. Bir uygulamada tema seçimini hem ayarlar ekranı hem de ana ekrandaki hızlı anahtar değiştirebilir. İki ekran aynı anahtara yazdığında kalan değer, hangisinin sonra yazdığına bağlıdır. Kullanıcı ayarlar ekranını açıkken ana ekrandan temayı değiştirirse, ayarlar ekranı kapanırken kendi eski kopyasını geri yazabilir. Sonuç, kullanıcının az önce yaptığı değişikliğin sessizce kaybolmasıdır. Hata raporunda bunun izi yoktur: hiçbir çağrı başarısız olmamıştır, her yazma kendi başına doğru çalışmıştır.

Açılış sayacı ise yineleme duyarlılığının tipik örneğidir. Sayacı bir artıran kod, kullanıcı uygulamayı her açtığında bir kez çalışsın diye yazılmıştır. Aynı kod ekranın yeniden görünmesinde, arka plandan dönüşte ya da bir yeniden denemede de çalışırsa sayaç gerçek açılış sayısından büyük çıkar. Sayaca bağlı bir karşılama ya da bir değerlendirme isteği gibi kararlar da bu yüzden yanlış zamanda tetiklenir. Ölçümün `iki` sütunu tam olarak bu durumu gösterir: işlem yinelendiğinde değer bir daha artmıştır.

Varsayılan değer meselesi daha da sinsidir. Hiç yazılmamış bir anahtarın okunması, uygulamanın yeni kurulduğu ve eski sürümden güncellendiği iki farklı durumda aynı görünür. Sınırlayıcı ölçüm bunu gösterdi: aynı işlem çifti, başlangıç durumuna göre farklı sınıfa düşebilir. Bu yüzden bir tercihin güvenli olup olmadığı yalnız işleme bakılarak söylenemez; işlemin hangi başlangıçtan koştuğu da sorulur.

#### Yanlış Kurulum ve Düzeltmesi

En yaygın yanlış kurulum, her ekranın tercihi kendi içinde bir kopya olarak tutmasıdır. Ekran açılırken değeri okur, kullanıcı değiştirdikçe kopyayı günceller ve ekran kapanırken bütün kopyayı depoya yazar. Bu düzende iki ekranın ömürleri çakıştığında sondaki yazma, öteki ekranın değişikliğini ezer.

Düzeltme iki adımdır. Birincisi, yalnız kullanıcının değiştirdiği anahtarı, değiştirdiği anda yazmaktır; ekran kapanırken tüm kopyayı yazmak yerine. Böylece ayrı anahtarlara yazan ekranlar birbirini etkilemez, ki ölçümdeki ayrı anahtar satırı bunu sağlam olarak gösterdi. İkincisi, aynı anahtarı birden çok yerden değiştirmek gerekiyorsa değişikliği tek bir yerden geçirmek ve ekranların depoyu değil o tek yeri dinlemesini sağlamaktır. Bu, sıra sorununu çözmez ama sırayı tek bir yerde görünür kılar; sıraya duyarlı bir işlemin güvenli hâle gelmesinin yolu çoğu zaman sıranın artık değişememesidir.

Sayaç için düzeltme farklıdır. Sayacı artırmak yerine "bu açılış sayıldı mı" bilgisini saklamak, işlemi yinelemeye karşı korur. Aynı oturumda ikinci kez gelen çağrı, işaretin dolu olduğunu görüp bir şey yapmaz. Ölçüm olarak bu, işlemin yinelemeye duyarlı olmaktan çıkıp sağlam olması demektir; ama bunu bu derste ayrıca ölçmedik, yalnız yönünü söylüyoruz.

#### KAYNAK Parçayı Okumak

Dersin başındaki adsız parça derlenmez, çünkü gerçek bir tercih deposu platforma ve uygulamanın yaşam döngüsüne bağlıdır. Parça yine de okunmaya değerdir. Okuma ve yazma çağrıları, ölçtüğümüz modeldeki iki işlemin birebir karşılığıdır: modelde bir anahtarı okuyup yazdığımız yerde, gerçek parça aynı anahtarı okuyup yazar. Aradaki fark, gerçek depoda yazmanın hemen diske gitmemesi ve okumaların başka bir süreçten gelen değişikliği görmesinin garanti edilmemesidir. Bu fark KL1 ve KL2 olarak kayıtlıdır ve ölçülen sınıfı değiştirmez; yalnız gerçek davranışın modele ne kadar yakın olduğunu söyler.

Parçadaki varsayılan değer verme biçimi de önemlidir. Okuma çağrısı anahtar yoksa bir değer döndürür ve bu değer, kodun başka bir yerinde ayrıca tanımlı bir varsayılanla çelişebilir. İki yerde tanımlı varsayılan, ölçümdeki iki farklı başlangıç durumunun gerçek hayattaki karşılığıdır. Varsayılanı tek bir yerde tanımlamak, sınırlayıcı ölçümün gösterdiği sınıf kaymasını baştan engeller.

#### Özet

- Tercih deposu küçük ve gizli olmayan değerleri anahtar-değer olarak saklar; gizli değerler anahtarlığa aittir.
- İki ekran aynı anahtara farklı değer yazınca sonucu yazma sırası belirler (`ab` `acik`, `ba` `koyu`); farklı anahtarlar birbirini etkilemez.
- Okuyup artıran bir işlem, açılış sayacı gibi, yinelendiğinde sonucu değiştirir (`1`, sonra `2`); sabit değer yazmak değiştirmez.
- Varsayılan değer, aynı sil ve yaz çiftini sağlamdan sıraya duyarlıya taşıyabilir; sonuç tek başlangıçtan ölçülür.
- Üç işlemin sayımı bir sağlam, bir yinelemeye duyarlı, bir sıraya duyarlıdır; sınırlayıcı ölçüm bir sağlam, iki sıraya duyarlı verir.
- Bu dersin duyarlılığı: **sıraya duyarlı**.

#### Sonraki Adım

Tercih deposu değerlerin açık durduğu bir yerdir. Sıradaki derste, Anahtarlık, gizli değerlerin saklandığı depoya geçilecek ve aynı öğeyi ikinci kez eklemenin durum kodunu nasıl değiştirdiği ölçülecek.

### Anahtarlık

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/anahtarlik

Gizli değerlerin saklandığı yerde aynı öğeyi ikinci kez eklemenin durum kodunu değiştirmesinin, güncelle yoksa ekle işleminin ise sıraya duyarlı olmasının ölçümü.

Önceki derste tercih deposunun değerleri açık durduğunu gördük. Parola, oturum belirteci ya da bir uygulama anahtarı gibi gizli değerler için platformun ayrı bir deposu vardır: **anahtarlık**. Anahtarlık değerleri şifreli saklar, uygulama silinip yeniden kurulsa bile bazı öğeleri korur ve erişimi öğe başına denetler. Karşılığında bir bedel öder: işlemler bir sonuç kodu döndürür ve bu kod, bir öğenin zaten var olup olmadığını söyler. Kursun sorusu aynı kalır: **bu işlem yinelenirse ya da sırası değişirse sonuç değişir mi?** Bu derste kod da sonucun parçası olur.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

`bir` işlemin başlangıç durumuna bir kez, `iki` iki kez uygulanmasıdır; `ab` önce `a` sonra `b`, `ba` ise tersi sıradır. Aşağıdaki model anahtarlığın saf Swift karşılığıdır: bir öğe, bir hesap adıyla bir değerden ibarettir. Model platformun kendisi değildir; yalnız işlemin durumda bıraktığı sonucu kurar.

#### Ekleme ve Durum Kodu

Anahtarlığa öğe eklemenin platform çağrısı şöyle okunur. Parça KAYNAK'tır; gerçek bir anahtarlık gerektirdiği için burada koşmaz.

```swift
import Security

let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: "token",
    kSecValueData as String: Data("a1".utf8)
]

let first = SecItemAdd(query as CFDictionary, nil)   // errSecSuccess
let second = SecItemAdd(query as CFDictionary, nil)  // errSecDuplicateItem
```

İkinci çağrı aynı sınıf ve aynı hesap adıyla geldiği için yeni bir öğe oluşturmaz; `errSecDuplicateItem` döner. Mevcut değer olduğu gibi kalır. Yani ikinci eklemenin etkisi yoktur ama sonucu vardır, ve bu sonuç bir hata kodudur. Varolan bir öğeyi değiştirmek için ayrı bir çağrı gerekir: `SecItemUpdate`. Öğe yoksa o çağrı da `errSecItemNotFound` verir. Uygulamalar bu iki çağrıyı **güncelle yoksa ekle** biçiminde birleştirir: önce güncellemeyi dener, öğe yoksa ekler.

#### Anahtarlığın Modeli

Model, hesap adından değere bir sözlük ve son işlemin durum kodunu tutar. Durum kodu karşılaştırmaya girer; `includesStatus` bunu kapatabilir ve sınırlayıcı ölçüm bundan yararlanır.

```swift
// KeychainModel.swift — anahtarligin saf Swift modeli
struct KeychainBox: Equatable, CustomStringConvertible {
    static let success = 0
    static let duplicateItem = -25299
    static let itemNotFound = -25300

    private(set) var items: [String: String]
    private(set) var status: Int
    let includesStatus: Bool

    init(includesStatus: Bool = true, items: [String: String] = [:]) {
        self.includesStatus = includesStatus
        self.items = items
        self.status = KeychainBox.success
    }

    var description: String {
        let pairs = items.keys.sorted().map { "\($0)=\(items[$0] ?? "-")" }
        let text = pairs.isEmpty ? "-" : pairs.joined(separator: ",")
        return includesStatus ? "\(text) durum=\(status)" : text
    }

    static func == (lhs: KeychainBox, rhs: KeychainBox) -> Bool {
        lhs.description == rhs.description
    }

    mutating func add(_ value: String, account: String) {
        if items[account] != nil {
            status = KeychainBox.duplicateItem
        } else {
            items[account] = value
            status = KeychainBox.success
        }
    }

    mutating func update(_ value: String, account: String) {
        if items[account] != nil {
            items[account] = value
            status = KeychainBox.success
        } else {
            status = KeychainBox.itemNotFound
        }
    }

    mutating func addOrUpdate(_ value: String, account: String) {
        update(value, account: account)
        if status == KeychainBox.itemNotFound {
            add(value, account: account)
        }
    }
}

func adding(_ value: String, account: String = "token") -> (KeychainBox) -> KeychainBox {
    { box in
        var next = box
        next.add(value, account: account)
        return next
    }
}

func upserting(_ value: String, account: String = "token") -> (KeychainBox) -> KeychainBox {
    { box in
        var next = box
        next.addOrUpdate(value, account: account)
        return next
    }
}
```

Durum kodu sayıları platformun sabitleriyle aynı tutulmuştur: başarı `0`, tekrar eden öğe `-25299`, öğe bulunamadı `-25300`. Karşılaştırma ve basım aynı `description` üzerinden yürür; böylece bir sınıflandırmada görünen şey ile eşitlikte sayılan şey ayrışmaz.

**KL4.** Model, öğenin kimliğini yalnız hesap adı sayar. Platformda kimlik sınıf, servis ve hesap gibi niteliklerin birleşimidir; tek hesap adıyla modellemek ölçümü sadeleştirir.

**KL5.** Model, durum kodlarını yukarıdaki sabitlerle üretir. Platformun gerçek geri dönüşü, erişim denetimi ve aygıt kilidi ölçülmedi.

#### Üç İşlemin Ölçümü

Üç işlem kurulur. Birincisi aynı değeri iki kez eklemektir. İkincisi güncelle yoksa ekle işleminin iki farklı değerle koşmasıdır. Üçüncüsü iki farklı değeri doğrudan eklemektir.

```swift
// main.swift — ekleme ve guncelle yoksa ekle
let sameValue = replay("ekle-ayni-deger", from: KeychainBox(), adding("a1"), then: adding("a1")).report()
let upsert = replay("guncelle-yoksa-ekle", from: KeychainBox(), upserting("a1"), then: upserting("b2")).report()
let twoValues = replay("ekle-iki-deger", from: KeychainBox(), adding("a1"), then: adding("b2")).report()

countSensitivities([sameValue, upsert, twoValues])
```

```
ekle-ayni-deger | bir=token=a1 durum=0 iki=token=a1 durum=-25299 ab=token=a1 durum=-25299 ba=token=a1 durum=-25299 -> yinelemeye-duyarli
guncelle-yoksa-ekle | bir=token=a1 durum=0 iki=token=a1 durum=0 ab=token=b2 durum=0 ba=token=a1 durum=0 -> siraya-duyarli
ekle-iki-deger | bir=token=a1 durum=0 iki=token=a1 durum=-25299 ab=token=a1 durum=-25299 ba=token=b2 durum=-25299 -> cift-duyarli
saglam: 0
yinelemeye-duyarli: 1
siraya-duyarli: 1
cift-duyarli: 1
```

İlk satırda öğe iki durumda da `a1`'dir; ayrışan yalnız durum kodudur: `0` ve `-25299`. Ekleme etkisiz kalmış, ama sonuç değişmiş. Sıra anlamsızdır, çünkü iki işlem aynıdır. Sınıf **yinelemeye duyarlı**dır. Bu, bir önceki dersin sayacından farklı bir yinelemedir: değer değil, işlemin bildirdiği sonuç değişir.

İkinci satırda güncelle yoksa ekle işlemi yinelendiğinde sonuç değişmez: öğe ve kod aynı kalır. Etkisizlik yinelemeyi çözmüştür. Ama iki farklı değerde sırayı çözmez: `ab` `b2`, `ba` `a1`. Son yazan kazanır ve kazananı sıra belirler. Sınıf **sıraya duyarlı**dır. Kural eşitsizliği görür; nedeni, yani ikinci çağrının güncelleme yolundan geçtiğini görmez.

Üçüncü satır iki etkiyi birleştirir. İkinci ekleme başarısız olur, bu yüzden hem yineleme hem sıra ayrışır. Öğe ilk gelenin değeriyle kalır ve ilk gelen sıraya bağlıdır. Sınıf **çift duyarlı**dır.

Sayım bir yinelemeye duyarlı, bir sıraya duyarlı, bir çift duyarlıdır; sağlam yoktur.

#### Neden Güncelle Yoksa Ekle

Doğrudan ekleme, öğenin önceden olmadığını varsayar. Uygulama her açılışta belirteci eklerse ikinci açılıştan itibaren çağrı hata döner ve eski değer kalır; yeni değer sessizce yazılmaz. Bu, belirteç yenilendiğinde eski belirteçle çalışmaya devam etmenin yaygın bir nedenidir. Güncelle yoksa ekle bu hatayı ortadan kaldırır: yineleme artık hata üretmez. Bedeli yukarıdaki ikinci satırdır. İki ayrı kaynak aynı hesaba farklı değer yazarsa kazananı sıra belirler.

Pratikte iki karar yeterlidir. Eklemenin sonucu denetlenir: `errSecDuplicateItem` bir arıza değil, öğenin zaten var olduğunun bilgisidir. Birden çok kaynağın yazabileceği bir hesap varsa, hangi kaynağın son sözü söyleyeceği tasarımda yazılır.

#### Sık Yapılan Hatalar

- Gizli bir değeri tercih deposuna yazmak. Anahtarlık bunun için vardır.
- Ekleme sonucunu denetlememek. Hata dönse bile uygulama başarılı sanır ve eski değerle devam eder.
- Her açılışta doğrudan eklemek. İlk açılışta çalışır, sonrakilerde sessizce başarısız olur.
- Güncelle yoksa ekle yolunu iki ayrı yerden çağırmak. Son yazanı sıra belirler ve hata ayıklama güçleşir.
- Silme ve eklemeyi tek adımmış gibi düşünmek. İki çağrı arasında uygulama kesilirse öğe kaybolur.
- Hesap adını dizgi olarak çoğaltmak. Yazım hatası yeni bir öğe açar.

**KL6.** Model, çağrıları kesintisiz ve tek iş parçacığında koşturur. İki çağrının aynı anda koştuğu durum kurulmadı.

#### Sınırlayıcı Ölçüm

Birinci sınır, durum kodunun karşılaştırılan duruma girip girmediğidir. Kural eşitsizliği görür; durum kodu duruma dahil değilse ikinci ekleme görünmez. Aynı üç eklemeyi `includesStatus: false` ile koşalım. Üçüncü satırda ise aynı işlem çiftini, öğenin baştan var olduğu bir başlangıçta ölçelim.

```swift
// main.swift — durum kodu olmadan ve dolu baslangicta
let plain = KeychainBox(includesStatus: false)
let withoutStatus = replay("durumsuz-ayni-deger", from: plain, adding("a1"), then: adding("a1")).report()
let withoutStatusTwo = replay("durumsuz-iki-deger", from: plain, adding("a1"), then: adding("b2")).report()
let seeded = replay("durumlu-dolu-baslangic", from: KeychainBox(items: ["token": "a1"]), adding("a1"), then: adding("a1")).report()

countSensitivities([withoutStatus, withoutStatusTwo, seeded])
```

```
durumsuz-ayni-deger | bir=token=a1 iki=token=a1 ab=token=a1 ba=token=a1 -> saglam
durumsuz-iki-deger | bir=token=a1 iki=token=a1 ab=token=a1 ba=token=b2 -> siraya-duyarli
durumlu-dolu-baslangic | bir=token=a1 durum=-25299 iki=token=a1 durum=-25299 ab=token=a1 durum=-25299 ba=token=a1 durum=-25299 -> saglam
saglam: 2
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

Durum kodu çıkarılınca aynı ekleme çifti **sağlam**a döndü; iki farklı değerde ise çift duyarlı sınıf sıraya duyarlıya indi. İşlem değişmedi, yalnız neye baktığımız değişti. Kodu duruma almak yinelemeyi görünür kılar; almamak onu gizler.

İkinci sınır başlangıç durumudur. Aynı iki ekleme boş başlangıçta yinelemeye duyarlıydı. Öğenin zaten bulunduğu başlangıçta ilk ekleme de hata verir ve durum `-25299`'da sabitlenir: sınıf sağlam. Sağlam çıkan işlem her durumda sağlam değildir; yalnız bu başlangıçta yinelemeyi göstermez.

#### Gerçek Uygulamada Nerede Görülür

Ekleme işleminin ikinci çağrıda başarısız olması, anahtarlığın en sık karşılaşılan davranışlarından biridir. Bir uygulama oturum anahtarını ilk girişte kaydeder; kullanıcı çıkış yapıp yeniden girdiğinde aynı kod çalışır ve ekleme, öğe zaten var diye reddedilir. Geliştirici bunu çoğunlukla yalnız bir hata kodu olarak görür ve kullanıcının yeni anahtarı hiç saklanmamış kalır: eski anahtar yerinde durur, uygulama ise yenisini kaydettiğini sanır. Ölçümdeki `-25299` durumu bu çakışmanın karşılığıdır ve `ekle-ayni-deger` satırı, aynı değerin ikinci kez eklenmesinde bile durumun değiştiğini gösterir.

Bu ayrım önemlidir, çünkü sonuç değeri aynı kalsa da durum kodu değişmiştir. Kodu okuyan çağıran, ikinci eklemeyi başarısızlık sayabilir ve bir hata ekranı gösterebilir. Durum kodunu hesaba katmadığımız sınırlayıcı satırlarda ise aynı işlem çifti sağlam görünür. Aradaki fark, ölçüde neyin eşitliğe dâhil edildiğidir; kural eşitsizliği görür, ama neyin eşitlik sayıldığını biz seçeriz.

Çıkış ve yeniden giriş dışında, anahtarlık çağrıları uygulamanın yeniden açılışında, bir yedekten geri yüklemede ve iki ayrı uzantının aynı öğeyi paylaştığı durumlarda da yinelenir. Paylaşılan bir öğeyi iki yerin yazması, ölçümdeki iki değerli ekleme satırının gerçek karşılığıdır: hangi değerin kaldığı, hangisinin önce çalıştığına bağlıdır.

#### Yanlış Kurulum ve Düzeltmesi

Yaygın yanlış kurulum, önce silip sonra eklemektir. Mantık şöyledir: öğe varsa sil, sonra ekle; böylece çakışma olmaz. Bu düzen iki çağrı arasında öğenin hiç bulunmadığı bir aralık bırakır. Uygulama bu aralıkta kesilirse ya da başka bir yer öğeyi tam o anda okursa, kullanıcı anahtarsız kalır. Ayrıca silme ve ekleme iki ayrı işlemdir; her birinin ayrı bir durum kodu vardır ve ikisini de denetlemek gerekir.

Düzeltme, ölçtüğümüz "güncelle, yoksa ekle" kalıbıdır. Önce güncellemeyi dener, öğe yoksa ekleme yapar. Güncelleme yinelemeye karşı sağlamdır; aynı değerle ikinci güncelleme bir şeyi değiştirmez. Kalıp sırayı ise çözmez: iki farklı değer iki sırayla gelirse son yazılan kalır. Bu yüzden `guncelle-yoksa-ekle` satırı yinelemeye karşı korumalı ama sıraya duyarlı çıktı. Kalıbın sağladığı şey, ilk çakışmanın hataya dönmemesidir; hangi değerin kalacağı sorusunu çözmez.

İkinci yaygın yanlış, durum kodunu yok saymaktır. Ekleme çağrısının sonucunu kontrol etmeyen kod, başarısız bir yazmayı başarılı sanır. Düzeltme basittir ve sıkıcıdır: her çağrının durumunu okumak ve yalnız beklenen kodlarda ilerlemek. Beklenmeyen bir kodda uygulama, kullanıcıya anlaşılır bir sonuç sunmalıdır.

#### KAYNAK Parçayı Okumak

Dersin başındaki parça, bir öğenin sorgu sözlüğüyle tanımlandığını gösterir. Sözlükteki sınıf, servis ve hesap alanları öğenin kimliğini oluşturur; değer ise ayrı bir alandır. Çağrı bir durum kodu döndürür ve kod başarıyı ya da hangi çakışmanın olduğunu bildirir. Modelimiz kimliği yalnız hesap adına indirgedi; bu KL4 olarak kayıtlıdır. Gerçekte aynı hesap adı iki farklı serviste iki ayrı öğe olabilir, ve modelde birbirine çakışan iki yazma gerçekte hiç çakışmayabilir.

Parçadaki erişim ayarı da modelin dışında kaldı. Öğenin ne zaman okunabileceği, aygıt kilitliyken erişilip erişilemeyeceği gibi kararlar durum kodlarını doğrudan etkiler. Bu yüzden KL5 durum kodlarının gerçek geri dönüşünü ve erişim denetimini ölçülmemiş olarak kaydeder. Bu derste ölçülen şey kuralın kendisidir: hangi işlem çifti, hangi başlangıçta hangi sınıfa düşer.

#### Ölçümden Tasarıma

Ölçümün üç satırı, anahtarlığa yazan her kod için bir kontrol listesine çevrilebilir. İlk soru, işlemin yinelenmesinin ne yaptığıdır. Ekleme çağrısı ikinci kez çalıştığında durum kodu değişir; bu, çağıranın başarısızlığı bir olay sanıp sanmayacağını belirler. İkinci soru, iki farklı değerin gelmesidir: hangisinin kalacağı kural olarak yazılmadıysa sonuç rastlantıya bırakılmıştır. Üçüncü soru, işlemin hangi başlangıçtan koştuğudur. Durumlu satırların dolu başlangıçta sağlam çıkması, öğenin zaten var olduğu bir uygulamada ekleme çağrısının tutarlı biçimde aynı hatayı verdiğini söyler; bu tutarlılık bir güvence değil, yalnız bir gözlemdir.

Pratikte bunun anlamı şudur: giriş akışında anahtarı yazan kod, yazmadan önce öğenin var olup olmadığını varsaymamalıdır. Güncelle-yoksa-ekle kalıbı bu varsayımı kaldırır. Çıkış akışında ise öğeyi silen kod, silinecek öğe yoksa da hata vermemelidir; silmenin yinelemeye karşı sağlam olması, çıkış düğmesine iki kez basılan bir kullanıcıyı korur. Bu derste silmeyi ölçmedik, ama aynı soruyu ona da sormak gerekir ve sorunun cevabı, ölçmeden varsaymak yerine ölçmekle bulunur.

Son olarak, anahtarlıktaki değerin sır olduğunu hatırlamak gerekir. Bu dersin çıktıları örnek değerler içerir; gerçek bir anahtarın günlüğe yazılması ya da ekranda gösterilmesi ayrı bir hatadır. Ölçüm kodu sır taşımaz, ama sizin ölçüm kodunuz da taşımamalıdır.

#### Özet

- Gizli değerler anahtarlığa aittir; işlemler bir durum kodu döndürür ve bu kod sonucun parçasıdır.
- Aynı öğeyi ikinci kez eklemek öğeyi değiştirmez ama durumu `0`'dan `-25299`'a çevirir; sınıf yinelemeye duyarlıdır.
- Güncelle yoksa ekle yinelemeyi çözer ama iki farklı değerde sırayı çözmez: `ab` `b2`, `ba` `a1`.
- İki farklı değeri doğrudan eklemek hem yinelemeyi hem sırayı ayrıştırır; sınıf çift duyarlıdır.
- Durum kodu karşılaştırmadan çıkarılınca aynı ekleme çifti sağlam görünür; başlangıçta öğe varsa da sağlam görünür.
- Bu dersin duyarlılığı: **yinelemeye duyarlı**.

#### Sonraki Adım

Anahtarlık küçük gizli değerleri saklar. Sıradaki derste, Dosya Sistemi, daha büyük verilerin dosyalara yazıldığı yere geçilecek ve bir dosyaya sona eklemenin hem yinelemeye hem sıraya duyarlı olduğu ölçülecek.

### Dosya Sistemi

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/dosya-sistemi

Büyük verilerin dosyalara yazıldığı yerde sona eklemenin, tam değiştirmenin ve dizin oluşturmanın yineleme ve sıra duyarlılığının ölçümü.

Tercih deposu küçük değerler, anahtarlık gizli değerler içindi. Bir belge, bir dışa aktarma dosyası, bir günlük ya da indirilmiş bir görsel gibi daha büyük veriler **dosya sistemi**ne yazılır. Dosya sisteminin sunduğu şey bir anahtar-değer eşlemesi değil, yol ve bayt dizisidir. Bu yüzden işlemleri iki gruba ayrılır: dosyanın içeriğini değiştirenler ve dizin yapısını değiştirenler. Kursun sorusu bu ikisinde de aynıdır: **bu işlem yinelenirse ya da sırası değişirse sonuç değişir mi?**

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

`bir` işlemin başlangıç durumuna bir kez, `iki` iki kez uygulanmasıdır; `ab` önce `a` sonra `b`, `ba` ise tersi sıradır. Bu derste ölçülen işlemler bir dizin ve dosya ağacının saf Swift modeline uygulanır. Model diske hiçbir şey yazmaz ve platformun kendisi değildir.

#### Hangi Veri Hangi Dizine

Uygulamanın dosya yazabildiği yerler amaçlarına göre ayrılır. Belgeler dizini kullanıcının ürettiği ve kaybolmaması gereken veri içindir; yedeklemeye girer. Destek dizini uygulamanın kullanıcıya göstermediği ama yeniden üretemeyeceği veriler içindir. Önbellek dizini yeniden üretilebilen veri içindir ve sistem yer gerektiğinde onu temizleyebilir. Geçici dizin bundan da kısa ömürlüdür.

```swift
import Foundation

let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let caches = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]

var exports = documents.appendingPathComponent("exports", isDirectory: true)
try FileManager.default.createDirectory(at: exports, withIntermediateDirectories: true)

var values = URLResourceValues()
values.isExcludedFromBackup = true
try exports.setResourceValues(values)

let target = exports.appendingPathComponent("settings")
try Data("v1".utf8).write(to: target, options: .atomic)
```

Bu parça KAYNAK'tır; gerçek bir uygulama dizini gerektirdiği için burada koşmaz. Dört ayrıntı okunur. `createDirectory` çağrısı `withIntermediateDirectories: true` ile dizin zaten varsa hata vermez. `isExcludedFromBackupKey` anahtarına karşılık gelen özellik, bir dosyayı ya da dizini yedeklemenin dışında bırakır; yeniden indirilebilen veri için kullanılır. `.atomic` seçeneği veriyi önce geçici bir dosyaya yazar, sonra hedefin yerine koyar. Önbellek dizininin ne zaman temizleneceğini sistem seçer; bu ders onu ölçmez.

#### Dosya Ağacının Modeli

Model, dizinlerin bir kümesini ve her dosya için satırların listesini tutar. Çıktıda dizinler ve dosyalar sıralı basılır; boş küme `-` olarak görünür.

```swift
// FolderModel.swift — dizin ve dosya agacinin saf Swift modeli
struct FolderBox: Equatable, CustomStringConvertible {
    private(set) var directories: Set<String>
    private(set) var files: [String: [String]]

    init(directories: Set<String> = [], files: [String: [String]] = [:]) {
        self.directories = directories
        self.files = files
    }

    var description: String {
        let dirText = directories.isEmpty ? "-" : directories.sorted().joined(separator: ",")
        let fileText = files.isEmpty ? "-" : files.keys.sorted().map { "\($0)=\(files[$0]?.joined(separator: "+") ?? "")" }.joined(separator: ",")
        return "dizin:\(dirText) dosya:\(fileText)"
    }

    mutating func ensureDirectory(_ path: String) { directories.insert(path) }
    mutating func append(_ line: String, to path: String) { files[path, default: []].append(line) }
    mutating func appendIfPresent(_ line: String, to path: String) {
        if files[path] != nil { files[path]?.append(line) }
    }
    mutating func writeAtomically(_ text: String, to path: String) { files[path] = [text] }
}

func ensuring(_ path: String) -> (FolderBox) -> FolderBox {
    { box in
        var next = box
        next.ensureDirectory(path)
        return next
    }
}

func appending(_ line: String, to path: String) -> (FolderBox) -> FolderBox {
    { box in
        var next = box
        next.append(line, to: path)
        return next
    }
}

func appendingIfPresent(_ line: String, to path: String) -> (FolderBox) -> FolderBox {
    { box in
        var next = box
        next.appendIfPresent(line, to: path)
        return next
    }
}

func writing(_ text: String, to path: String) -> (FolderBox) -> FolderBox {
    { box in
        var next = box
        next.writeAtomically(text, to: path)
        return next
    }
}
```

Model iki yazma biçimini ayırır. `append` dosyanın sonuna bir satır ekler; mevcut içerik korunur. `writeAtomically` dosyanın içeriğini tamamen yenisiyle değiştirir; yarım yazılmış bir ara durum görünmez. Gerçek platformda atomik yazma bir geçici dosya ve bir yer değiştirme ile yapılır. Modelde yalnız bu işlemin sonucu kurulur.

**KL7.** Model, bir yazmanın ya tamamen görüldüğünü ya da hiç görülmediğini varsayar. Yazma sırasında uygulamanın kesilmesi ve yarım dosya kalması kurulmadı.

**KL8.** Model, dosya adlarını düz yol dizgisi olarak tutar. Dizinin dosyadan önce var olması gerekmez; gerçek yazmada üst dizin yoksa çağrı hata verir.

#### Dört İşlemin Ölçümü

Dört işlem kurulur. Birincisi aynı satırı iki kez eklemektir. İkincisi iki farklı satırı eklemektir. Üçüncüsü iki farklı içeriği atomik yazmaktır. Dördüncüsü iki farklı dizini oluşturmaktır.

```swift
// main.swift — sona ekleme, atomik yazma, dizin olusturma
let sameLine = replay("ekle-ayni-satir", from: FolderBox(), appending("a1", to: "log"), then: appending("a1", to: "log")).report()
let twoLines = replay("ekle-iki-satir", from: FolderBox(), appending("a1", to: "log"), then: appending("b2", to: "log")).report()
let atomic = replay("atomik-yaz", from: FolderBox(), writing("v1", to: "settings"), then: writing("v2", to: "settings")).report()
let folders = replay("dizin-olustur", from: FolderBox(), ensuring("cache"), then: ensuring("docs")).report()

countSensitivities([sameLine, twoLines, atomic, folders])
```

```
ekle-ayni-satir | bir=dizin:- dosya:log=a1 iki=dizin:- dosya:log=a1+a1 ab=dizin:- dosya:log=a1+a1 ba=dizin:- dosya:log=a1+a1 -> yinelemeye-duyarli
ekle-iki-satir | bir=dizin:- dosya:log=a1 iki=dizin:- dosya:log=a1+a1 ab=dizin:- dosya:log=a1+b2 ba=dizin:- dosya:log=b2+a1 -> cift-duyarli
atomik-yaz | bir=dizin:- dosya:settings=v1 iki=dizin:- dosya:settings=v1 ab=dizin:- dosya:settings=v2 ba=dizin:- dosya:settings=v1 -> siraya-duyarli
dizin-olustur | bir=dizin:cache dosya:- iki=dizin:cache dosya:- ab=dizin:cache,docs dosya:- ba=dizin:cache,docs dosya:- -> saglam
saglam: 1
yinelemeye-duyarli: 1
siraya-duyarli: 1
cift-duyarli: 1
```

Aynı satırı iki kez eklemek dosyayı büyütür: `a1` ve `a1+a1`. İki işlem aynı olduğu için sıra anlamsızdır. Sınıf **yinelemeye duyarlı**dır. Bir yeniden deneme mantığı bir ekleme çağrısını iki kez koşturursa günlüğe satır çiftlenir.

İki farklı satırı eklemek hem yinelemeyi hem sırayı ayrıştırır. Dosya ya `a1+b2` ya `b2+a1` olur ve her iki durumda da iki kez eklemek dosyayı büyütür. Sınıf **çift duyarlı**dır. Sona eklemenin bu kadar duyarlı olması şaşırtıcı değildir: dosyanın içeriği, o ana kadar eklenen her şeyin sıralı toplamıdır.

Atomik yazma yinelemeyi çözer: aynı içeriği ikinci kez yazmak dosyayı değiştirmez. Ama iki farklı içerikte sırayı çözmez: `ab` `v2`, `ba` `v1`. Son yazan kazanır. Atomiklik yarım dosyayı engeller; hangi yazmanın son olduğunu belirlemez. Sınıf **sıraya duyarlı**dır.

Dizin oluşturma dört satırın tek sağlam olanıdır. `withIntermediateDirectories: true` ile dizin zaten varsa işlem bir şey yapmaz; iki farklı dizin hangi sırayla oluşturulursa oluşturulsun aynı küme çıkar. Sınıf **sağlam**dır.

Sayım her sınıftan birer tanedir.

#### Seçim Rehberi

Ölçüm dosya yazma kararını küçük bir kurala indirir. Veri bir kez yazılıp bir daha dokunulmuyorsa atomik yazma seçilir; yarım dosya ve çiftlenme riski ortadan kalkar, ama iki yazıcı varsa kazananı sıra belirler. Veri zamanla büyüyen bir günlükse sona ekleme seçilir, ama çiftlenmeyi önleyen bir anahtar ya da sayaç gerekir. Dizin oluşturma her zaman sağlam sayılabilir; uygulama başlarken ihtiyaç duyduğu dizinleri sormadan oluşturabilir.

#### Sık Yapılan Hatalar

- Yeniden üretilebilen veriyi belgeler dizinine koymak. Yedeklemeyi şişirir.
- Kullanıcı verisini önbellek dizinine koymak. Sistem onu yer açmak için silebilir.
- Atomik yazmayı kullanmadan büyük bir dosyayı yerinde değiştirmek. Kesilme yarım dosya bırakır.
- Sona eklemeyi yeniden deneme içinde çağırmak. Satır çiftlenir.
- İki farklı yerden aynı dosyaya yazmak ve kazananın belli olduğunu sanmak.
- Dizin oluşturmadan önce var olup olmadığını denetlemek. Denetlemek gereksizdir; oluşturma zaten sağlamdır.

#### Sınırlayıcı Ölçüm

Bazı işlemler dosyanın önceden var olmasına bağlıdır. Burada işlem, yalnız dosya varsa satır ekleyen bir ekleme olsun: dosyayı açamayan çağrı sessizce bir şey yapmaz. Aynı işlem çifti iki başlangıçta ölçülür: dosyanın olmadığı ve olduğu.

```swift
// main.swift — ayni islem cifti iki farkli baslangicta
let missing = replay("ekle-varsa-bos", from: FolderBox(), appendingIfPresent("x", to: "log"), then: appendingIfPresent("x", to: "log")).report()
let present = replay("ekle-varsa-dolu", from: FolderBox(files: ["log": ["a1"]]), appendingIfPresent("x", to: "log"), then: appendingIfPresent("x", to: "log")).report()

countSensitivities([missing, present])
```

```
ekle-varsa-bos | bir=dizin:- dosya:- iki=dizin:- dosya:- ab=dizin:- dosya:- ba=dizin:- dosya:- -> saglam
ekle-varsa-dolu | bir=dizin:- dosya:log=a1+x iki=dizin:- dosya:log=a1+x+x ab=dizin:- dosya:log=a1+x+x ba=dizin:- dosya:log=a1+x+x -> yinelemeye-duyarli
saglam: 1
yinelemeye-duyarli: 1
siraya-duyarli: 0
cift-duyarli: 0
```

Aynı iki işlem boş başlangıçta sağlam, dolu başlangıçta yinelemeye duyarlı çıktı. Dosya yokken işlem hiçbir şey yapmaz ve bu yüzden hiçbir şeyi bozamaz; dosya varken her çağrı bir satır ekler. Sağlam ölçülen ilk satır işlemin sağlam olduğunu söylemez, yalnız o başlangıçta etkisiz kaldığını söyler. Kural eşitsizliği görür; başlangıç durumunu genellemez.

Ölçümün dışında kalanlar açıkça sayılır. Dizin türleri, yedekleme dışı bırakma özelliği ve önbellek dizininin temizlenme zamanı KAYNAK ve düzyazı olarak kaldı. Bunlar sistemin zamanlamasına ve politikasına bağlıdır; bu modelde bir işlemin sonucu olarak kurulamaz.

#### Gerçek Uygulamada Nerede Görülür

Dosyaya ekleme, bir günlük dosyasının tipik kullanımıdır ve ölçümün `ekle-ayni-satir` satırı bunun yinelemeye karşı korumasız olduğunu gösterdi. Uygulama bir olayı günlüğe yazar; olayı üreten kod bir yeniden denemede ya da bir yaşam döngüsü geçişinde ikinci kez çalışırsa aynı satır dosyada iki kez bulunur. Günlüğü sonradan okuyan bir çözümleme, bir olayı iki kez sayar. Dosya her zaman sona ekleme biçimindeyse çoğaltmanın izi silinemez, çünkü iki satır birbirinden ayırt edilemez.

İki farklı satırın iki sırayla eklenmesi ise çift duyarlılığı gösterir: `ekle-iki-satir` satırında hem yineleme hem sıra sonucu değiştirdi. Günlükte bu, olayların sırasının kayması anlamına gelir. Sıra önemsiz görünebilir, ama bir günlüğün çoğu zaman anlatı olarak okunduğunu düşünün: "bağlandı" satırı "koptu" satırından sonra gelirse okuyan yanlış sonuca varır.

Atomik yazma, bu dersin sağlam görünmesi beklenen ama yine de sıraya duyarlı çıkan işlemidir. Dosyanın tamamını geçici bir adla yazıp sonra asıl adın üzerine taşımak, yarım dosya kalmasını engeller; ancak iki yazma yarışırsa sonuncusu kazanır. Bu, yarım dosya sorununu çözmesine karşın sıra sorununu çözmediğinin ölçüdeki kanıtıdır. Dizin oluşturma ise iki sıra ve yineleme için sağlamdır, çünkü varlığı kontrol eden bir işlem, olan bir şeyi ikinci kez oluşturmaz.

#### Yanlış Kurulum ve Düzeltmesi

Yaygın yanlış kurulum, yapılandırmayı doğrudan asıl dosyaya yazmaktır. Yazma sırasında uygulama kesilirse dosya yarım kalır ve bir sonraki açılışta okunamaz. Kullanıcı, uygulamanın artık açılmadığını görür. Düzeltme geçici dosya kalıbıdır: önce yeni içerik ayrı bir adla yazılır, tamamlandığı doğrulanır ve sonra asıl dosyanın yerine tek adımda konur. Bu, dosyanın ya eski ya da yeni içerikte bulunmasını sağlar. KL7 modelin bu garantiyi varsaydığını kaydeder.

İkinci yanlış, günlük gibi biriken veriyi ve ayar gibi üzerine yazılan veriyi aynı biçimde saklamaktır. Birikenin her satırı bağımsız bir olaydır ve yineleme çoğaltma yaratır. Üzerine yazılanın ise her yazması bir önceki değeri ezer ve yineleme zararsızdır. Doğru kurulum, ikisini ayrı dosyalarda ve ayrı kurallarla tutmaktır. Birikeni çoğaltmaya karşı korumak istiyorsanız her olaya bir kimlik verip aynı kimliği ikinci kez yazmamak gerekir; bu, sıra duyarlılığını yine de ortadan kaldırmaz, yalnız yinelemeyi çözer.

Üçüncü yanlış, dizinin varlığını varsaymaktır. Dosya yazan kod, üst dizin yoksa hata alır. Dizin oluşturma işleminin ölçümde sağlam çıkması, "önce dizini oluştur, sonra dosyayı yaz" sırasının güvenle kurulabileceğini söyler.

#### KAYNAK Parçayı Okumak

Dersin başındaki adsız parça, uygulamanın kendi alanındaki dizinleri sorgular. Belgeler dizini kullanıcının verisi içindir, önbellek dizini yeniden üretilebilen veri içindir ve geçici dizin kısa ömürlü veri içindir. Sistem bu dizinlere farklı davranır: önbellek dizinindeki dosyalar yer azaldığında silinebilir, belgeler dizinindeki dosyalar yedeğe girer. Parçanın gösterdiği asıl ders budur: dosyanın nereye yazıldığı, dosyanın ne kadar yaşayacağını belirler.

Model dizin adlarını düz yol dizgisi olarak tuttuğu için bu ayrımı kurmaz; KL8 bunu kaydeder. Ölçümler dolayısıyla yalnız işlemlerin sonuçlarını, yani eklemenin, üzerine yazmanın ve oluşturmanın sınıflarını söyler. Hangi dizinin silinebilir olduğu ise model dışıdır ve uygulama tasarımında ayrıca düşünülmelidir.

#### Ölçümden Tasarıma

Dört ölçümün ortak dersi, dosya işlemlerinin kendi başlarına değil, ne için kullanıldıklarıyla değerlendirileceğidir. Sona ekleme günlük için doğru bir işlemdir, ama çoğaltmaya karşı korunmasızdır. Dosyanın üzerine yazma ayar için doğru bir işlemdir, ama son yazanı kazandırır. Bu yüzden tasarım sorusu "hangi işlemi kullanayım" değil, "bu veri için hangi kusuru kabul edebilirim" olur. Günlükte arada bir çoğalan satır çoğu zaman kabul edilebilir; bir ayar dosyasında kaybolan değişiklik çoğu zaman edilemez.

Kusuru kabul edilemez olan veri için iki yol vardır. Birincisi, her yazmayı tek bir yerden geçirmektir; böylece sıra tek bir yerde belirlenir. İkincisi, yazmayı yinelemeye karşı koruyacak bir kimlik eklemektir: her satır bir olay kimliği taşır ve aynı kimlik ikinci kez yazılmaz. Kimlik, çift duyarlı bir işlemi yalnız sıraya duyarlı bir işleme çevirir; sıra sorununu ise ancak tek bir yazıcı çözer.

Dosya sistemi bir de başka bir kusur getirir: ölçmediğimiz her şey. Yer dolduğunda yazma başarısız olur, kullanıcı uygulamayı yazma sırasında kapatır, sistem önbellek dizinini temizler. Bu durumların hiçbiri modelde yoktur. Ölçüm, işlemin mantığındaki kusuru gösterir; ortamın kusurlarını değil. İkisini de düşünmek, dosyaya yazan her kodun sorumluluğundadır.

#### Özet

- Veri türüne göre dizin seçilir: kullanıcı verisi belgelere, yeniden üretilebilen veri önbelleğe; yedeklemeyi yeniden indirilebilen veri için kapatmak mümkündür.
- Aynı satırı iki kez eklemek dosyayı `a1`'den `a1+a1`'e büyütür; sınıf yinelemeye duyarlıdır.
- İki farklı satırı eklemek yinelemeyi ve sırayı birlikte ayrıştırır (`a1+b2` ve `b2+a1`); sınıf çift duyarlıdır.
- Atomik yazma yinelemeyi çözer ama sırayı çözmez: `ab` `v2`, `ba` `v1`.
- Dizin oluşturma sağlamdır; dosya varlığına bağlı ekleme ise boş başlangıçta sağlam, dolu başlangıçta yinelemeye duyarlıdır.
- Bu dersin duyarlılığı: **çift duyarlı**.

#### Sonraki Adım

Dosyalar veriyi düz bir bayt dizisi olarak tutar. Sıradaki derste, Yerel Veritabanı, verinin satır ve birincil anahtarla saklandığı yere geçilecek ve varsa güncelle yoksa ekle işleminin bu kez sağlam çıkıp çıkmadığı ölçülecek.

### Yerel Veritabanı

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/yerel-veritabani

Birincil anahtarlı varsa güncelle yoksa ekle işleminin sağlam, kendiliğinden artan anahtarla eklemenin duyarlı olduğunun ve okuma sırasının sonucu değiştirdiğinin ölçümü.

Dosyalar veriyi bir bayt dizisi olarak tutar; içinde bir kaydı bulmak, bir alanı güncellemek ya da iki kaydı birleştirmek uygulamanın işidir. Veri satırlara ve alanlara bölünebiliyorsa, aranıyor ve güncelleniyorsa **yerel veritabanı** daha uygun bir araçtır. Aygıttaki en yaygın seçenek gömülü bir ilişkisel veritabanıdır; tek bir dosyada yaşar ve sorgu dili sunar. Bu dersin sorusu önceki derslerle aynıdır: **bu işlem yinelenirse ya da sırası değişirse sonuç değişir mi?** Fark şudur: veritabanı kimliği kendisi yönetir ve bu kimliğin nereden geldiği sonucu belirler.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

`bir` işlemin başlangıç durumuna bir kez, `iki` iki kez uygulanmasıdır; `ab` önce `a` sonra `b`, `ba` ise tersi sıradır. Aşağıdaki model bir tablonun saf Swift karşılığıdır. Model sorgu motoru değildir; yalnız işlemin tabloda bıraktığı sonucu kurar.

#### Birincil Anahtar ve Ekleme

Her satırın bir **birincil anahtar**ı vardır ve tabloda aynı anahtardan iki satır olamaz. Bir satır eklenirken anahtar iki yoldan gelir. Uygulama anahtarı kendisi verir; bu durumda aynı anahtarla gelen ikinci ekleme ya hata verir ya da varsa güncelle yoksa ekle sözdizimiyle mevcut satırı değiştirir. Ya da veritabanı anahtarı kendiliğinden üretir; bu durumda her ekleme yeni bir satır açar.

```swift
import SQLite3

var database: OpaquePointer?
sqlite3_open("notes.sqlite", &database)

sqlite3_exec(database, "CREATE TABLE IF NOT EXISTS notes (id TEXT PRIMARY KEY, body TEXT)", nil, nil, nil)
sqlite3_exec(database, "INSERT INTO notes (id, body) VALUES ('k1', 'a1') ON CONFLICT(id) DO UPDATE SET body = excluded.body", nil, nil, nil)
sqlite3_exec(database, "SELECT id, body FROM notes ORDER BY id", nil, nil, nil)
sqlite3_close(database)
```

Bu parça KAYNAK'tır; bir veritabanı dosyası gerektirdiği için burada koşmaz. `ON CONFLICT(id) DO UPDATE` yan tümcesi varsa güncelle yoksa ekle davranışını kurar: anahtar yoksa satırı ekler, varsa `body` alanını yenisiyle değiştirir. `ORDER BY id` okumayı anahtara göre sıralar. Bu yan tümce olmadan satırların hangi sırayla döneceği sorgu motoruna bırakılır.

#### Tablonun Modeli

Model, anahtarların ekleme sırasını ve anahtardan değere bir eşlemeyi tutar. İki ek bilgi taşır: sıradaki kendiliğinden artan anahtar ve okumanın anahtara göre sıralı olup olmadığı.

```swift
// TableModel.swift — bir tablonun saf Swift modeli
struct TableBox: Equatable, CustomStringConvertible {
    private(set) var keys: [String] = []
    private(set) var values: [String: String] = [:]
    private(set) var nextId = 1
    let sortedRead: Bool

    init(sortedRead: Bool = true) {
        self.sortedRead = sortedRead
    }

    var description: String {
        let order = sortedRead ? keys.sorted() : keys
        return order.isEmpty ? "-" : order.map { "\($0)=\(values[$0] ?? "-")" }.joined(separator: ",")
    }

    static func == (lhs: TableBox, rhs: TableBox) -> Bool {
        lhs.description == rhs.description
    }

    mutating func upsert(_ value: String, key: String) {
        if values[key] == nil { keys.append(key) }
        values[key] = value
    }

    mutating func insert(_ value: String) {
        let key = String(nextId)
        nextId += 1
        keys.append(key)
        values[key] = value
    }
}

func upserting(_ value: String, key: String) -> (TableBox) -> TableBox {
    { table in
        var next = table
        next.upsert(value, key: key)
        return next
    }
}

func inserting(_ value: String) -> (TableBox) -> TableBox {
    { table in
        var next = table
        next.insert(value)
        return next
    }
}
```

Mevcut bir anahtara upsert, satırı yerinde bırakıp yalnız değeri değiştirir; ekleme sırasındaki yeri korunur. Kendiliğinden artan anahtar `1`'den başlar ve her eklemede bir artar.

**KL9.** Model, sıralama belirtilmediğinde satırların ekleme sırasıyla döneceğini varsayar. Gerçek sorgu motoru bu sırayı garanti etmez; sınırlayıcı ölçüm bu varsayımı açıkça kullanır.

**KL10.** Model tek bir tabloyu ve tek bir birincil anahtar sütununu kurar. Birden çok sütunlu anahtar ve ilişkiler kurulmadı.

#### Dört İşlemin Ölçümü

Dört işlem kurulur. Birincisi iki farklı anahtara varsa güncelle yoksa ekle uygulamaktır. İkincisi aynı anahtara iki farklı değer yazmaktır. Üçüncüsü aynı değeri kendiliğinden artan anahtarla iki kez eklemektir. Dördüncüsü iki farklı değeri aynı yolla eklemektir.

```swift
// main.swift — birincil anahtar ve kendiliginden artan anahtar
let distinctKeys = replay("varsa-guncelle-ayri-anahtar", from: TableBox(), upserting("a1", key: "k1"), then: upserting("b2", key: "k2")).report()
let sameKey = replay("varsa-guncelle-ayni-anahtar", from: TableBox(), upserting("a1", key: "k1"), then: upserting("b2", key: "k1")).report()
let autoSame = replay("otomatik-ayni-deger", from: TableBox(), inserting("a1"), then: inserting("a1")).report()
let autoTwo = replay("otomatik-iki-deger", from: TableBox(), inserting("a1"), then: inserting("b2")).report()

countSensitivities([distinctKeys, sameKey, autoSame, autoTwo])
```

```
varsa-guncelle-ayri-anahtar | bir=k1=a1 iki=k1=a1 ab=k1=a1,k2=b2 ba=k1=a1,k2=b2 -> saglam
varsa-guncelle-ayni-anahtar | bir=k1=a1 iki=k1=a1 ab=k1=b2 ba=k1=a1 -> siraya-duyarli
otomatik-ayni-deger | bir=1=a1 iki=1=a1,2=a1 ab=1=a1,2=a1 ba=1=a1,2=a1 -> yinelemeye-duyarli
otomatik-iki-deger | bir=1=a1 iki=1=a1,2=a1 ab=1=a1,2=b2 ba=1=b2,2=a1 -> cift-duyarli
saglam: 1
yinelemeye-duyarli: 1
siraya-duyarli: 1
cift-duyarli: 1
```

İlk satırda iki farklı anahtar hangi sırayla yazılırsa yazılsın aynı tabloyu verir ve aynı yazmayı yinelemek tabloyu değiştirmez. Okuma anahtara göre sıralandığı için ekleme sırası da görünmez. Sınıf **sağlam**dır. Bu, bu derste ölçülen tek sağlam satırdır ve nedeni iki şeyin birlikte bulunmasıdır: anahtarı uygulama verir ve okuma sıralıdır.

İkinci satırda aynı anahtara iki farklı değer yazılıyor. Yineleme zararsızdır; ama `ab` `b2`, `ba` `a1`. Son yazan kazanır. Birincil anahtar çift satırı engeller, çakışmada kimin kazanacağını söylemez. Sınıf **sıraya duyarlı**dır.

Üçüncü satırda anahtarı veritabanı üretir. Aynı değeri iki kez eklemek iki satır bırakır: `1=a1` ve `1=a1,2=a1`. İki işlem aynı olduğundan sıra anlamsızdır. Sınıf **yinelemeye duyarlı**dır. Yeniden denenen bir ekleme çağrısı, veritabanı bunu iki ayrı kayıt sayacağı için çiftlenir.

Dördüncü satırda iki farklı değer iki farklı sırayla eklenince anahtarlar da değerlere farklı dağılır: `1=a1,2=b2` ve `1=b2,2=a1`. Sınıf **çift duyarlı**dır.

Sayım her sınıftan birer tanedir.

#### Anahtarı Kim Verir

Ölçümün söylediği pratik bir kural olur. Kayıtların doğal bir kimliği varsa, örneğin sunucunun verdiği bir kimlik ya da içerikten türeyen bir özet, anahtarı uygulama vermelidir ve ekleme varsa güncelle yoksa ekle olarak yazılmalıdır. Böylece yeniden deneme güvenli hâle gelir. Doğal bir kimlik yoksa kendiliğinden artan anahtar kullanılır; ama yeniden deneme çiftlenir ve bunu önleyen bir şey, örneğin ayrı bir benzersiz sütun, tasarıma eklenir.

#### Sık Yapılan Hatalar

- Yeniden denenen bir ekleme çağrısında kendiliğinden artan anahtara güvenmek. Satır çiftlenir.
- Sıralama belirtmeden okumak ve ekleme sırasının korunduğunu sanmak.
- Varsa güncelle yoksa ekle yerine önce okuyup sonra ekleyen iki adımlı bir yol yazmak. İki adım arasında başka bir yazma araya girebilir.
- Aynı satırı iki kaynaktan güncelleyip kazananın belli olduğunu sanmak. Son yazan kazanır.
- Her küçük değer için tablo açmak. Tercih deposu bunun için yeterlidir.
- Çok sayıda satırı tek tek, ayrı işlemlerle yazmak. Hepsi tek bir işlemde yazılırsa tutarlılık ve hız kazanılır.

**KL11.** Model, her çağrının kesintisiz ve tek bir bağlantıdan koştuğunu varsayar. İki bağlantının aynı anda yazdığı durum kurulmadı.

#### Sınırlayıcı Ölçüm

İlk satırın sağlamlığı okuma sırasına dayanıyordu. Aynı iki işlemi, okumayı ekleme sırasıyla yapan bir tabloda ölçelim ve karşılaştırma için sıralı olanla yan yana koyalım.

```swift
// main.swift — okuma sirasi
let sorted = replay("anahtar-sirasi-okuma", from: TableBox(sortedRead: true), upserting("a1", key: "k1"), then: upserting("b2", key: "k2")).report()
let insertion = replay("ekleme-sirasi-okuma", from: TableBox(sortedRead: false), upserting("a1", key: "k1"), then: upserting("b2", key: "k2")).report()

countSensitivities([sorted, insertion])
```

```
anahtar-sirasi-okuma | bir=k1=a1 iki=k1=a1 ab=k1=a1,k2=b2 ba=k1=a1,k2=b2 -> saglam
ekleme-sirasi-okuma | bir=k1=a1 iki=k1=a1 ab=k1=a1,k2=b2 ba=k2=b2,k1=a1 -> siraya-duyarli
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

İşlem ve veri aynıdır; yalnız okuma değişti. Anahtara göre sıralı okuma sağlam, ekleme sırasıyla okuma sıraya duyarlıdır: `k1,k2` ve `k2,k1`. Tablonun içeriği iki durumda da aynı iki satırdır; fark satırların nasıl listelendiğidir. Bu, bir kümenin sıralı bir dizi gibi okunduğu her yerde görülen bir hatadır: sonuç kümeyi değil, listelemeyi etkiler.

Sınırın iki yönü vardır. Birincisi KL9'un kullanıldığı yerdir: ekleme sırasıyla okuma gerçek sorgu motorunda garanti değildir, burada yalnız modelin bir seçeneğidir. İkincisi kuralın görmediği şeydir: kural iki durumun eşit olmadığını söyler, farkın okuma sırasından geldiğini söylemez. Nedeni insan koyar.

#### Gerçek Uygulamada Nerede Görülür

Otomatik üretilen anahtar, çoğu tabloda varsayılan seçimdir ve ölçümün `otomatik-ayni-deger` ile `otomatik-iki-deger` satırları bunun bedelini gösterir. Bir kaydı kaydetme kodu yeniden çalıştığında, veritabanı her seferinde yeni bir kimlik verir ve aynı kayıt iki satır olarak durur. Bir sipariş formunun gönder düğmesine iki kez basılması, bir ağ hatasından sonra isteğin yeniden denenmesi ya da uygulamanın yarım kalmış bir işi yeniden başlatması bu çoğaltmayı üretir. Liste ekranı iki aynı siparişi gösterir; kullanıcı bunu bir hata olarak görür, uygulama ise iki ayrı geçerli kayıt görür.

Anahtarı çağıranın verdiği durumda tablo çoğaltmayı reddeder, ama başka bir sorun çıkar: aynı anahtara iki farklı değer yazan iki işlem varsa kalan değer, hangisinin son çalıştığına bağlıdır. `varsa-guncelle-ayni-anahtar` satırı bunu gösterir. Bu, tabloların sağlam sayılan bir yerinde bile sıra duyarlılığının sürdüğünü anlatır. Ayrı anahtarlara yazan işlemler ise birbirini etkilemez, ve `varsa-guncelle-ayri-anahtar` satırı sağlam çıkar.

Bu yüzden veritabanında "sağlam" bir etiket tabloya değil, işlemin hangi anahtarlara dokunduğuna bağlıdır. Aynı tablo, çakışmayan anahtarlarla çalışırken sağlam, aynı anahtara yazılırken sıraya duyarlıdır. Dersin duyarlılık etiketinin sağlam olması, tabloların en çok korunan işlem biçimine, yani anahtarlı güncellemeye dayanır.

#### Yanlış Kurulum ve Düzeltmesi

En yaygın yanlış kurulum, kaydın var olup olmadığını önce sorgulayıp sonra eklemektir. Önce "bu kayıt var mı" diye bakılır, yoksa eklenir. İki işlem aynı anda bu soruyu sorarsa ikisi de "yok" yanıtını alır ve ikisi de ekler. Kontrol ile ekleme arasındaki aralık, yinelemenin sızdığı yerdir.

Düzeltme, kontrolü ayrı bir adım olarak bırakmak yerine tablonun kendisine yaptırmaktır. Anahtar çağıran tarafından, kayıttan türetilen kararlı bir değer olarak verilir ve tablo aynı anahtarın ikinci kez eklenmesini reddeder ya da var olan satırı günceller. Böylece "var mı, yoksa ekle" tek bir işlem hâline gelir ve araya girilecek bir aralık kalmaz. Ölçümün diliyle: işlem yinelemeye duyarlı olmaktan çıkar.

İkinci yanlış, sorgu sonucunun sırasına güvenmektir. Sıralama belirtilmeyen bir sorgunun satırları hangi sırayla döndüreceği garanti edilmez. Bir ekranın sırası bugün ekleme sırasıyla örtüşse bile, tabloya bir dizin eklendiğinde ya da veri büyüdüğünde değişebilir. Sınırlayıcı ölçümdeki ekleme sırası satırı bunu açıkça kullandı ve KL9 bu varsayımın modelin bir seçeneği olduğunu kaydetti. Düzeltme, sıranın önemli olduğu her sorguda sıralamayı açıkça belirtmektir; anahtar sırası, ölçümde sağlam çıkan okuma biçimidir.

#### KAYNAK Parçayı Okumak

Dersin başındaki adsız parça, bir tabloyu tanımlayıp ona satır ekleyen ve sonra sorgulayan bir bağlantı çağrısı dizisidir. Gerçek bir bağlantı bir dosyayı açar, sorguyu bir ifade olarak hazırlar, değerleri ifadeye bağlar ve adım adım koşturur. Modelimizdeki tek çağrı, gerçekte bu dört adımın toplamıdır; KL10 bu sadeleştirmeyi ve tablonun tek bir birincil anahtar sütunuyla sınırlı kalmasını kaydeder.

Parçadaki değerler kısalık için doğrudan sorgu metnine yazılmıştır; kullanıcıdan gelen metin bu biçimde sorguya katılırsa sorgunun kendisini değiştirebilir. Gerçek kodda değerler hazırlanan ifadeye bağlanır ve kullanıcı metni sorgu metnine hiç katılmaz. Bu, güvenlik açısından önemli bir alışkanlıktır ve bu dersin ölçümleriyle ilgisi yoktur; yine de gerçek koddaki yerini bilmek için not edilir. Bağlantının her çağrıda kesintisiz çalıştığı varsayımı KL11'dir: iki bağlantının aynı anda yazdığı durum ölçülmedi, ve gerçek veritabanlarında bu durum kilit ve işlem yalıtımı gibi ayrı konulara girer.

#### Ölçümden Tasarıma

Bu dersin sonucu, tabloların neden güvenli bir varsayılan olduğunu ve bu güvenin nereye kadar uzandığını söyler. Anahtarlı güncelleme sağlamdır, ama yalnız ayrı anahtarlara yazıldığında. Aynı anahtara iki farklı değer yazılıyorsa sıra yine belirleyicidir. Tablonun kattığı şey, çakışmanın görünür olması ve kuralın tabloda yazılı olmasıdır: son yazan mı kazanır, ilk yazan mı, yoksa ikinci yazma reddedilir mi. Bu kuralı kodda saklamak yerine şemada ifade etmek, her yazma yolunun aynı kurala uymasını sağlar.

Tasarım için üç soru yeterlidir. Kaydın doğal bir kimliği var mı? Varsa anahtarı ondan türetmek, yinelemeyi tablonun reddetmesini sağlar. Yoksa, üretilen bir kimlik her zaman çoğaltma riski taşır ve bu risk işlem tarafında, örneğin her istek için bir kez üretilen bir anahtarla yönetilir. İki işlem aynı kayda yazabiliyor mu? Yazabiliyorsa kuralın ne olduğu yazılmalıdır. Okuma sırası önemli mi? Önemliyse sıralama açıkça belirtilmelidir.

Bu üç sorunun hiçbiri veritabanı seçimine bağlı değildir. Sorgu dili ne kadar güçlü olursa olsun, işlemin yinelemeye ve sıraya karşı davranışı kaydın nasıl tanımlandığına bağlıdır. Ölçümün öğrettiği budur: sağlam olan tablo değil, tablonun doğru kullanıldığı işlemdir.

Küçük bir ek uyarı: ölçüm tek tabloya bakar. Gerçek bir veritabanında iki tablo bir ilişkiyle bağlıdır ve bir işlemin ikisine birden yazması gerekir. Bu durumda yarım kalan bir yazma, tutarsız bir veri bırakabilir; bu konu işlem yalıtımına girer ve bu dersin ölçümünün kapsamı dışındadır.

#### Özet

- Veri satırlara ve alanlara bölünüp aranıyorsa dosya yerine yerel veritabanı seçilir.
- Birincil anahtarlı varsa güncelle yoksa ekle iki farklı anahtarda ve sıralı okumada sağlamdır.
- Aynı anahtara iki farklı değer yazmak sıraya duyarlıdır: `ab` `b2`, `ba` `a1`.
- Kendiliğinden artan anahtarla ekleme tabloyu `1=a1`'den `1=a1,2=a1`'e büyütür; iki farklı değerde çift duyarlıdır.
- Aynı iki işlem, okuma anahtara göre sıralıysa sağlam, ekleme sırasıyla yapılıyorsa sıraya duyarlıdır.
- Bu dersin duyarlılığı: **sağlam**.

#### Sonraki Adım

Veritabanını doğrudan sorgularla kullanmak yerine nesnelerle çalışan çerçeveler vardır. Sıradaki derste, Nesne Kalıcılık Çerçeveleri, bir nesneyi bağlamda oluşturup kaydetmenin yinelemeye duyarlı olduğu ve benzersizlik kısıtının bunu nasıl değiştirdiği ölçülecek.

### Nesne Kalıcılık Çerçeveleri

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/nesne-kalicilik-cerceveleri

Nesneleri bir bağlamda oluşturup kaydetmenin yinelemeye duyarlı, benzersizlik kısıtlı kaydın ise sıraya duyarlı olduğunun ve kısıtın sınıfı nasıl değiştirdiğinin ölçümü.

Önceki derste veritabanına satır ve birincil anahtar düzeyinde yaklaştık. Çoğu uygulama satırlarla değil, kendi tiplerinden **nesnelerle** düşünür: bir not, bir sipariş, bir kişi. **Nesne kalıcılık çerçevesi** bu nesneleri bir **bağlam** içinde izler, değişiklikleri biriktirir ve kaydedildiğinde alttaki depoya yazar. Satırın karşılığı nesnedir; birincil anahtarın karşılığı ise çoğu zaman bir benzersizlik kısıtıdır. Sorumuz değişmez: **bu işlem yinelenirse ya da sırası değişirse sonuç değişir mi?**

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

`bir` işlemin başlangıç durumuna bir kez, `iki` iki kez uygulanmasıdır; `ab` önce `a` sonra `b`, `ba` ise tersi sıradır. Model bir bağlamın saf Swift karşılığıdır. Çerçevenin kendisi değildir; yalnız bir nesneyi oluşturup kaydetmenin sonucunu kurar.

#### Bağlam, Nesne ve Kayıt

Bir bağlam, bellekte yaşayan nesneler kümesinin yöneticisidir. Uygulama yeni bir nesne oluşturur, bağlama ekler ve kaydeder. Kaydedilene kadar değişiklik yalnız bellektedir. İki yazım dili vardır: eski çerçeve bir kalıcı kap ve bir yönetilen nesne bağlamı kullanır; yeni çerçeve `@Model` ile işaretlenmiş sınıflar ve bir model bağlamı kullanır.

```swift
import SwiftData

@Model
final class Note {
    @Attribute(.unique) var id: String
    var body: String

    init(id: String, body: String) {
        self.id = id
        self.body = body
    }
}

let container = try ModelContainer(for: Note.self)
let context = ModelContext(container)
context.insert(Note(id: "k1", body: "a1"))
try context.save()
```

Bu parça KAYNAK'tır; bir model kabı ve disk gerektirdiği için burada koşmaz. `@Attribute(.unique)` bir niteliği benzersiz yapar: aynı `id` ile gelen ikinci nesne yeni bir kayıt açmaz, mevcut kaydı günceller. Nitelik kısıtsız bırakılırsa her ekleme yeni bir kayıt olur. Eski çerçevede aynı amaç benzersizlik kısıtıyla ve bir birleştirme politikasıyla sağlanır. Bu dersin ölçümü iki durumu karşılaştırır.

#### Bağlamın Modeli

Model, kayıtların bir listesini tutar. Her kayıt bir anahtar ve bir değerdir. Kısıt açıksa aynı anahtarla gelen kayıt mevcut kaydın yerine geçer; kapalıysa yeni bir kayıt olarak eklenir. Anahtarsız oluşturulan nesneye bağlam kendi anahtarını verir.

```swift
// ContextModel.swift — bir baglamin saf Swift modeli
struct ContextBox: Equatable, CustomStringConvertible {
    struct Entry {
        let key: String
        var value: String
    }

    private(set) var entries: [Entry] = []
    private(set) var nextId = 1
    let enforcesUnique: Bool

    init(enforcesUnique: Bool = true) {
        self.enforcesUnique = enforcesUnique
    }

    var description: String {
        entries.isEmpty ? "-" : entries.map { "\($0.key)=\($0.value)" }.joined(separator: ",")
    }

    static func == (lhs: ContextBox, rhs: ContextBox) -> Bool {
        lhs.description == rhs.description
    }

    mutating func create(_ value: String) {
        entries.append(Entry(key: "obj\(nextId)", value: value))
        nextId += 1
    }

    mutating func save(_ value: String, key: String) {
        if enforcesUnique, let index = entries.firstIndex(where: { $0.key == key }) {
            entries[index].value = value
        } else {
            entries.append(Entry(key: key, value: value))
        }
    }
}

func creating(_ value: String) -> (ContextBox) -> ContextBox {
    { context in
        var next = context
        next.create(value)
        return next
    }
}

func saving(_ value: String, key: String) -> (ContextBox) -> ContextBox {
    { context in
        var next = context
        next.save(value, key: key)
        return next
    }
}
```

**KL12.** Model, aynı benzersiz anahtar geldiğinde son yazanın kazandığını varsayar: mevcut kaydın değeri yenisiyle değişir. Çerçevenin gerçek birleştirme politikası ve bunun ayarları ölçülmedi.

**KL13.** Model, her işlemi eklemek ve hemen kaydetmek olarak kurar. Kaydedilmemiş değişiklikler, geri alma ve birden çok bağlam kurulmadı.

#### Dört İşlemin Ölçümü

Dört işlem kurulur. Birincisi aynı değerle yeni bir nesne oluşturmaktır. İkincisi iki farklı değerle iki nesne oluşturmaktır. Üçüncüsü benzersiz bir anahtara aynı değeri iki kez kaydetmektir. Dördüncüsü aynı anahtara iki farklı değer kaydetmektir.

```swift
// main.swift — nesne olusturma ve benzersiz kayit
let createSame = replay("olustur-ayni-deger", from: ContextBox(), creating("a1"), then: creating("a1")).report()
let createTwo = replay("olustur-iki-deger", from: ContextBox(), creating("a1"), then: creating("b2")).report()
let uniqueSame = replay("kisitli-ayni-deger", from: ContextBox(), saving("a1", key: "k1"), then: saving("a1", key: "k1")).report()
let uniqueTwo = replay("kisitli-iki-deger", from: ContextBox(), saving("a1", key: "k1"), then: saving("b2", key: "k1")).report()

countSensitivities([createSame, createTwo, uniqueSame, uniqueTwo])
```

```
olustur-ayni-deger | bir=obj1=a1 iki=obj1=a1,obj2=a1 ab=obj1=a1,obj2=a1 ba=obj1=a1,obj2=a1 -> yinelemeye-duyarli
olustur-iki-deger | bir=obj1=a1 iki=obj1=a1,obj2=a1 ab=obj1=a1,obj2=b2 ba=obj1=b2,obj2=a1 -> cift-duyarli
kisitli-ayni-deger | bir=k1=a1 iki=k1=a1 ab=k1=a1 ba=k1=a1 -> saglam
kisitli-iki-deger | bir=k1=a1 iki=k1=a1 ab=k1=b2 ba=k1=a1 -> siraya-duyarli
saglam: 1
yinelemeye-duyarli: 1
siraya-duyarli: 1
cift-duyarli: 1
```

Aynı değerle yeni nesne oluşturmak yinelendiğinde iki nesne bırakır: `obj1=a1` ve `obj1=a1,obj2=a1`. Sıra anlamsızdır. Sınıf **yinelemeye duyarlı**dır. Bir ekranın yeniden görünmesiyle ya da bir yeniden denemeyle ikinci kez koşan oluşturma kodu, kullanıcıya çift kayıt olarak yansır.

İki farklı değerle oluşturma hem yinelemeyi hem sırayı ayrıştırır: kimlikler değerlere farklı dağılır. Sınıf **çift duyarlı**dır.

Benzersiz anahtara aynı değeri iki kez kaydetmek hiçbir şeyi değiştirmez. Kısıt, yinelemeyi sağlam kılar. Sınıf **sağlam**dır.

Aynı anahtara iki farklı değer kaydedilince ise sonucu sıra belirler: `ab` `b2`, `ba` `a1`. Kısıt çift kaydı önler, ama çakışmada hangi değerin kazanacağı sıraya kalır. Sınıf **sıraya duyarlı**dır.

Sayım her sınıftan birer tanedir.

#### Şema Göçü

Bir uygulamanın nesne tipi sürümler arasında değişir: bir nitelik eklenir, bir ad değişir, iki tip birleşir. Kullanıcının aygıtında ise eski sürümün yazdığı veri durur. **Şema göçü**, eski şemayla yazılmış veriyi yeni şemaya taşıyan adımdır. Basit değişiklikler, örneğin bir niteliğin eklenmesi, çerçeve tarafından kendiliğinden yapılabilir; bir adın değişmesi ya da iki alanın birleşmesi gibi değişiklikler açık bir eşleme gerektirir.

Şema göçü bu dersin modelinde kurulmadı. Çünkü sonucu depodaki eski veriye ve sürüm sırasına bağlıdır; bir bağlamın bellekteki hâlinden okunamaz. Burada KAYNAK ve düzyazı olarak kalır. Pratik kural basittir: şemayı değiştiren her sürüm, eski sürümün yazdığı bir veriyle denenmeden yayımlanmaz.

#### Sık Yapılan Hatalar

- Oluşturma kodunu bir ekranın görünme olayına bağlamak. Ekran her göründüğünde yeni bir nesne doğar.
- Kayıt için doğal bir kimlik varken bunu benzersiz yapmamak. Yeniden deneme çiftlenir.
- Benzersizlik kısıtının çakışmayı çözdüğünü sanmak. Çakışmada kazananı sıra belirler.
- Kaydetmeyi unutmak. Değişiklik bellektedir ve uygulama kapanınca kaybolur.
- Bağlamı iş parçacıkları arasında paylaşmak. Çerçeve bunu desteklemez; her iş parçacığı kendi bağlamını kullanır.
- Şemayı değiştirip eski veriyle denemeden yayımlamak.

#### Sınırlayıcı Ölçüm

Kısıtın katkısını ayırmak için aynı kayıt çiftini kısıt kapalıyken ölçelim. Kısıt kapalıysa aynı anahtarla gelen kayıt mevcut kaydın yerine geçmez; yeni bir kayıt olur.

```swift
// main.swift — kisit acik ve kapali
let openSame = replay("kisitsiz-ayni-deger", from: ContextBox(enforcesUnique: false), saving("a1", key: "k1"), then: saving("a1", key: "k1")).report()
let openTwo = replay("kisitsiz-iki-deger", from: ContextBox(enforcesUnique: false), saving("a1", key: "k1"), then: saving("b2", key: "k1")).report()
let closedSame = replay("kisitli-ayni-deger-yeniden", from: ContextBox(), saving("a1", key: "k1"), then: saving("a1", key: "k1")).report()

countSensitivities([openSame, openTwo, closedSame])
```

```
kisitsiz-ayni-deger | bir=k1=a1 iki=k1=a1,k1=a1 ab=k1=a1,k1=a1 ba=k1=a1,k1=a1 -> yinelemeye-duyarli
kisitsiz-iki-deger | bir=k1=a1 iki=k1=a1,k1=a1 ab=k1=a1,k1=b2 ba=k1=b2,k1=a1 -> cift-duyarli
kisitli-ayni-deger-yeniden | bir=k1=a1 iki=k1=a1 ab=k1=a1 ba=k1=a1 -> saglam
saglam: 1
yinelemeye-duyarli: 1
siraya-duyarli: 0
cift-duyarli: 1
```

Aynı kayıt çifti, kısıt kapalıyken yinelemeye duyarlı, açıkken sağlam çıktı. İki değerli çift ise kısıtsız çift duyarlı, kısıtlı sıraya duyarlıydı. Kısıt yinelemeyi çözdü ve iki satırı bir satıra indirdi; sırayı çözmedi.

Bu ölçümün sınırı KL12'dir. Model, çakışmada son yazanın kazanacağını varsayar. Gerçek çerçevede birleştirme politikası ayarlanabilir ve bazı politikalar mevcut kaydı korur; bu derste hiçbiri ölçülmedi. Politika değişirse `ab` ile `ba` yer değiştirebilir, ama ikisinin eşit olmayacağı sonucu değişmez.

#### Gerçek Uygulamada Nerede Görülür

Nesne kalıcılık çerçeveleri, bir nesne grafiğini sizin yerinize satırlara çevirir; bu rahatlık, yinelemenin görünmez hâle gelmesinin de nedenidir. Kullanıcı bir not oluşturduğunda uygulama yeni bir nesne yaratır ve bağlamı kaydeder. Aynı kod yolu bir yeniden denemede ya da bir ekran yenilemesinde tekrar çalışırsa, çerçeve her seferinde yeni bir nesne üretir. `olustur-ayni-deger` satırında gördüğümüz çoğaltma budur: iki çağrı, iki nesne. Liste ekranında aynı not iki kez durur ve hiçbir hata verilmemiştir.

Benzersizlik kısıtı bu çoğaltmayı engeller. Bir nitelik benzersiz olarak işaretlenince, aynı değerli ikinci nesne eklenemez; çerçeve ya yeni nesneyi var olanla birleştirir ya da kaydı reddeder. `kisitli-ayni-deger` satırında iki çağrı tek nesne verir ve sınıf sağlam olur. Ama kısıt, iki farklı değerin aynı benzersiz anahtarla gelmesini çözmez: `kisitli-iki-deger` satırında kalan değer sıraya bağlıdır. Kısıt yinelemeyi çözer, sırayı çözmez.

Gerçek bir uygulamada bu, sunucudan gelen kayıtların yerel kopyayla eşleştirilmesinde görülür. Sunucu her kaydın kararlı bir kimliğini verir; yerel kayıt bu kimlikle benzersiz kılınırsa, aynı yanıtın iki kez işlenmesi bir kayıt üretir. Kimlik olmadan yerel nesne yeniden oluşturulur ve iki kopya doğar.

#### Yanlış Kurulum ve Düzeltmesi

Yaygın yanlış kurulum, benzersizliği kodda kontrol etmektir: "bu değerli nesne var mı, yoksa oluştur". Kontrol ile oluşturma arasında kalan aralık, iki işlemin ikisinin de "yok" yanıtı alabildiği yerdir. Ayrıca kontrol yalnız bağlamdaki nesnelere bakar; başka bir bağlamda ya da henüz kaydedilmemiş bir nesne gözden kaçar.

Düzeltme, benzersizliği şemanın bir parçası yapmaktır. Kısıt depolama katmanında durur ve her yazmada uygulanır; kodun hangi yoldan geldiği önemli olmaz. Çerçevenin birleştirme politikası da burada belirleyicidir. Politika, çakışmada hangi değerin kalacağını söyler. Modelimiz son yazanın kazandığını varsaydı, ve bu KL12 olarak kayıtlıdır. Başka bir politika mevcut kaydı korursa `ab` ile `ba` yer değiştirir; ama iki sıranın aynı sonucu vermediği gerçeği değişmez.

İkinci yanlış, kaydetmeyi unutmaktır. Nesneyi oluşturup bağlamı kaydetmeyen kod, değişikliğin kalıcı olduğunu sanır. Uygulama kapanınca nesne kaybolur. Modelimiz her işlemi ekleyip hemen kaydeder; KL13 bunu kaydeder. Gerçek kodda kaydetme çağrısını kimin yaptığı, iş yapan her yerde açıkça belirlenmelidir.

#### KAYNAK Parçayı Okumak

Dersin başındaki adsız parça, bir nesne tipini tanımlar, bir bağlam açar, nesneyi oluşturur ve kaydeder. Nesne tipi, kalıcı olacak niteliklerini belirtir ve çerçeve bunları tabloya çevirir. Parçanın gösterdiği asıl şey, sizin "kaydet" dediğiniz çağrının aslında bir işlem sınırı olmasıdır: bu çağrıya kadar yapılan değişiklikler bellekte durur ve çağrıyla birlikte diske gider.

Model bunu tek adıma indirgedi ve kaydedilmemiş değişiklikleri, geri almayı ve birden çok bağlamı kurmadı. Bunlar gerçek uygulamada önemli durumlardır; ancak ölçtüğümüz soru, yani aynı işlemin yinelenmesi ve sırasının değişmesi, bu sadeleştirmeyle de anlamlı kalır. Şema göçü kısmında anlatılan sürüm değişikliği ise ayrı bir konudur ve bu derste yalnız anlatılır, ölçülmez.

#### Ölçümden Tasarıma

Nesne çerçevesinin ölçümü, kısıtın ne yaptığını ve ne yapmadığını ayırır. Kısıtsız durumda yinelenen oluşturma çoğaltma yaratır; kısıtla aynı çağrılar tek nesne verir. Ama kısıt yalnız eşitliği çözer. Aynı anahtarla gelen iki farklı değerde hangisinin kalacağı, çerçevenin birleştirme politikasına bağlıdır. Dolayısıyla tasarım iki kararı ayrı ayrı vermeyi gerektirir: hangi nitelik kimliği belirler ve çakışmada hangi değer kalır.

Kimlik seçimi bir veri modeli kararıdır. Sunucudan gelen kayıtlar için sunucunun verdiği kimlik doğal adaydır. Kullanıcının oluşturduğu kayıtlar için uygulama bir kimlik üretir ve bu kimliği oluşturma anında, bir kez, kaydın kendisine yazar. Kimliği her çağrıda yeniden üretmek, kısıtı anlamsız kılar, çünkü her çağrı farklı bir kimlik taşır ve hiçbir çakışma doğmaz.

Politika kararı ise ürün kararıdır. Son yazanın kazanması basittir ve veri kaybı riski taşır; mevcut kaydı korumak güvenlidir ama yeni bilgiyi atar; alan alan birleştirmek en iyi sonucu verir ve en çok iş ister. Hangisinin doğru olduğunu ölçüm söylemez. Ölçüm yalnız seçilen politikanın hangi sınıfa yol açtığını gösterir.

Bir de şema göçü vardır. Veri tipi sürümler arasında değişir ve eski sürümün yazdığı veri yeni şemaya taşınmalıdır. Basit eklemeler çerçeve tarafından otomatik yapılabilir; ad değişikliği ve birleştirme açık bir eşleme ister. Göç, her sürümde bir kez koşan bir işlemdir. Yinelenirse ya da yarıda kesilirse, veri ya eski ya yeni biçimde kalmalıdır; yarım bir göç, ölçtüğümüz yinelemeye duyarlılığın en pahalı biçimidir.

Ölçümün dışında kalan bir durumu da not etmek gerekir: ilişkili nesneler. Bir not ile etiketleri arasında ilişki varsa, notu silmek etiketlere ne yapar, etiketi silmek nota ne yapar? Çerçeve bu sorunun cevabını silme kuralı olarak ister: ilişkiyi kopar, bağlı nesneleri de sil ya da silmeyi reddet. Kural verilmezse varsayılan davranış devreye girer ve çoğu zaman yetim nesneler kalır. Bu dersin modeli ilişkileri kurmaz; ama gerçek bir şemada bu karar, benzersizlik kısıtı kadar önemlidir ve aynı özenle yazılmalıdır.

Silme kuralının ölçümle bağlantısı doğrudandır. Bir silme işlemi yinelenirse, ikinci silme zaten olmayan bir nesneyi hedefler; çerçevenin bu durumda hata verip vermediği, silmenin yinelemeye karşı sağlam olup olmadığını belirler. Bunu ölçmeden varsaymamak, bu dersin genel alışkanlığıdır.

#### Özet

- Nesne kalıcılık çerçevesi, nesneleri bir bağlamda izler ve kaydedildiğinde depoya yazar.
- Aynı değerle yeni nesne oluşturmak yinelendiğinde iki nesne bırakır (`obj1=a1` sonra `obj1=a1,obj2=a1`); sınıf yinelemeye duyarlıdır.
- Benzersizlik kısıtlı kayıt aynı değerde sağlamdır, aynı anahtara farklı değerde sıraya duyarlıdır (`ab` `b2`, `ba` `a1`).
- Kısıt kaldırılırsa aynı kayıt çifti yinelemeye duyarlı hâle gelir; iki değerli çift çift duyarlı olur.
- Şema göçü ve birleştirme politikası ölçülmedi; KAYNAK ve düzyazı olarak kaldı.
- Bu dersin duyarlılığı: **yinelemeye duyarlı**.

#### Sonraki Adım

Şimdiye kadar tüm veri tek bir aygıtta duruyordu. Sıradaki derste, Bulut Eşitleme, aynı kaydı iki aygıtın yazdığı durumu ölçeceğiz ve aygıtların değerlerinin farklı sırayla gelmesinin sonucu nasıl değiştirdiğini göreceğiz.

### Bulut Eşitleme

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/bulut-esitleme

Aynı kaydı iki aygıtın yazdığı durumda değerlerin geliş sırasının sonucu belirlediğinin, alan başına küme birleşiminin sağlam olduğunun ve sunucu kuralının sınıfı değiştirdiğinin ölçümü.

Bu konunun ilk beş dersi tek bir aygıttaki veriyi ele aldı. Kullanıcının telefonu, tableti ve bilgisayarı varsa aynı kayıt birden çok yerde durur. **Bulut eşitleme**, bu kopyaları bir sunucu üzerinden birbirine yaklaştırır: her aygıt yaptığı değişikliği gönderir, sunucu bunları birleştirir ve diğer aygıtlar sonucu çeker. Yeni bir soru gelmez; mevcut soru yeni bir yerde sorulur: **bu işlem yinelenirse ya da sırası değişirse sonuç değişir mi?** Fark şudur: iki aygıt birbirini beklemez, bu yüzden yazmaların sunucuya hangi sırayla vardığı uygulamanın elinde olmayabilir.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

`bir` işlemin başlangıç durumuna bir kez, `iki` iki kez uygulanmasıdır; `ab` önce `a` sonra `b`, `ba` ise tersi sıradır. Model sunucudaki tek bir kaydın saf Swift karşılığıdır. Ağı, gecikmeyi ve gerçek teslim sırasını kurmaz; yalnız bir yazmanın kayıtta bıraktığı sonucu kurar.

#### Çakışma ve Kural

İki aygıt aynı alana farklı değer yazarsa sunucuda bir **çakışma** doğar. Bir kural gerekir: hangi değer kalacak? En yaygın kural son yazanın kazanmasıdır; sunucuya sonra ulaşan değer öncekinin yerine geçer. Başka bir kural her yazmaya bir damga koymak ve en büyük damgalıyı tutmaktır. Üçüncü bir yol, değerin kendisini birleştirmektir: bir etiket kümesine iki aygıt farklı etiketler eklediyse sonuç ikisinin birleşimidir.

```swift
import CloudKit

let recordID = CKRecord.ID(recordName: "k1")
let record = CKRecord(recordType: "Note", recordID: recordID)
record["theme"] = "koyu"

let database = CKContainer.default().privateCloudDatabase
database.save(record) { saved, error in
    if let error = error as? CKError, error.code == .serverRecordChanged {
        // sunucudaki kayit bu arada degismis; uygulama kendi kuralini uygular
    }
}
```

Bu parça KAYNAK'tır; bir bulut hesabı ve ağ gerektirdiği için burada koşmaz. `serverRecordChanged` hatası, kaydedilmek istenen kaydın sunucudaki sürümünün bu arada değiştiğini bildirir. Çakışmayı çözmek uygulamaya bırakılır: sunucudaki sürüm hatayla birlikte döner ve uygulama hangi değerin kalacağına karar verir. Bu dersin ölçümü bu kararın üç biçimini karşılaştırır. Hatanın kendisi ve gerçek teslim sırası ölçülmez.

#### Kaydın Modeli

Model, tek bir kayıttaki skaler alanları ve küme alanlarını tutar. Her skaler değer bir damga taşır. Sunucunun kuralı bir ayardır: gelen yazma ya her zaman kazanır ya da yalnız damgası mevcut damgadan küçük değilse kazanır.

```swift
// SyncModel.swift — sunucudaki bir kaydin saf Swift modeli
struct SyncBox: Equatable, CustomStringConvertible {
    enum Rule {
        case lastArrival
        case highestStamp
    }

    struct Stamped {
        let value: String
        let stamp: Int
    }

    private(set) var scalars: [String: Stamped] = [:]
    private(set) var sets: [String: Set<String>] = [:]
    let rule: Rule

    init(rule: Rule = .lastArrival) {
        self.rule = rule
    }

    var description: String {
        let scalarText = scalars.keys.sorted().map { "\($0)=\(scalars[$0]?.value ?? "-")@\(scalars[$0]?.stamp ?? 0)" }
        let setText = sets.keys.sorted().map { "\($0)={\((sets[$0] ?? []).sorted().joined(separator: ","))}" }
        let all = scalarText + setText
        return all.isEmpty ? "-" : all.joined(separator: " ")
    }

    static func == (lhs: SyncBox, rhs: SyncBox) -> Bool {
        lhs.description == rhs.description
    }

    mutating func write(_ value: String, stamp: Int, field: String) {
        if rule == .highestStamp, let current = scalars[field], current.stamp > stamp {
            return
        }
        scalars[field] = Stamped(value: value, stamp: stamp)
    }

    mutating func merge(_ items: [String], field: String) {
        sets[field, default: []].formUnion(items)
    }
}

func writing(_ value: String, stamp: Int, field: String) -> (SyncBox) -> SyncBox {
    { record in
        var next = record
        next.write(value, stamp: stamp, field: field)
        return next
    }
}

func merging(_ items: [String], field: String) -> (SyncBox) -> SyncBox {
    { record in
        var next = record
        next.merge(items, field: field)
        return next
    }
}
```

Model, iki aygıtı iki ayrı işlem olarak kurar; `ab` birinci aygıtın yazmasının sunucuya önce, `ba` ikincinin önce vardığı durumdur.

**KL14.** Model, sunucunun kuralı tutarlı uyguladığını varsayar: kural bir ayardır ve her yazmaya aynen işler. Gerçek teslim sırası, gecikme ve `serverRecordChanged` ölçülmedi.

**KL15.** Damgalar, modele verilen sayılardır; saat okunmaz ve basılmaz. Gerçek aygıt saatlerinin sapması kurulmadı.

**KL16.** Model her yazmanın sunucuya ulaştığını varsayar. Kaybolan, geciken ve yinelenen teslim kurulmadı.

#### İki İşlemin Ölçümü

İki işlem kurulur. Birincisi iki aygıtın aynı alana farklı değer yazmasıdır. İkincisi iki aygıtın aynı etiket kümesine farklı etiketler eklemesidir.

```swift
// main.swift — iki aygit ayni kaydi yazar
let twoDevices = replay("iki-aygit-ayni-alan", from: SyncBox(), writing("koyu", stamp: 1, field: "theme"), then: writing("acik", stamp: 2, field: "theme")).report()
let tags = replay("kume-birlesimi", from: SyncBox(), merging(["a1"], field: "tags"), then: merging(["b2"], field: "tags")).report()

countSensitivities([twoDevices, tags])
```

```
iki-aygit-ayni-alan | bir=theme=koyu@1 iki=theme=koyu@1 ab=theme=acik@2 ba=theme=koyu@1 -> siraya-duyarli
kume-birlesimi | bir=tags={a1} iki=tags={a1} ab=tags={a1,b2} ba=tags={a1,b2} -> saglam
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0
```

İlk satırda aynı yazmayı yinelemek bir şey değiştirmiyor: `bir` ve `iki` aynı. Ama iki aygıtın değerleri sunucuya farklı sırayla varınca sonuç değişir: `ab` `acik`, `ba` `koyu`. Son yazan kazanır ve kazananı geliş sırası belirler, yani uygulamanın değil ağın. Sınıf **sıraya duyarlı**dır. Bu, konunun başından beri biriken zincirin sonudur: iki ekranın tercihi, yinelenen anahtarlık öğesi, dosyaya ekleme, birincil anahtarlı satır, iki kez oluşturulan nesne ve şimdi iki aygıtın yazdığı değer. Her adımda yerel bir sorun, bir sonrakinde başka bir yerde yeniden belirdi.

İkinci satırda iki aygıt aynı kümeye farklı etiketler ekliyor. Yineleme kümeyi değiştirmez, ve sıra da sonucu değiştirmez: iki sıra da `{a1,b2}` verir. Küme birleşimi sırayı önemsiz kılar, çünkü hiçbir değer ötekinin yerine geçmez. Sınıf **sağlam**dır. Alan başına birleştirme bu yüzden çakışmaları çözmenin en dayanıklı yoludur; bedeli, her alanın birleştirilebilir bir yapıda olması gerekmesidir. Bir temanın birleşimi olmaz.

Sayım bir sağlam, bir sıraya duyarlıdır.

#### Neyi Birleştirmeli

Ölçüm uygulamanın veri modeline bir soru olarak döner: bu alan birleştirilebilir mi? Etiketler, okunmuş işaretleri, ekli dosyaların listesi birleşir. Bir tema, bir ad, bir tarih birleşmez; bunlarda bir kazanan seçilmelidir. Kazanan seçerken damga kullanmak sıraya bağımlılığı azaltır ama kaldırmaz. Aşağıdaki sınırlayıcı ölçüm bunu gösterir.

#### Sık Yapılan Hatalar

- Son yazanın kazanacağını varsayıp veri kaybını fark etmemek. Kaybeden değer sessizce silinir.
- Birleştirilebilir bir alanı skaler yazmak. İki aygıtın eklediği etiketlerden biri kaybolur.
- Aygıt saatine güvenip damgayı oradan almak. Saatler sapar.
- Çakışma hatasını yoksaymak. Kayıt kaydedilmemiş kalır ve kullanıcı bunu fark etmez.
- Eşitlemeyi her değişiklikte hemen çalıştırmak. Çağrılar yinelenir ve sıra daha da kontrolsüz olur.
- Bir aygıtta yapılan silmeyi diğerindeki yazmayla aynı kurala bağlamak. Silme ayrı bir işlemdir ve ayrı düşünülür.

#### Sınırlayıcı Ölçüm

İlk satırın sıraya duyarlılığı, sunucunun son geleni kazandıran kuralına dayanıyordu. Aynı iki yazmayı damgası büyük olanı tutan bir kuralla ölçelim ve damgalar eşitken de bir kez daha deneyelim.

```swift
// main.swift — sunucu kurali
let arrival = replay("kural-gelis-sirasi", from: SyncBox(rule: .lastArrival), writing("koyu", stamp: 1, field: "theme"), then: writing("acik", stamp: 2, field: "theme")).report()
let stamped = replay("kural-damga", from: SyncBox(rule: .highestStamp), writing("koyu", stamp: 1, field: "theme"), then: writing("acik", stamp: 2, field: "theme")).report()
let tie = replay("kural-damga-esit", from: SyncBox(rule: .highestStamp), writing("koyu", stamp: 1, field: "theme"), then: writing("acik", stamp: 1, field: "theme")).report()

countSensitivities([arrival, stamped, tie])
```

```
kural-gelis-sirasi | bir=theme=koyu@1 iki=theme=koyu@1 ab=theme=acik@2 ba=theme=koyu@1 -> siraya-duyarli
kural-damga | bir=theme=koyu@1 iki=theme=koyu@1 ab=theme=acik@2 ba=theme=acik@2 -> saglam
kural-damga-esit | bir=theme=koyu@1 iki=theme=koyu@1 ab=theme=acik@1 ba=theme=koyu@1 -> siraya-duyarli
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 2
cift-duyarli: 0
```

Aynı iki yazma, kurala göre iki ayrı sınıfa düştü. Geliş sırası kuralında sıraya duyarlı; en büyük damga kuralında sağlam, çünkü büyük damgalı yazma hangi sırayla gelirse gelsin kazanır. Damgalar eşit olunca sıra geri döndü: `ab` `acik`, `ba` `koyu`. Damga sırayı kaldırmadı, yalnız sıranın devreye girdiği durumu daralttı.

Sınır KL14'tür. Model, sunucunun kuralı doğru ve tutarlı uyguladığını varsayar. Gerçek bir hizmette kuralın nerede ve nasıl uygulandığı, hatanın ne zaman döndüğü ve yazmaların hangi sırayla vardığı bu modelin dışındadır. Ölçülen şey kuralın sonucudur; kuralın gerçekten işlediği değil.

#### Gerçek Uygulamada Nerede Görülür

Bir notlar uygulamasını düşünün. Kullanıcı telefonda bir notun başlığını değiştirir, aynı anda tablette aynı notun başlığını başka bir şeye çevirir. İki aygıt da çevrimdışıdır ve birkaç dakika sonra bağlanır. Sunucuya hangisi önce ulaşırsa ulaşsın, ikinci gelen birincinin yerine geçer; kalan başlığı ağın gecikmesi belirler, kullanıcı değil. Ölçümdeki `iki-aygit-ayni-alan` satırı bu durumun modelidir.

Etiketler ise farklıdır. Telefon bir etiket ekler, tablet başka bir etiket ekler. Alan bir küme olarak tutulur ve sunucu iki kümenin birleşimini kaydeder. Hangi aygıtın önce ulaştığı artık önemli değildir: `kume-birlesimi` satırında iki sıra da aynı sonucu verdi. Bu fark, bir uygulamanın veri modelini kurarken ilk sorulacak sorudur: bu alan birleştirilebilir mi, yoksa bir kazanan mı seçilmelidir?

Silme işlemi ayrı bir sorun getirir. Bir aygıt kaydı siler, öteki aynı kaydı günceller. Sunucu kaydı silinmiş mi sayacak, güncellenmiş mi? Bu dersin modeli silmeyi kurmaz; ama gerçek bir eşitleme tasarımında bu karar, skaler çakışma kuralı kadar önemlidir ve açıkça verilmelidir.

#### Yanlış Kurulum ve Düzeltmesi

En yaygın yanlış kurulum, her aygıtın kayıtların tamamını yerel saatiyle damgalayıp en yeni damgayı kazandırmaktır. Aygıt saatleri sapar; kullanıcı saati elle değiştirebilir, ve bir aygıtın saati yanlış ileri olunca onun bütün yazmaları, sonradan yapılan doğru yazmaları ezer. Sınırlayıcı ölçümde gösterdiğimiz gibi, damga kuralı sıra bağımlılığını azaltır ama damgalar eşit olunca sıra geri döner. Gerçek saatler söz konusu olduğunda bu eşitlik nadir değildir.

Düzeltme iki katmanlıdır. Birincisi, damgayı aygıt saatinden değil, sunucunun verdiği artan bir sürüm sayısından almaktır; böylece damgalar karşılaştırılabilir ve sapma etkisizdir. İkincisi, mümkün olan her alanı birleştirilebilir bir yapıya taşımaktır. Etiket kümesi, okundu işareti kümesi, ek dosya listesi bu sınıftandır. Birleşen alanlar için çakışma kuralı gerekmez.

İkinci yanlış, çakışma hatasını yoksaymaktır. Sunucu kaydın bu arada değiştiğini bildirdiğinde uygulama sessiz kalırsa, kullanıcının değişikliği hiç kaydedilmemiş olur ve kullanıcı bunu fark etmez. Doğru kurulum, hatayla dönen sunucu sürümünü alıp kendi kuralını uygulamak ve sonucu yeniden göndermektir. KL15 modelin damgaları dışarıdan verilen sayılar olarak aldığını kaydeder; gerçek saat sapması modelde yoktur.

#### KAYNAK Parçayı Okumak

Dersin başındaki adsız parça, bir kaydı oluşturur, bir alanına değer yazar ve özel bir veritabanına kaydeder. Kaydetme çağrısının kapanışı, işlemin sonucunu bir hata ya da kaydedilmiş kayıt olarak bildirir. Hata türlerinden biri, kaydedilmek istenen kaydın sunucudaki sürümünün bu arada değiştiğini söyler. Bu hata bir istisna değil, normal bir sonuçtur: çok aygıtlı bir uygulamada çakışma er ya da geç olur.

Modelimiz bu hatayı ve gerçek teslim sırasını kurmaz; KL14 sunucunun kuralı tutarlı uyguladığını varsayar, KL16 ise her yazmanın sunucuya ulaştığını. Gerçek ağda yazmalar kaybolur, gecikir ve yinelenir. Yinelenen bir yazmanın sunucuda ikinci kez uygulanması, yinelemeye duyarlılık sorusunun yeniden gündeme gelmesidir ve bir sonraki derste, bildirimlerin iki kez gelmesi bağlamında ele alınacaktır.

#### Ölçümden Tasarıma

Eşitleme tasarımında ölçümün söylediği şey, alan alan karar vermek gerektiğidir. Her alan için üç seçenekten biri seçilir: birleştirilebilir bir yapıya çevirmek, damgayla bir kazanan seçmek ya da çakışmayı kullanıcıya sormak. Etiketler, işaretler ve listeler birinci gruba girer; ölçümde sağlam çıktılar. Ad, tema ve tarih gibi alanlar ikinci gruptadır ve en iyi ihtimalle sıra bağımlılığını daraltır. Üçüncü grup nadirdir ama bazı veriler için tek dürüst seçenektir; kullanıcının iki farklı metin arasında seçim yapması, birini sessizce silmekten iyidir.

Kural her zaman yazılı olmalıdır. "Son yazan kazanır" bir kuraldır ve doğru bir kuraldır, yeter ki bilinçli seçilmiş olsun. Tehlikeli olan, kuralın hiç düşünülmemesi ve sunucunun varsayılanının ürünün kuralı hâline gelmesidir.

Bu konunun zinciri burada kapanır. Basit tercihte iki ekranın yazısı sıraya bağlıydı, anahtarlıkta ekleme yinelemeye, dosyada hem yinelemeye hem sıraya, tabloda anahtara, nesne çerçevesinde kimliğe ve eşitlemede alanın doğasına. Her katmanda aynı iki soru sorulmuştur: yinelenirse ve sırası değişirse ne olur. Cevaplar değişti, soru değişmedi. Kalıcılığın her katmanı yeni bir kusur getirmez; aynı iki kusuru yeni bir yerde barındırır.

Son bir not: eşitleme başarısı kullanıcıya nasıl gösterilir sorusu da tasarımın parçasıdır. Değişiklik yerelde hemen görünür, sunucuya sonra gider. Kullanıcı, değerin henüz gönderilmediğini bilmezse ve aygıtı kapatırsa, değişiklik bekleyen bir kuyrukta kalır. Bekleyen işlemleri saklamak ve bağlantı dönünce yeniden göndermek gerekir; yeniden gönderme de yinelemedir. Bu yüzden eşitlemenin bekleyen yazmaları, yinelemeye karşı sağlam olmak zorundadır.

#### Özet

- Bulut eşitleme aynı kaydı birden çok aygıtın yazmasına izin verir; çakışmada hangi değerin kalacağını bir kural belirler.
- İki aygıtın aynı alana yazdığı farklı değerler geliş sırasına bağlıdır: `ab` `acik`, `ba` `koyu`; sınıf sıraya duyarlıdır.
- Alan başına küme birleşimi sağlamdır: iki sıra da `{a1,b2}` verir.
- En büyük damga kuralı aynı yazma çiftini sağlam yapar, ama damgalar eşit olunca sıra geri döner.
- Sunucu kuralı, gerçek teslim sırası ve çakışma hatası ölçülmedi; KL14 olarak kaldı.
- Bu dersin duyarlılığı: **sıraya duyarlı**.

#### Sonraki Adım

Eşitleme, değerin aygıtlar arasında nasıl yürüdüğünü anlattı. Sıradaki derste, Uzak Bildirimler, sunucunun uygulamaya kendiliğinden haber vereceği yola geçeceğiz: bir aygıt belirteci, bir izin ve bir rozetin yanında, aynı bildirimin iki kez gelmesinin sonucu değiştirip değiştirmediğini ölçeceğiz.

### Uzak Bildirimler

Source: https://academia.sh/tr/kurslar/ios-veri-ve-ag/uzak-bildirimler

Sunucudan gelen bildirimin yerelde rozeti artırmasının yinelemeye duyarlı, yükteki mutlak rozet değerinin sıraya duyarlı olmasının ölçümü ve kursun on dokuz dersinin kapanış tablosu.

Önceki derste aynı kaydı iki aygıtın yazması sırayı uygulamanın elinden aldı. Uzak bildirimde sıra ve sayı bir adım daha uzaklaşır: iletiyi sunucu hazırlar, platformun bildirim servisi taşır ve uygulama onu ancak teslim edilince görür. Bu servis teslimi **garanti etmez**; bir bildirim gecikebilir, hiç gelmeyebilir ya da iki kez gelebilir. Kursun sorusu bu uçta da aynıdır: **bu işlem yinelenirse ya da sırası değişirse sonuç değişir mi?** Bu ders son dersimizdir; ölçümden sonra kursun on dokuz dersi tek tabloda toplanır.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

`bir` işlemin başlangıç durumuna bir kez, `iki` iki kez uygulanmasıdır; `ab` önce `a` sonra `b`, `ba` ise tersi sıradır. Aşağıdaki model uygulamanın bildirimle değişen durumunu kurar: bir rozet sayısı, bir aygıt belirteci ve işlenmiş bildirim kimlikleri.

#### İzin ve Kayıt Akışı

Uygulama bildirim alabilmek için iki adım atar. Önce kullanıcıdan izin ister, sonra platformdan bir aygıt belirteci ister. Belirteç, bu uygulamanın bu aygıttaki adresidir ve sunucuya iletilir; sunucu bildirimi bu adrese gönderir. Parça KAYNAK'tır, çünkü gerçek bir izin ekranı ve bir ağ bağlantısı gerektirir.

```swift
import UIKit
import UserNotifications

let center = UNUserNotificationCenter.current()
let granted = try await center.requestAuthorization(options: [.alert, .badge, .sound])

if granted {
    await MainActor.run {
        UIApplication.shared.registerForRemoteNotifications()
    }
}

func application(_ application: UIApplication,
                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let hex = deviceToken.map { String(format: "%02x", $0) }.joined()
    print(hex)
}
```

Kullanıcı izni reddederse kayıt çağrısı yapılmaz ve bildirim gelmez; reddetmenin ardından ayarlardan izin açılabilir, bu yüzden uygulama izin durumunu her açılışta yeniden okur. Belirteç sabit değildir: platform onu yenileyebilir, bu yüzden uygulama her açılışta kayıt çağrısını yeniler ve gelen değeri sunucuya yeniden bildirir. Bu yinelemenin sonucu, bu dersin ölçtüğü üç işlemden biridir.

#### Bildirimin Modeli

Model üç durumu tutar. Rozet uygulamanın simgesindeki sayıdır. Belirteç, son kaydedilen aygıt adresidir. İşlenenler, uygulamanın daha önce işlediği bildirim kimlikleridir ve yalnız kimlikle eleme yapan işlemde dolar. Basımda işlenenler boşsa gösterilmez.

```swift
// PushModel.swift — rozet, belirtec ve islenen bildirimlerin saf Swift modeli
struct PushState: Equatable, CustomStringConvertible {
    private(set) var badgeCount: Int
    private(set) var token: String
    private(set) var handled: Set<String>

    init(badgeCount: Int, token: String = "-", handled: Set<String> = []) {
        self.badgeCount = badgeCount
        self.token = token
        self.handled = handled
    }

    var description: String {
        let base = "rozet=\(badgeCount) belirtec=\(token)"
        return handled.isEmpty ? base : base + " islenen=" + handled.sorted().joined(separator: ",")
    }

    func incrementing() -> PushState {
        PushState(badgeCount: badgeCount + 1, token: token, handled: handled)
    }

    func setting(badgeCount value: Int) -> PushState {
        PushState(badgeCount: value, token: token, handled: handled)
    }

    func registering(token value: String) -> PushState {
        PushState(badgeCount: badgeCount, token: value, handled: handled)
    }

    func incrementing(onceFor id: String) -> PushState {
        guard !handled.contains(id) else { return self }
        return PushState(badgeCount: badgeCount + 1, token: token, handled: handled.union([id]))
    }
}

func incrementingBadge() -> (PushState) -> PushState {
    { $0.incrementing() }
}

func settingBadge(_ value: Int) -> (PushState) -> PushState {
    { $0.setting(badgeCount: value) }
}

func registeringToken(_ value: String) -> (PushState) -> PushState {
    { $0.registering(token: value) }
}

func incrementingOnce(id: String) -> (PushState) -> PushState {
    { $0.incrementing(onceFor: id) }
}
```

**KL21.** Model, her bildirimi tam bir kez uygulanan bir işlem olarak kurar. Platformun aynı bildirimi sıfır, bir ya da birkaç kez teslim edebildiği ve bunun sıklığı ölçülmedi; ölçülen şey, teslim iki kez olursa ne olacağıdır.

**KL22.** Mutlak rozet değeri sunucuda hesaplanır ve yükte taşınır; model değerin doğru hesaplandığını varsayar. Sunucunun bu sayıyı nasıl bulduğu ölçülmedi.

**KL23.** Bildirim kimliği benzersiz ve sabit varsayılır. Gerçek bir hizmette kimliğin nasıl üretildiği ve yeniden denemede korunup korunmadığı ölçülmedi.

#### Üç İşlemin Ölçümü

Üç işlem kurulur. Birincisi her teslimde rozeti yerelde bir artırmaktır. İkincisi yükte mutlak rozet değerini taşımaktır: sunucu `aps` içindeki `badge` alanına son sayıyı yazar ve uygulama onu olduğu gibi kullanır. Üçüncüsü aynı belirteci yeniden kaydetmektir. Rozet üçten başlar.

```swift
// main.swift — rozet ve belirtec islemleri
let start = PushState(badgeCount: 3)
let local = replay("rozet-yerel-artir", from: start, incrementingBadge(), then: incrementingBadge()).report()
let absolute = replay("rozet-mutlak-deger", from: start, settingBadge(4), then: settingBadge(7)).report()
let register = replay("belirtec-yeniden-kaydet", from: start, registeringToken("t1"), then: registeringToken("t1")).report()

countSensitivities([local, absolute, register])
```

```
rozet-yerel-artir | bir=rozet=4 belirtec=- iki=rozet=5 belirtec=- ab=rozet=5 belirtec=- ba=rozet=5 belirtec=- -> yinelemeye-duyarli
rozet-mutlak-deger | bir=rozet=4 belirtec=- iki=rozet=4 belirtec=- ab=rozet=7 belirtec=- ba=rozet=4 belirtec=- -> siraya-duyarli
belirtec-yeniden-kaydet | bir=rozet=3 belirtec=t1 iki=rozet=3 belirtec=t1 ab=rozet=3 belirtec=t1 ba=rozet=3 belirtec=t1 -> saglam
saglam: 1
yinelemeye-duyarli: 1
siraya-duyarli: 1
cift-duyarli: 0
```

İlk satır ağ istemcisi dersindeki `rozet-artir` işleminin aynısıdır: bir teslim rozeti dörde, iki teslim beşe çıkarır. Bildirim iki kez teslim edilirse kullanıcı gerçekte olmayan bir okunmamış ileti görür. İşlem **yinelemeye duyarlıdır**.

İkinci satırda yük sayıyı taşır, bu yüzden aynı bildirimin ikinci teslimi sonucu değiştirmez: `bir` ile `iki` aynıdır. Ama iki farklı bildirim farklı sayılar taşır ve rozet sonra gelenin değerinde kalır: `ab` yedi, `ba` dört. Mutlak değer yinelemeyi çözdü, sırayı çözmedi; işlem **sıraya duyarlıdır**. Bir önceki dersin son yazan kazanır kuralıyla aynı yapıdır.

Üçüncü satırda aynı değeri yeniden kaydetmek durumu değiştirmez; sıra da anlamsızdır, çünkü iki işlem aynıdır. İşlem **sağlamdır**. Belirteç yenilemenin güvenli olmasının nedeni budur: sunucu aynı adresi iki kez almış gibi davranır.

Sayım bir sağlam, bir yinelemeye duyarlı, bir sıraya duyarlıdır.

#### Teslim Garanti Değildir

Ölçüm, bildirimin iki kez teslim edildiği durumu işlemin iki uygulaması olarak kurar. Gerçekte teslimin kaç kez ve hangi sırayla olacağı platformun elindedir. Bildirim hiç gelmeyebilir: aygıt kapalıdır, ağa erişemez, kullanıcı izni geri almıştır ya da platform bildirimi bırakmıştır. İki bildirim gönderildiğinde ikincisi önce gelebilir. Bu yüzden bildirim, uygulamanın tek doğruluk kaynağı olamaz.

Bundan iki sonuç çıkar. Birincisi, bildirimin taşıdığı bilgi uygulama açıldığında sunucudan yeniden okunabilmelidir; bildirim bir ipucudur, kayıt değildir. İkincisi, bildirimin yaptığı yerel değişiklik ya yinelemeye karşı korunmalı ya da hiç yapılmamalıdır. Rozeti yerelde artırmak ilk yolu gerektirir, mutlak değer ikinci yolun bir biçimidir. Bu seçim bir kural değil bir tasarım kararıdır, ve kural onu bizim yerimize vermez.

#### Sınırlayıcı Ölçüm

Kural eşitsizliği görür; teslimin kaç kez olduğunu görmez. Aynı rozet işlemini bu kez bildirim kimliğiyle ölçelim: uygulama bir kimliği yalnız bir kez işler. Ayrıca iki bildirimin aynı mutlak değeri taşıdığı durumu deneyelim.

```swift
// main.swift — kimlikle eleme ve esit mutlak deger
let start = PushState(badgeCount: 3)
let sameNotification = replay("kimlikli-ayni-bildirim", from: start, incrementingOnce(id: "n1"), then: incrementingOnce(id: "n1")).report()
let twoNotifications = replay("kimlikli-iki-bildirim", from: start, incrementingOnce(id: "n1"), then: incrementingOnce(id: "n2")).report()
let equalAbsolute = replay("mutlak-esit-deger", from: start, settingBadge(4), then: settingBadge(4)).report()

countSensitivities([sameNotification, twoNotifications, equalAbsolute])
```

```
kimlikli-ayni-bildirim | bir=rozet=4 belirtec=- islenen=n1 iki=rozet=4 belirtec=- islenen=n1 ab=rozet=4 belirtec=- islenen=n1 ba=rozet=4 belirtec=- islenen=n1 -> saglam
kimlikli-iki-bildirim | bir=rozet=4 belirtec=- islenen=n1 iki=rozet=4 belirtec=- islenen=n1 ab=rozet=5 belirtec=- islenen=n1,n2 ba=rozet=5 belirtec=- islenen=n1,n2 -> saglam
mutlak-esit-deger | bir=rozet=4 belirtec=- iki=rozet=4 belirtec=- ab=rozet=4 belirtec=- ba=rozet=4 belirtec=- -> saglam
saglam: 3
yinelemeye-duyarli: 0
siraya-duyarli: 0
cift-duyarli: 0
```

Üç işlem de sağlam çıktı. Kimlikle eleme, aynı bildirimin ikinci teslimini yok saydı; iki farklı bildirimde ise artış toplamı sıradan bağımsızdır, çünkü işlenenler küme olarak tutulur ve sıralı basılır. Eşit mutlak değer de sıra farkını gizledi: iki bildirim aynı sayıyı taşıyorsa hangisinin önce geldiğini kimse göremez.

Burada kural iki şeyi görmez. Birincisi, bildirimin gerçekte iki kez teslim edilip edilmediğidir: elemenin işe yarayıp yaramadığını ölçüm göstermez, yalnız teslim iki kez olursa sonucun aynı kalacağını gösterir. İkincisi, eşit değerin tesadüf mü tasarım mı olduğudur. Ölçülen sınıf bir kurulumun sonucudur, işlemin doğasının değil.

#### Saklama Seçimi

Kalıcılık konusunun altı dersi bir seçimi destekler: veri hangi saklama yerine girer. Bu seçim bir sayıya dökülmez; duyarlılık seçimin tek ölçütü değildir. Ölçütleri verinin biçimi belirler.

- Küçük, tek değerli ve gizli olmayan ayarlar tercih deposuna girer.
- Parola, belirteç ve anahtar gibi gizli değerler anahtarlığa girer.
- Uygulamanın kendi ürettiği belgeler, medya ve önbellekler dosya sistemine girer; hangi dizinin yedeklendiği ayrıca düşünülür.
- Sorgulanan, ilişkili ve çok sayıda kayıt yerel veritabanına ya da nesne kalıcılık çerçevesine girer.
- Birden çok aygıtta aynı olması gereken kayıt bulut eşitlemeye girer ve çakışma kuralı baştan yazılır.

Bildirim bu listede saklama yeri değildir; durumu değiştiren bir olaydır. Bildirimin getirdiği her değişiklik yukarıdaki yerlerden birine yazılır ve o yerin sınıfını devralır.

#### Özet

- Platformun bildirim servisi teslimi garanti etmez; bir bildirim gecikebilir, hiç gelmeyebilir ya da iki kez gelebilir.
- Rozeti her teslimde yerelde artırmak yinelemeye duyarlıdır: bir teslim rozeti dörde, iki teslim beşe çıkarır.
- Yükte mutlak rozet değeri taşımak yinelemeyi çözer, sırayı çözmez: `ab` yedi, `ba` dört verir; sınıf sıraya duyarlıdır.
- Aynı belirteci yeniden kaydetmek sağlamdır; kimlikle eleme de rozet artışını sağlam yapar.
- Gerçek teslim sayısı, sırası ve kimliğin üretimi ölçülmedi; KL21–KL23 olarak kaldı.
- Bu dersin duyarlılığı: **yinelemeye duyarlı**.

#### Kurs Kapanışı

Kursun on dokuz dersi aynı soruyla ilerledi: işlem yinelenirse ya da sırası değişirse sonuç değişir mi? Tablo her dersin ölçtüğü işlemi, dersin Özet bölümünde yazılı duyarlılığı ve sınırlayıcı ölçümü tek satırda verir.

| Ders | İşlem | Duyarlılık | Sınırlayıcı ölçüm |
|---|---|---|---|
| Ağ İstemcisi | önbelleğe yazma, anahtarlı gönderim, yeniden deneme, rozet artırma, tercih yazma, günlüğe ekleme | dördü (1/2/2/1) | başlangıç durumu: sıfırdan sağlam, birden yinelemeye duyarlı |
| JSON Kodlama ve Çözme | çözülen kaydı listeye ekleme, kimliğe göre yazma, kodla-çöz | sıraya duyarlı | ham metin ile çözülmüş değer eşitliği |
| Hata ve Yeniden Deneme | anahtarsız ve anahtarlı sipariş, bütçe harcama | yinelemeye duyarlı | yanıt kaybı ile ulaşmayan istek ayırt edilemez |
| Kimlik Doğrulama | son yazan kazanır, büyük damga kazanır | sıraya duyarlı | yenileme belirtecinin ikinci kullanımı reddedilir |
| Görsel ve Veri Önbelleği | aynı anahtar, iki anahtar, yaz-oku | sağlam | boş ve dolu önbellek |
| Arka Plan Aktarımı | denetlemeden kuyruğa alma, tanımlayıcıya göre kayıt | yinelemeye duyarlı | bitme ve kuyruğa alma sırasını sistem belirler |
| Eşzamansız Fonksiyonlar | stok ayırma, stok okuma, ayrı raflar | sıraya duyarlı | kalan stok ile kabul defteri |
| Görevler ve Görev Grupları | grup kümesi, geliş sırası, grup toplamı | sağlam (kümede), sıraya duyarlı (dizide) | liste ve sayı görünümü |
| Aktörler | koltuk talebi, bilet satışı, bırakma | sıraya duyarlı (koltuk), yinelemeye duyarlı (sayaç) | duraklama noktası ölçülmedi |
| Ana İş Parçacığı Yalıtımı | sürümsüz ve sürümlü yazım, bayrak, sayaç | sıraya duyarlı (sürümsüz), sağlam (sürümlü) | numara yanıt gelişinde verilirse çift duyarlı |
| Kuyruk Tabanlı Eşzamanlılık | sona ekleme, değer atama, benzersiz ekleme | çift duyarlı (ekleme), sıraya duyarlı (atama) | sıralı liste ve satır sayısı |
| Tepkisel Akışlar | abonelik, tekrar düşürme, toplama, son değer | yinelemeye duyarlı (paylaşımsız), sıraya duyarlı (farklı değer), sağlam (paylaşımlı) | eşit ve farklı değer çifti |
| Basit Tercih Saklama | iki ekran aynı anahtar, ayrı anahtar, açılış sayacı | sıraya duyarlı | üç varsayılan değer |
| Anahtarlık | ekleme, güncelle yoksa ekle | yinelemeye duyarlı | durum kodu karşılaştırmaya girmezse sağlam |
| Dosya Sistemi | sona ekleme, atomik yazma, dizin oluşturma | çift duyarlı | dosya yokken sağlam, varken yinelemeye duyarlı |
| Yerel Veritabanı | varsa güncelle yoksa ekle, otomatik anahtar | sağlam | anahtar sırasıyla okuma sağlam, ekleme sırasıyla sıraya duyarlı |
| Nesne Kalıcılık Çerçeveleri | nesne oluşturma, benzersiz öznitelik | yinelemeye duyarlı | kısıt kapalıyken yinelemeye duyarlı |
| Bulut Eşitleme | iki aygıtın yazması, küme birleşimi | sıraya duyarlı | geliş sırası ya da damga kuralı |
| Uzak Bildirimler | rozeti yerelde artırma, yükte mutlak rozet, belirteci yeniden kaydetme | yinelemeye duyarlı | kimlikle eleme sağlam; teslim sayısı ve sırası ölçülmedi |

Açılış dışındaki on sekiz ders, bu ders dahil, şöyle dağılır: sağlam 3, yinelemeye duyarlı 6, sıraya duyarlı 7, çift duyarlı 2. Karışık sınıflı derslerde Özet'te ilk yazılan sınıf sayıldı.

Açılış dersinin çekirdeği ayrı bir sayımdır: altı işlem dört sınıfa 1, 2, 2 ve 1 olarak dağıldı. İki dağılım aynı şey değildir. Çekirdek altı işlemi sayar; yukarıdaki dağılım açılış dışındaki on sekiz dersin duyarlılığını sayar.

Tablodan üç gözlem çıkar. Aynı işlem başlangıç durumuna, karşılaştırılan duruma ve kurala göre başka sınıfa düşer; hiçbir sınıf bir işlemin değişmez özelliği değildir. Yinelemeyi çözen önlem, etkisizlik ya da kimlikle eleme, sırayı çözmez; sırayı çözen önlem, damga ya da sürüm, yinelemeyi başka bir yerden çözmeli. Ve ölçüm her zaman bir modelin içindedir: platformun gerçek teslimi, zamanlaması ve politikası sınırlayıcı ölçüm sütununda ölçülmedi diye kayıtlıdır.

Sıradaki kurs, M30/K06, mimari, test ve yayınlama konularını ele alır: bir iOS uygulamasının katmanlara nasıl ayrıldığı, nasıl sınandığı ve mağazaya nasıl çıkarıldığı.

