---
title: 'iOS Platformu ve Araçlar'
source: 'https://academia.sh/tr/kurslar/ios-platformu'
language: tr
updated: '2026-10-21T05:03:10+00:00'
license: 'CC BY-SA 4.0'
lessons: 12
outcomes:
    - 'Platform katmanlarını ve çerçevelerin bu katmanlardaki yerini açıklar'
    - 'Proje yapısını ve derleme yapılandırmasını yönetir'
    - 'Uygulama yaşam döngüsünü ve arayüz kılavuzlarını uygular'
    - 'İki tanımın buluşmasında birinin mi öncelik kazandığını, kesişimin mi birleşimin mi alındığını ya da buluşmanın reddedildiğini ayırt eder'
    - 'Platformun çözüm kuralını küçük bir modelle kurar ve modelin platformun kendisi olmadığını tanır'
---

# iOS Platformu ve Araçlar

Bir iOS uygulamasında aynı şey çoğu zaman iki yerde tanımlanır: proje ve hedef düzeyinde bir derleme ayarı, uygulamanın desteklediği ve aygıtın sunduğu ekran yönleri, iki paketin aynı bağımlılık için istediği sürüm aralıkları, iki modülün dışa açtığı aynı ad. Kurs her düzeneği tek bir soruyla ölçer — bu iki tanım buluşunca nasıl çözülüyor? Platform katmanları, sistem ve alan çerçeveleri, uygulama yaşam döngüsü, arayüz kılavuzları, sürüm uyumluluğu, proje yapısı, paket yöneticisi, simülatör, hata ayıklama ve Objective-C ile birlikte çalışma aynı çerçeveden geçirilir.

## Platform Yapısı

Çekirdek işletim sisteminden arayüz katmanına platform katmanları, sistem çerçeveleri ve kütüphaneler, konum, sağlık, medya, grafik, artırılmış gerçeklik ve aygıt üstü model çerçeveleri, uygulama yaşam döngüsü ve arka plan, arayüz tasarım kılavuzları, sürüm ve uyumluluk kararı.

### Platform Katmanları

Source: https://academia.sh/tr/kurslar/ios-platformu/platform-katmanlari

Sekiz tanım buluşmasının dört çözüme ayrıldığının ölçümü; platform katmanlarının çekirdek işletim sisteminden arayüz katmanına sıralanışı.

Önceki kursta Swift dilini bir an ölçüsüyle okuduk: bir ifade hemen mi, ertelenerek mi, hiç mi değerlendiriliyor. Bu kurs dilden platforma geçer. Platformda aynı şey çoğu zaman iki yerde tanımlanır: bir derleme ayarı hem projede hem hedefte durur, bir uygulamanın ekran yönleri hem uygulamada hem aygıtta, bir paketin sürüm aralığı hem kendisinde hem onu isteyen paketlerde. Bu iki tanım buluştuğunda sistemin ne yaptığı kursun ölçü sorusudur: **bu iki tanım buluşunca nasıl çözülüyor?** Üst ad **çözüm**.

Dört şekil beklenir ve ilk bakışta hepsi aynı görünür. Bazen iki tanımdan biri kazanır (öncelik), bazen yalnız ikisinde de olanlar kalır (kesişim), bazen ikisinden birinde olan her şey toplanır (birleşim), bazen de sonuç çıkmaz (ret). Bu derste sekiz küçük buluşma kurar ve hangisinin hangi çözüme düştüğünü sayarız. Platformun katmanlarını da bu sayımın ardından, çözümlerin nerede yaşadığını göstermek için sıralarız.

#### Çekirdek

Her ders aynı yardımcıyı kullanır: iki tanımı, bu tanımların buluşmasından çıkan sonucu ve sonucun hangi çözüme düştüğünü tutan bir tip. `Meeting` sonucu değiştirmez, yalnızca sınıflandırır. Sonuç her zaman modelden gelir; yani sözlük birleştirme, süzme, ad arama ve aralık denetimi gibi gerçek işlemlerin çıktısıdır, elle yazılmış bir küme değildir. Sınıflandırma sırası şöyledir: iki küme de boş değilse ve biri ötekinin alt kümesi değilse ölçüm geçerlidir, aksi hâlde `olcum-disi` yazılır; sonuç yoksa ya da boşsa `ret`; sonuç birleşime eşitse `birlesim`; kesişime eşitse `kesisim`; kümelerden birine eşitse `oncelik`; hiçbirine uymuyorsa `kural-disi`. Küme değerleri sıralanarak basılır, çünkü `Set`'in yineleme sırası koşumdan koşuma değişebilir.

```swift
// Meeting.swift — iki tanim bulusunca nasil cozuldugunu siniflandirir
struct Meeting {
    let label: String
    let first: Set<String>
    let second: Set<String>
    let result: Set<String>?

    init(_ label: String, first: Set<String>, second: Set<String>, result: Set<String>?) {
        self.label = label
        self.first = first
        self.second = second
        self.result = result
    }

    var resolution: String {
        if first.isEmpty || second.isEmpty
            || first.isSubset(of: second) || second.isSubset(of: first) {
            return "olcum-disi"
        }
        guard let result, !result.isEmpty else { return "ret" }
        if result == first.union(second) { return "birlesim" }
        if result == first.intersection(second) { return "kesisim" }
        if result == first || result == second { return "oncelik" }
        return "kural-disi"
    }

    static func list(_ values: Set<String>) -> String {
        values.isEmpty ? "bos" : values.sorted().joined(separator: ",")
    }

    func report() {
        let outcome = result.map(Meeting.list) ?? "yok"
        print("\(label): ilk \(Meeting.list(first)) | ikinci \(Meeting.list(second)) | sonuc \(outcome) -> \(resolution)")
    }
}

func summarize(_ meetings: [Meeting]) {
    for resolution in ["oncelik", "kesisim", "birlesim", "ret"] {
        let count = meetings.filter { $0.resolution == resolution }.count
        print("\(resolution): \(count)")
    }
}
```

#### Sekiz Buluşma

Aşağıdaki ölçüm sekiz buluşmayı kurar. Her birinin iki tarafı ve sonucu aynı biçimde üretilir: derleme ayarında hedef düzeyi proje düzeyinin üstüne yazılır, ad aramasında yerel tanım önce aranır ve iki içe alınan modülde aynı ad belirsiz kalır, ekran yönünde uygulamanın desteklediği ile aygıtın sunduğu süzülür, sürüm aralığında iki paketin istediği aralık kesişir, miras alınan yollarda hedef `$(inherited)` ile proje değerini açar, özellik listesinde şablon dosyası ile ayarlardan üretilen anahtarlar birleşir.

```swift
// main.swift — sekiz bulusma, dort cozum
func parts(_ version: String) -> [Int] {
    version.split(separator: ".").map { Int($0)! }
}

func versions(in published: [String], from lower: String, below upper: String) -> Set<String> {
    Set(published.filter {
        !parts($0).lexicographicallyPrecedes(parts(lower))
            && parts($0).lexicographicallyPrecedes(parts(upper))
    })
}

func lookup(_ name: String, local: [String: String], imported: [[String: String]]) -> Set<String>? {
    if let found = local[name] { return [found] }
    let matches = imported.compactMap { $0[name] }
    return matches.count == 1 ? Set(matches) : nil
}

func expand(_ values: [String], inherited: [String]) -> [String] {
    values.flatMap { $0 == "$(inherited)" ? inherited : [$0] }
}

// Derleme ayari: hedef duzeyi proje duzeyinin ustune yazar
let projectLevel = ["SWIFT_VERSION": "5", "TARGETED_DEVICE_FAMILY": "1"]
let targetLevel = ["SWIFT_VERSION": "6"]
let effective = projectLevel.merging(targetLevel) { _, fromTarget in fromTarget }

// Ad arama: yerel tanim once aranir; iki modulde ayni ad belirsizdir
let kitNames = ["format": "Kit.format", "Logger": "Kit.Logger"]
let toolsNames = ["Logger": "Tools.Logger"]
let localNames = ["format": "yerel.format"]

// Ekran yonu: uygulamanin destekledigi ile aygitin sundugu
let appOrientations: Set = ["dikey", "ters-dikey"]
let deviceOrientations: Set = ["dikey", "yatay-sol", "yatay-sag"]

// Surum araliklari: iki paketin ayni bagimlilik icin istedigi
let published = ["1.2.0", "1.3.0", "1.4.0", "2.0.0", "2.1.0"]
let wideFirst = versions(in: published, from: "1.2.0", below: "2.0.0")
let wideSecond = versions(in: published, from: "1.3.0", below: "3.0.0")
let narrowFirst = versions(in: published, from: "1.2.0", below: "1.4.0")
let narrowSecond = versions(in: published, from: "2.0.0", below: "3.0.0")

// Miras alinan yollar: hedef `$(inherited)` ile proje degerini acar
let projectPaths = ["Ortak/Basliklar"]
let targetPaths = ["$(inherited)", "Hedef/Basliklar"]

// Info.plist: sablon dosyasi ile ayarlardan uretilen anahtarlar
let templateKeys = ["CFBundleName": "Ornek", "UILaunchScreen": "-"]
let generatedKeys = ["CFBundleName": "Ornek", "NSCameraUsageDescription": "Kamera"]

let meetings = [
    Meeting("derleme-ayari",
            first: [projectLevel["SWIFT_VERSION"]!],
            second: [targetLevel["SWIFT_VERSION"]!],
            result: [effective["SWIFT_VERSION"]!]),
    Meeting("yerel-golgeleme",
            first: [kitNames["format"]!],
            second: [localNames["format"]!],
            result: lookup("format", local: localNames, imported: [kitNames, toolsNames])),
    Meeting("ekran-yonu",
            first: appOrientations,
            second: deviceOrientations,
            result: appOrientations.filter { deviceOrientations.contains($0) }),
    Meeting("surum-araligi",
            first: wideFirst,
            second: wideSecond,
            result: wideFirst.intersection(wideSecond)),
    Meeting("miras-yollar",
            first: Set(projectPaths),
            second: Set(targetPaths.filter { $0 != "$(inherited)" }),
            result: Set(expand(targetPaths, inherited: projectPaths))),
    Meeting("plist-anahtarlari",
            first: Set(templateKeys.keys),
            second: Set(generatedKeys.keys),
            result: Set(templateKeys.merging(generatedKeys) { current, _ in current }.keys)),
    Meeting("cift-ad",
            first: [kitNames["Logger"]!],
            second: [toolsNames["Logger"]!],
            result: lookup("Logger", local: localNames, imported: [kitNames, toolsNames])),
    Meeting("ayrik-aralik",
            first: narrowFirst,
            second: narrowSecond,
            result: narrowFirst.intersection(narrowSecond)),
]

for meeting in meetings {
    meeting.report()
}
summarize(meetings)
```

```
derleme-ayari: ilk 5 | ikinci 6 | sonuc 6 -> oncelik
yerel-golgeleme: ilk Kit.format | ikinci yerel.format | sonuc yerel.format -> oncelik
ekran-yonu: ilk dikey,ters-dikey | ikinci dikey,yatay-sag,yatay-sol | sonuc dikey -> kesisim
surum-araligi: ilk 1.2.0,1.3.0,1.4.0 | ikinci 1.3.0,1.4.0,2.0.0,2.1.0 | sonuc 1.3.0,1.4.0 -> kesisim
miras-yollar: ilk Ortak/Basliklar | ikinci Hedef/Basliklar | sonuc Hedef/Basliklar,Ortak/Basliklar -> birlesim
plist-anahtarlari: ilk CFBundleName,UILaunchScreen | ikinci CFBundleName,NSCameraUsageDescription | sonuc CFBundleName,NSCameraUsageDescription,UILaunchScreen -> birlesim
cift-ad: ilk Kit.Logger | ikinci Tools.Logger | sonuc yok -> ret
ayrik-aralik: ilk 1.2.0,1.3.0 | ikinci 2.0.0,2.1.0 | sonuc bos -> ret
oncelik: 2
kesisim: 2
birlesim: 2
ret: 2
```

Çıktının son dört satırı **2/2/2/2** sayımını verir: iki öncelik, iki kesişim, iki birleşim, iki ret. `derleme-ayari` ve `yerel-golgeleme` öncelik: ikinci tarafın değeri sonuca olduğu gibi geçti. `ekran-yonu` ve `surum-araligi` kesişim: sonuçta yalnız iki tarafın da içerdiği değerler kaldı. `miras-yollar` ve `plist-anahtarlari` birleşim: sonuç iki kümenin tümünü taşıyor. `cift-ad` ve `ayrik-aralik` ret: birinde sonuç hiç oluşmadı (`yok`), ötekinde boş küme oluştu (`bos`), ve kural ikisini aynı çözüme koyar.

Dağılımın eşit çıkması kuralın değil kurulan buluşmaların bir sonucudur: sekiz örneği biz seçtik ve dört çözümün her birine ikişer tanesi düştü. Bu yüzden dağılımdan bir sonuç çıkarmayız; ölçüm yalnız dört çözümün de gerçekten var olduğunu ve her birinin farklı bir işlemden geldiğini gösterir.

**PY1.** Bu ölçüm yukarıdaki sekiz buluşma ve yazılan modelin kuralları için. Gerçek bir geliştirme ortamının derleme ayarı çözümlemesinin ya da gerçek bir paket yöneticisinin sürüm çözümlemesinin bu kuralları aynen uyguladığı ölçülmedi; model, platformun bu kuralını kurar ve sınıflandırır.

#### Katmanlar ve Çözümler

Platform aşağıdan yukarıya dört katman olarak okunur ve her katman altındaki katmanların üzerine kurulur. En altta **çekirdek işletim sistemi** katmanı bulunur: bellek, süreç, ağ ve aygıt sürücüleri buradadır. Onun üstünde **çekirdek hizmetler** katmanı gelir: koleksiyonlar, tarih ve dosya işleri, ağ istemcileri ve günlük kaydı gibi görünür olmayan, her uygulamanın gereksindiği hizmetler. Üçüncü katman **medya** katmanıdır: grafik, ses ve görüntü. En üstte **arayüz** katmanı vardır: görünümler, olay işleme ve uygulamanın ekranda yaşayan her şeyi. Bir uygulama arayüz katmanından konuşur, ama altındaki üç katmanın tümünden de hizmet alır.

Çözümler bu katmanlara eşit dağılmaz; aynı tanım çifti farklı katmanlarda farklı biçimlerde ortaya çıkar. Ad aramasının öncelik ve ret çözümleri dilin ve modüllerin işidir; ekran yönü arayüz katmanına ve aygıta, dolayısıyla alttaki donanıma bakar; derleme ayarı, miras alınan yollar ve özellik listesi ise uygulamanın nasıl kurulduğuna ait olduğundan hiçbir çalışma zamanı katmanında yaşamaz, geliştirme ortamının proje ve hedef tanımlarında yaşar. Sürüm aralığı ise paketin bildirimine aittir ve derleme sırasında çözülür.

Bu sıralamanın pratik bir sonucu vardır: bir tanımın hangi katmanda durduğunu bilmek, o tanımın çözümünü kimin yaptığını, yani derleyicinin mi, paket yöneticisinin mi, sistemin mi karar verdiğini söyler. Sonraki derslerde her buluşmayı bu soruyla karşılayacağız.

**PY2.** Katman sayısı ve adları burada öğretim amacıyla dörde indirilmiş bir sıralamadır; bir çerçevenin tam olarak hangi katmana ait olduğu, çerçevenin bu derste ölçülmeyen iç yapısına bağlıdır ve ölçülmedi.

#### Çözümü Kim Yapıyor

Sekiz buluşmayı çözümü yapan tarafa göre gruplamak, katman sorusunu somutlaştırır. Ad aramasını derleyici yapar: kaynak dosya derlenirken hangi tanımın görüneceğine o karar verir ve karar çalışma zamanına hiç taşınmaz. Derleme ayarını, miras alınan yolları ve özellik listesini derleme sistemi çözer; bu üçü uygulamanın içinde değil, uygulamayı üreten sürecin içinde bulunur. Sürüm aralığını paket yöneticisi çözer ve sonucu bir kilit dosyasına yazar. Ekran yönünü ise çalışma zamanında sistem çözer: uygulama ne desteklediğini bildirir, aygıt ne sunduğunu bilir ve sonuç ancak ikisi karşılaşınca ortaya çıkar.

Bu ayrım iki şeyi önceden söyler. Birincisi, çözümü yapan taraf değişince hata da başka yerde görünür: derleyicinin verdiği ret bir derleme hatasıdır, sistemin verdiği ret ise uygulama çalışırken fark edilir. İkincisi, aynı tanım çifti iki farklı yerde farklı çözülebilir; bir ayarın projede ve hedefte nasıl buluştuğunu bilmek, bir bağımlılığın iki paketten nasıl istendiğini söylemez.

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

Dört çözümün hepsi, iki tanımdan biri ötekini içerdiğinde ayrışmaz: iç içe iki küme birleşimde dıştakine, kesişimde içtekine eşittir, öncelikte ikisinden birine eşittir; ret için ise çatışan bir eleman yoktur, çünkü içteki kümenin her elemanı dıştakinde de bulunur. Sonuçlar farklı olsa da bu durumda sınıflandırma bir şey söylemez. Aşağıdaki ölçüm aynı iç içe çiftle dört sonucu da kurar ve `Meeting`'in dördünü de ölçüm dışı bıraktığını gösterir.

```swift
// main.swift — ic ice iki tanim: dort sonuc da ayni sinifa duser
let outer: Set = ["Ortak/Basliklar", "Hedef/Basliklar"]
let inner: Set = ["Ortak/Basliklar"]

let nested = [
    Meeting("ic-ice-oncelik", first: inner, second: outer, result: inner),
    Meeting("ic-ice-kesisim", first: inner, second: outer, result: inner.intersection(outer)),
    Meeting("ic-ice-birlesim", first: inner, second: outer, result: inner.union(outer)),
    Meeting("ic-ice-ret", first: inner, second: outer, result: nil),
]

for meeting in nested {
    meeting.report()
}
summarize(nested)
```

```
ic-ice-oncelik: ilk Ortak/Basliklar | ikinci Hedef/Basliklar,Ortak/Basliklar | sonuc Ortak/Basliklar -> olcum-disi
ic-ice-kesisim: ilk Ortak/Basliklar | ikinci Hedef/Basliklar,Ortak/Basliklar | sonuc Ortak/Basliklar -> olcum-disi
ic-ice-birlesim: ilk Ortak/Basliklar | ikinci Hedef/Basliklar,Ortak/Basliklar | sonuc Hedef/Basliklar,Ortak/Basliklar -> olcum-disi
ic-ice-ret: ilk Ortak/Basliklar | ikinci Hedef/Basliklar,Ortak/Basliklar | sonuc yok -> olcum-disi
oncelik: 0
kesisim: 0
birlesim: 0
ret: 0
```

Dört satır da `olcum-disi`, dört sayaç da sıfır. İç içe bir çift hangi işlemle birleştirilirse birleştirilsin dört çözümden hiçbirine yazılamaz; yani çözümü ayıran şey işlem değil, iki tanımın gerçekten farklı şeyler söylemesidir. Bu, kursun kalanında bir uyarıya dönüşür: bir tanım ötekini zaten içeriyorsa buluşma kurulmaz, çünkü sonuç değişse bile sınıflandırma hangi kural seçilirse seçilsin aynı kalır.

**PY3.** Bu ölçüm yalnız `inner` kümesinin `outer` kümesinin alt kümesi olduğu çift için. Birbirine eşit iki kümenin ve kümelerden birinin boş olduğu durumun da `olcum-disi` verdiği `Meeting`'in koşullarından okunur ama ayrıca ölçülmedi.

#### Özet

- Bu iki tanım buluşunca sonuç dört çözümden birine düşer: öncelik, kesişim, birleşim ya da ret.
- Sekiz buluşmada çıktı 2 öncelik, 2 kesişim, 2 birleşim, 2 ret verdi; dağılım kurulan örneklerin sonucudur, kuralın değil.
- Sonucun hiç oluşmaması (`yok`) ile boş kümeye düşmesi (`bos`) aynı çözümdür: ret.
- Platform dört katmandan kurulur (çekirdek işletim sistemi, çekirdek hizmetler, medya, arayüz); bir tanımın katmanı, çözümü kimin yaptığını söyler.
- İç içe iki tanım hiçbir çözüme girmez: dört sonucun dördü de `olcum-disi` çıktı.
- Bu dersin çözümü: **çekirdeğin dördü (2/2/2/2)**.

#### Sonraki Adım

Sekiz buluşmanın ikisi ad aramasındı: yerel tanım içe alınanı gölgeledi, iki modülde aynı ad ise ret verdi. Bir sonraki derste çerçeveleri ve kütüphaneleri bu gözle okuruz: iki modül aynı adı açınca ne olur, ve birini niteleyince buluşma neden kalkar.

### Çerçeveler ve Kütüphaneler

Source: https://academia.sh/tr/kurslar/ios-platformu/cerceveler-ve-kutuphaneler

İki içe alınan modülün görünür adlarının birleştiğinin, aynı adın ret verdiğinin ve yerel tanımın içe alınanı gölgelediğinin ölçümü.

Önceki derste sekiz buluşmanın dördünü dört çözüme ayırdık; ad arama buluşmalarından biri öncelik, biri ret verdi. Bu ders o iki buluşmayı yakından okur. Bir uygulama birden çok modülü içe aldığında, her modülün açtığı adlar aynı kapsamda buluşur. Soru değişmez: **bu iki tanım buluşunca nasıl çözülüyor?** Bu derste bakılan çözüm **birleşim**.

Önce iki kavramı ayıralım. Kütüphane (library), uygulamanın çağırdığı hazır koddur: ne zaman çağıracağını uygulama seçer. Çerçeve (framework) ise kendi iskeletini kurar ve uygulamanın kodunu o iskeletin içinde çağırır; uygulama bir olayı işleyen bir parça olarak çerçevenin akışına girer. Platformda sistemle gelen çerçevelere sistem çerçevesi denir, uygulamanın bir bağımlılık olarak eklediği koda ise çoğunlukla paket ya da kütüphane. Her ikisi de bir **modül** olarak içe alınır ve ikisinin de açtığı adlar aynı kaynak dosyada yan yana durur. Ölçülen şey de bu yan yana gelmedir.

```swift
// Meeting.swift — iki tanim bulusunca nasil cozuldugunu siniflandirir
struct Meeting {
    let label: String
    let first: Set<String>
    let second: Set<String>
    let result: Set<String>?

    init(_ label: String, first: Set<String>, second: Set<String>, result: Set<String>?) {
        self.label = label
        self.first = first
        self.second = second
        self.result = result
    }

    var resolution: String {
        if first.isEmpty || second.isEmpty
            || first.isSubset(of: second) || second.isSubset(of: first) {
            return "olcum-disi"
        }
        guard let result, !result.isEmpty else { return "ret" }
        if result == first.union(second) { return "birlesim" }
        if result == first.intersection(second) { return "kesisim" }
        if result == first || result == second { return "oncelik" }
        return "kural-disi"
    }

    static func list(_ values: Set<String>) -> String {
        values.isEmpty ? "bos" : values.sorted().joined(separator: ",")
    }

    func report() {
        let outcome = result.map(Meeting.list) ?? "yok"
        print("\(label): ilk \(Meeting.list(first)) | ikinci \(Meeting.list(second)) | sonuc \(outcome) -> \(resolution)")
    }
}

func summarize(_ meetings: [Meeting]) {
    for resolution in ["oncelik", "kesisim", "birlesim", "ret"] {
        let count = meetings.filter { $0.resolution == resolution }.count
        print("\(resolution): \(count)")
    }
}
```

Bir modül, dışarıya açtığı adlardan oluşan bir yüzeydir. Aşağıdaki yardımcı üç şeyi modeller: bir modülün adı ve açtığı adlar, iki ya da daha çok modül içe alındığında görünen adların toplamı, ve bir adın hangi tanımdan geldiğini bulan arama. Arama önce yerel tanıma bakar; yerel tanım yoksa içe alınan modüller arasında bulur; bulunan tanım tek değilse sonuç yoktur. Bir de modül adıyla nitelenmiş kullanım vardır: `Graphics.Canvas` yazıldığında yalnız `Graphics` modülünün adı aday olur. Bu dosyanın çıktısı yoktur; yardımcı olarak durur ve sonraki ölçülen bloklarla birlikte derlenir.

```swift
// Modules.swift — modul yuzeyi, gorunen adlar ve ad arama
struct Module {
    let name: String
    let exports: [String]
}

func visibleNames(of modules: [Module]) -> Set<String> {
    Set(modules.flatMap { $0.exports })
}

func qualified(_ name: String, from module: Module) -> Set<String>? {
    module.exports.contains(name) ? [module.name + "." + name] : nil
}

func owners(of name: String, in modules: [Module], local: Set<String>) -> Set<String>? {
    if local.contains(name) { return ["yerel." + name] }
    let found = modules.filter { $0.exports.contains(name) }.map { $0.name + "." + name }
    return found.count == 1 ? Set(found) : nil
}
```

#### İki Modülü İçe Almak

İki modül içe alındığında ikisinin açtığı adlar aynı kapsamda görünür. Aşağıdaki ölçüm bir grafik modülü ile bir depolama modülü kurar ve ikisinin açtığı adlara `visibleNames` ile bakar. Ortak ad yoktur; bu yüzden hiçbir ad ötekinin yerine geçmez ve bir tanım ötekini ezmez.

```swift
// main.swift — iki modulu ice almak gorunen adlari toplar
let graphics = Module(name: "Graphics", exports: ["Canvas", "draw", "render"])
let storage = Module(name: "Storage", exports: ["load", "save"])

let imports = Meeting("gorunen-adlar",
                      first: Set(graphics.exports),
                      second: Set(storage.exports),
                      result: visibleNames(of: [graphics, storage]))
imports.report()
```

```
gorunen-adlar: ilk Canvas,draw,render | ikinci load,save | sonuc Canvas,draw,load,render,save -> birlesim
```

Sonuç iki yüzeyin tümüdür: beş adın hepsi görünür, hiçbiri düşmedi. Bu birleşimdir ve çerçeve eklemenin olağan sonucudur; yeni bir modül içe alındığında uygulamanın görebildiği ad sayısı azalmaz, artar. Bu aynı zamanda tehlikenin kaynağıdır: görünür ad sayısı büyüdükçe iki modülün aynı adı açma ihtimali de büyür.

**PY4.** Bu ölçüm iki modüllük ve ortak adı olmayan bir çift için. Bir modülün adlarının ötekinin alt kümesi olduğu durum ölçülmedi; o durumda buluşma `olcum-disi` olur.

#### Sistem Çerçevesi ve Paket

İçe alınan modülün nereden geldiği, adların nasıl buluştuğunu değiştirmez ama kimin kontrolünde olduğunu değiştirir. Sistem çerçeveleri aygıtta işletim sistemiyle birlikte bulunur; uygulama onları derleme sırasında bağlar ve çalışırken sistemin kendi kopyasını kullanır. Bu yüzden sürümleri uygulamanın değil, çalıştığı işletim sisteminin sürümüne bağlıdır ve bir adın var olup olmadığı o sürüme göre değişebilir; bu konuyu sürüm dersinde ölçeceğiz. Paket olarak eklenen kütüphane ise bir bağımlılık bildirimiyle istenir, derleme sırasında çözülür ve uygulamayla birlikte dağıtılır; sürümünü uygulama seçer.

Her `import` satırı tek bir dosya için geçerlidir. Bir dosyada görünen adlar, o dosyanın içe aldığı modüllerin yüzeylerinin birleşimi artı uygulamanın kendi tanımlarıdır. Başka bir dosya, aynı modülü içe almadıkça o modülün üst düzey adlarını göremez. Yani buluşma, bir uygulama için tek seferlik değil, dosya başına ayrı kurulan bir buluşmadır: iki modülün çakıştığı adı yalnız ikisini birden içe alan dosyalar yaşar.

#### Aynı Ad ve Yerel Tanım

İki modül aynı adı açtığında ve kullanım nitelenmediğinde, arama iki aday bulur. Hangisinin kastedildiğine dair bir bilgi yoktur ve derleyici birini seçmez: sonuç yoktur, çözüm ret. Aynı ad uygulamanın kendi kaynağında da tanımlıysa tablo değişir. Yerel tanım içe alınanın önüne geçer; yani içe alınan ad hâlâ vardır ama aramada görünmez, buna gölgeleme (shadowing) denir ve çözüm öncelik olur. Aşağıdaki ölçüm iki durumu da kurar: grafik ve grafik çizelgeleri modüllerinin ikisi de `Canvas` adını açar, ve uygulama kendi `render` adını tanımlar.

```swift
// main.swift — ayni ad belirsiz kalir, yerel tanim onu golgeler
let graphics = Module(name: "Graphics", exports: ["Canvas", "draw", "render"])
let charts = Module(name: "Charts", exports: ["Canvas", "plot"])
let local: Set = ["render"]

let cases = [
    Meeting("ayni-ad",
            first: qualified("Canvas", from: graphics)!,
            second: qualified("Canvas", from: charts)!,
            result: owners(of: "Canvas", in: [graphics, charts], local: [])),
    Meeting("yerel-tanim",
            first: qualified("render", from: graphics)!,
            second: owners(of: "render", in: [], local: local)!,
            result: owners(of: "render", in: [graphics, charts], local: local)),
]

for item in cases {
    item.report()
}
summarize(cases)
```

```
ayni-ad: ilk Graphics.Canvas | ikinci Charts.Canvas | sonuc yok -> ret
yerel-tanim: ilk Graphics.render | ikinci yerel.render | sonuc yerel.render -> oncelik
oncelik: 1
kesisim: 0
birlesim: 0
ret: 1
```

`ayni-ad` satırında sonuç `yok`: arama iki aday gördü ve hiçbirini seçmedi. `yerel-tanim` satırında sonuç ikinci taraftır, yani yerel tanım; içe alınan `Graphics.render` düştü. Aynı iki modülün yüzeyleri bir önceki ölçümde birleşim vermişti; buluşmanın çözümü yüzeylerin kendisinden değil, aranan adın kimin elinde olduğundan çıktı. Yüzeylerin toplamı bir ad aranmadığı sürece sorun çıkarmaz; arama başladığında aranan ad tek bir tanıma çözülmek zorundadır.

Aynı gölgelemeyi gerçek bir sistem modülüyle kurmak için ders dışı bir örnek şöyledir. Parça platform modülüne dayandığı için kaynak olarak durur ve çıktısı iddia edilmez.

```swift
// Kaynak parca: yerel Data tanimi Foundation.Data adini golgeler
import Foundation

struct Data {
    let bytes: [UInt8]
}

let own = Data(bytes: [1, 2])
let system: Foundation.Data = Foundation.Data()
```

Yerel `Data` yalın adı alır; sistem çerçevesindeki tip ancak modül adıyla nitelenerek erişilebilir kalır. Model, platformun bu kuralını kurar: ölçülen bloktaki arama, aynı önceliği `yerel-tanim` satırında yeniden üretir.

**PY5.** Bu ölçüm yalnız ad aramasının önceliği için ve iki içe alınan modülün aynı adı açtığı çift için. Bir adın hem bir tipte hem bir işlevde tanımlı olduğu durum, aşırı yükleme ile çözülen adlar ve üç modülün aynı adı açtığı durum ölçülmedi.

#### Modülün Açtığı Yüzey

Bir modülün dışarıya açtığı yüzey (public surface), onu içe alan kodun görebildiği bildirimlerin tümüdür: tipler, işlevler, sabitler, protokoller. Modülün içinde kalan bildirimler yüzeye girmez ve içe alan kod onlara ulaşamaz. Bu ayrım, kütüphane ile çerçeve arasındaki farkın pratikteki yüzüdür. Kütüphane genellikle küçük bir yüzey açar ve çağıran kod ne zaman hangi işlevi çağıracağını kendisi seçer. Çerçeve ise daha geniş bir yüzey açar; bu yüzeyin önemli bir bölümü, uygulamanın doldurması beklenen yerlerden oluşur: bir protokole uymak, bir sınıftan türemek, bir olay işleyicisi yazmak. Akışı çerçeve yönetir, uygulama kendi parçasını verir.

Yüzeyin genişliği ad çakışması ihtimalini de belirler. Bir kaynak dosya birkaç modülü içe aldığında, her modülün açtığı adlar o dosyada görünür olur. Modül sayısı arttıkça ve yüzeyler genişledikçe iki modülün aynı adı açma ihtimali büyür. Derleyici bu çakışmayı içe alma anında bildirmez; ad kullanıldığı yerde çözülemezse bildirir. Bu yüzden önceki bölümlerdeki `ayni-ad` ölçümünde çözüm, adı kullanan satıra gelene kadar ortaya çıkmaz: iki tanım yan yana durur, ret ancak kullanım anında yaşanır.

Bir diğer sonuç, yüzeyin bir sözleşme olmasıdır. Sistem çerçevesinin yüzeyi sistem sürümüyle birlikte gelir ve uygulama onu değiştiremez. Bir paketin yüzeyi ise uygulamanın bağımlılık olarak seçtiği sürümle sabitlenir ve paketi yazan taraf onu değiştirdiğinde uygulama yeni sürümü seçene kadar etkilenmez. Hangi tarafın yüzeyi hangi hızla değiştirebildiği, hangi bağımlılığın nasıl ele alınacağını belirler; bu ölçümün konusu değil, bu bölümün yalnız bağlamıdır.

#### Nitelenmiş Ad Ne Zaman Gerekir

Nitelenmiş ad (qualified name), bir adın başına modülün adını koyarak o adın hangi modülden geldiğini açıkça söylemektir. Çakışma olmadığı sürece niteleme gereksizdir ve okumayı ağırlaştırır; ad yalnız bir modülde varsa zaten tek bir anlamı vardır. Çakışma olduğunda ise niteleme, iki adayın arasından birini seçmenin dildeki yoludur.

Niteleme yerel tanımla farklı çalışır. Yerel tanım, uygulamanın kendi modülünde yazılan bildirimdir ve içe alınan modüllerin açtığı aynı adlı bildirimlerin önüne geçer; çözüm öncelik çıkar ve okuyan kişi bunu görmeden de bu sonuca varabilir. Niteleme ise okuyana bunu görünür kılar: ad, hangi modülden geldiğini kendisi söyler ve çözüm artık buluşmaya bağlı kalmaz, ortada tek bir tanım kalır. Dersin sonundaki sınırlayıcı ölçümde `nitelenmis-ad` buluşmasının ölçüm dışı çıkmasının nedeni de budur: niteleme iki tanımı buluşturmaz, buluşmayı ortadan kaldırır.

Pratik bir kural şudur: çakışma yoksa nitelemeyin; çakışma varsa ve yerel tanım istenen sonuç değilse nitelemek yerine adı değiştirmeyi düşünün. İçe alınan modülün adını değiştirmek elinizde olmayabilir, ama yerel tanımın adını değiştirmek olur. Niteleme, adı değiştirmenin mümkün olmadığı durumların aracıdır.

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

Ret her zaman son durum değildir. Kullanım modül adıyla nitelendiğinde aday küme teke iner, çünkü yalnız yazılan modülün adı aranır. Bu da buluşmayı ortadan kaldırır: nitelemeden önceki adaylar iki, sonraki aday birdir ve ikincisi birincinin alt kümesidir. Aşağıdaki ölçüm bunu kurar.

```swift
// main.swift — modulle nitelenen ad adayi teke indirir
let graphics = Module(name: "Graphics", exports: ["Canvas", "draw", "render"])
let charts = Module(name: "Charts", exports: ["Canvas", "plot"])

let all = qualified("Canvas", from: graphics)!.union(qualified("Canvas", from: charts)!)
let written = all.filter { $0.hasPrefix("Graphics.") }

let narrowed = Meeting("nitelenmis-ad", first: all, second: written, result: written)
narrowed.report()
```

```
nitelenmis-ad: ilk Charts.Canvas,Graphics.Canvas | ikinci Graphics.Canvas | sonuc Graphics.Canvas -> olcum-disi
```

Çıktı `olcum-disi`: ikinci aday kümesi ilkinin alt kümesi, dolayısıyla iki tanım artık buluşmuyor. Ret çözülebilir bir durumdur. Burada çakışan iki ad da içe alınan modüllere aittir ve hiçbirinin adını değiştirmek sizin elinizde değildir; yukarıdaki kuralın niteleme için ayırdığı durum tam olarak budur ve çözüm adı nitelemektir. Birleşim ise ad kullanılmadığı sürece sorun çıkarmaz.

**PY6.** Bu ölçüm yalnız `Module.name` önekine göre süzmek için; modül adının kendisinin başka bir modülün adıyla çakıştığı durum ölçülmedi.

#### Özet

- Çerçeve uygulamanın kodunu kendi akışında çağırır, kütüphaneyi uygulama çağırır; ikisi de modül olarak içe alınır ve açtıkları adlar aynı kapsamda buluşur.
- İki modülün yüzeyi, ortak ad olmadığında birleşim verir: görünür ad sayısı yalnız artar.
- Aynı ad iki modülde nitelenmeden kullanılırsa arama sonuç vermez ve çözüm ret olur.
- Yerel tanım içe alınan adı gölgeler; bu buluşmanın çözümü önceliktir.
- Modülle nitelenen ad aday kümesini teke indirir ve buluşma kalkar: çıktı `olcum-disi`.
- Bu dersin çözümü: **birleşim**.

#### Sonraki Adım

Bu derste iki tanım ad olarak buluştu. Sıradaki derste buluşan şey bir yetki: uygulamanın istediği izinler ile kullanıcının verdikleri. Kullanıcının verisine ya da aygıtın bir yeteneğine dokunan alan çerçeveleri (konum, sağlık, medya, artırılmış gerçeklik) bu yetkileri ister; çözüm kesişimdir.

### Alan Çerçeveleri

Source: https://academia.sh/tr/kurslar/ios-platformu/alan-cerceveleri

İstenen izinlerin verilenlerle kesiştiğinin, aygıtta bulunmayan yeteneğin ret verdiğinin ve iznin kapsamının kuralı değiştirmediğinin ölçümü.

Önceki ders iki modülün aynı adı açmasını ölçtü; buluşan şey bir addı. Bu derste buluşan şey bir yetkidir: uygulamanın istediği ile kullanıcının verdiği. Soru aynı kalır: **bu iki tanım buluşunca nasıl çözülüyor?** Bu derste bakılan çözüm **kesişim**: sonuçta yalnız iki tarafın da içerdiği yetkiler kalır.

Sistem çerçevelerinin bir bölümü genel amaçlıdır: koleksiyonlar, tarih, dosya, ağ. Bir bölümü ise tek bir alana ayrılmıştır ve alan çerçevesi (domain framework) diye anılır. Konum çerçevesi (Core Location) aygıtın nerede olduğunu verir; harita çerçevesi (MapKit) onu haritada gösterir; sağlık çerçevesi (HealthKit) sağlık verilerini okur ve yazar; medya çerçevesi (AVFoundation) ses ve görüntü yakalama ile oynatmayı yönetir; grafik çerçeveleri çizim işini üstlenir; artırılmış gerçeklik çerçevesi (ARKit) kameradan gelen görüntüyü ortamın bir modeline çevirir; aygıt üstü model çerçevesi (Core ML) eğitilmiş bir modeli aygıtta çalıştırır. Bu çerçevelerin ortak yanı, çoğunun kullanıcının verisine ya da aygıtın bir yeteneğine dokunmasıdır. Dokunduğu şey iki ayrı yerde tanımlıdır: uygulama ne istediğini bildirir, kullanıcı ve aygıt ne sunduğunu belirler.

```swift
// Meeting.swift — iki tanim bulusunca nasil cozuldugunu siniflandirir
struct Meeting {
    let label: String
    let first: Set<String>
    let second: Set<String>
    let result: Set<String>?

    init(_ label: String, first: Set<String>, second: Set<String>, result: Set<String>?) {
        self.label = label
        self.first = first
        self.second = second
        self.result = result
    }

    var resolution: String {
        if first.isEmpty || second.isEmpty
            || first.isSubset(of: second) || second.isSubset(of: first) {
            return "olcum-disi"
        }
        guard let result, !result.isEmpty else { return "ret" }
        if result == first.union(second) { return "birlesim" }
        if result == first.intersection(second) { return "kesisim" }
        if result == first || result == second { return "oncelik" }
        return "kural-disi"
    }

    static func list(_ values: Set<String>) -> String {
        values.isEmpty ? "bos" : values.sorted().joined(separator: ",")
    }

    func report() {
        let outcome = result.map(Meeting.list) ?? "yok"
        print("\(label): ilk \(Meeting.list(first)) | ikinci \(Meeting.list(second)) | sonuc \(outcome) -> \(resolution)")
    }
}

func summarize(_ meetings: [Meeting]) {
    for resolution in ["oncelik", "kesisim", "birlesim", "ret"] {
        let count = meetings.filter { $0.resolution == resolution }.count
        print("\(resolution): \(count)")
    }
}
```

#### İzin, Yetki Bildirimi ve Aygıt Yeteneği

Üç kavramı ayıralım. Yetki bildirimi (usage description), uygulamanın özellik listesinde (`Info.plist`) bir izin için kullanıcıya gösterilecek gerekçedir; bildirim yoksa izin sorusu kullanıcıya hiç ulaşmaz. İzin (permission), kullanıcının uygulamaya verdiği ya da vermediği yetkidir ve uygulamanın isteğinden bağımsız olarak değişebilir. Aygıt yeteneği (device capability) ise donanımın sunduğu bir özelliktir: kamera, ivmeölçer ya da derinlik algılayıcı gibi. Uygulama istediğini, kullanıcı verdiğini, aygıt sunduğunu söyler; uygulamanın bir özelliği ancak üç taraf aynı fikirdeyse çalışır.

Konum izni istenirken yetki bildirimi özellik listesine bir anahtar olarak yazılır ve kod yalnızca kullanımdayken yetkiyi ister. Aşağıdaki iki parça platform çerçevesine dayandığı için kaynak olarak durur ve çıktısı iddia edilmez.

```xml
<key>NSLocationWhenInUseUsageDescription</key>
<string>Yakindaki duraklari gostermek icin konum kullanilir.</string>
```

```swift
// Kaynak parca: konum izni yalniz uygulama kullanimdayken istenir
import CoreLocation

let manager = CLLocationManager()
manager.requestWhenInUseAuthorization()
```

Aşağıdaki yardımcı bu üç tarafın ikisini modeller: istenen izinlerin hangilerinin verildiğini bulan süzme ve istenen yeteneklerin hangilerinin aygıtta bulunduğunu bulan süzme. İkisi de bir kümeyi ötekine göre süzer, dolayısıyla sonuç iki kümenin kesişimidir. Bu dosyanın çıktısı yoktur.

```swift
// Permissions.swift — istenen izinleri ve yetenekleri verilenlere gore suzer
func granted(requested: Set<String>, grants: [String: String]) -> Set<String> {
    requested.filter { grants[$0] != nil }
}

func usable(requested: Set<String>, hardware: Set<String>) -> Set<String> {
    requested.filter { hardware.contains($0) }
}
```

#### İstenen ile Verilen

Aşağıdaki ölçümde uygulamanın yeni sürümü üç izin ister: kamera, konum ve sağlık. Kullanıcı kamera istemini reddetmiştir; konum ve sağlık izinlerini vermiştir. Mikrofon ise uygulamanın önceki sürümünün istediği ve kullanıcının o zaman verdiği bir izindir: yeni sürüm mikrofonu artık istemez, ama verilmiş izin aygıtta uygulamanın kaydında kalır. Böylece iki tarafın da ötekinde olmayan bir elemanı vardır.

```swift
// main.swift — istenen izinler verilenlerle kesisir
let requested: Set = ["kamera", "konum", "saglik"]
let grants = ["konum": "kullanirken", "mikrofon": "verildi", "saglik": "verildi"]

let request = Meeting("izin-istegi",
                      first: requested,
                      second: Set(grants.keys),
                      result: granted(requested: requested, grants: grants))
request.report()
```

```
izin-istegi: ilk kamera,konum,saglik | ikinci konum,mikrofon,saglik | sonuc konum,saglik -> kesisim
```

İstenen ama reddedilen `kamera` düştü; önceki sürümden kalan ve yeni sürümün istemediği `mikrofon` da düştü. Kalan iki ad iki tarafta da var: uygulamanın kullanabildiği yetki kümesi, bu sürümde istediğinin ve kullanıcının kaydında verilmiş olanın ortak kısmıdır. Tek bir sürüm içinde verilen izinler istenenlerin alt kümesidir; kayıttaki fazlalık yalnız istek kümesi sürümler arasında değiştiğinde oluşur. Önceki dersteki birleşimden farkı yönündedir: orada ad sayısı büyüyordu, burada küçülüyor.

**PY7.** Bu ölçüm üç istek ve kayıtta üç verilmiş izinden oluşan tek bir durum için. İzinlerin sonradan geri alınması, kullanıcının izni sormadan reddetmesi ve bir iznin yalnız bir kez verilmesi ölçülmedi.

#### Aygıtta Bulunmayan Yetenek

Yetenek tarafında kesişim kendi içinde iki biçimde ortaya çıkar. Uygulama iki yetenek ister ve aygıt yalnız birini sunar: sonuç kesişimdir ve uygulama kalan yetenekle çalışır. Uygulama yalnız aygıtta bulunmayan yeteneği ister: kesişim boş kalır ve çözüm ret olur. Aşağıdaki ölçüm bu iki durumu yan yana kurar.

```swift
// main.swift — aygitta olmayan yetenek kesisimi bosaltir
let hardware: Set = ["ivmeolcer", "kamera"]

let cases = [
    Meeting("yetenek-kismi",
            first: ["derinlik-algilayici", "kamera"],
            second: hardware,
            result: usable(requested: ["derinlik-algilayici", "kamera"], hardware: hardware)),
    Meeting("yetenek-yok",
            first: ["derinlik-algilayici"],
            second: hardware,
            result: usable(requested: ["derinlik-algilayici"], hardware: hardware)),
]

for item in cases {
    item.report()
}
summarize(cases)
```

```
yetenek-kismi: ilk derinlik-algilayici,kamera | ikinci ivmeolcer,kamera | sonuc kamera -> kesisim
yetenek-yok: ilk derinlik-algilayici | ikinci ivmeolcer,kamera | sonuc bos -> ret
oncelik: 0
kesisim: 1
birlesim: 0
ret: 1
```

`yetenek-kismi` bir yetenek kaybıyla sürer; `yetenek-yok` ise sonuçsuzdur. İkisi aynı kuraldan çıkar, çünkü ikisi de aynı süzmeyle üretilir ve fark yalnız istenen kümenin aygıta ne kadar yaklaştığındadır. Ret burada bir hata değil bir bilgidir: uygulama o yeteneğe ihtiyaç duyuyorsa bunu aygıta kurulmadan önce bildirmek, kurulduktan sonra beklenmedik bir boşlukla karşılaşmaktan iyidir. Bir yeteneğin zorunlu mu isteğe bağlı mı olduğu ayrı bir karardır ve burada ölçülmedi.

**PY8.** Bu ölçüm iki yeteneklik bir aygıt ve tek yeteneklik bir istek için. Yeteneğin sürüme ya da bölgeye bağlı olarak değişmesi ölçülmedi.

#### Alan Çerçevelerinin Sunduğu ve İstediği

Alan çerçevelerinin her biri farklı bir şey sunar ve farklı bir şey ister. Aşağıdaki özet kavramsaldır; hiçbir sayı ya da sürüm iddiası taşımaz.

Konum çerçevesi aygıtın coğrafi konumunu verir. Kullanıcının verisine dokunduğu için bir izin ister ve izin iki kapsamda verilebilir: yalnız uygulama kullanılırken ya da her zaman. Harita çerçevesi konumu haritada gösterir; haritayı çizmek için ayrıca bir izin gerekmez, ama kullanıcının kendi konumunu göstermek konum çerçevesinin iznine bağlıdır. Sağlık çerçevesi sağlık verilerini okur ve yazar; her veri tipi için okuma ve yazma izni ayrı ayrı sorulur ve kullanıcı bunlardan yalnız bazılarını verebilir. Medya çerçevesi kamera ve mikrofon gibi yakalama aygıtlarına ve ses ile görüntü oynatmaya erişir; yakalama bir izin ister, oynatma çoğu durumda istemez. Grafik çerçeveleri çizim işini üstlenir ve kullanıcının verisine dokunmadığı için izin istemez. Artırılmış gerçeklik çerçevesi kamera görüntüsünü ortamın bir modeline çevirir; kamerayı kullandığı için kamera iznine bağlıdır ve ayrıca aygıtın bu işi yapabilecek donanımı taşıması gerekir. Aygıt üstü model çerçevesi eğitilmiş bir modeli aygıtta çalıştırır; model yerel olarak çalıştığı sürece izin istemez, ama modelin girdisi kameradan ya da mikrofondan geliyorsa o girdinin izni gerekir.

Bu liste iki şeyi gösterir. Birincisi, izin çerçevenin değil, çerçevenin dokunduğu verinin özelliğidir: aynı kamera iznini iki farklı çerçeve paylaşabilir. İkincisi, bir çerçevenin izin istememesi onun her aygıtta çalışacağı anlamına gelmez; aygıt yeteneği ayrı bir koşuldur.

#### Yetki Bildirimi

Yetki bildirimi (usage description), uygulamanın özellik listesinde yazdığı, izin iletişim kutusunda kullanıcıya gösterilen açıklama metnidir. İzin istenen her veri tipi için bu açıklama zorunludur ve eksikse platform izin istemini hiç göstermez ya da uygulamayı sonlandırır. Açıklama, uygulamanın bu veriyi ne için istediğini kullanıcıya söyler. İyi bir açıklama somuttur ve yalnız o özelliği anlatır; geniş ve belirsiz bir açıklama kullanıcının izni reddetmesine yol açar.

Önemli bir ayrım: yetki bildirimi bir izin değildir. Bildirim, izin istemini göstermenin ön koşuludur. Uygulamanın istediği ile kullanıcının verdiği arasındaki buluşma, bildirimin yazılmasından sonra, izin istemi gösterildiğinde yapılır. Bu yüzden önceki ölçümler istenen ve verilen kümeleri buluşturur, bildirimi ayrı bir taraf olarak saymaz.

#### İzin Kapsamı

İzin kapsamı (permission scope), verilen iznin ne kadar geniş olduğudur. Konum örneğinde iki kapsam vardır: yalnız kullanırken ve her zaman. Kapsam arttıkça uygulamanın yapabildiği işler artar; bu farkın buluşma kuralına yansıyıp yansımadığını dersin sonundaki sınırlayıcı ölçüm gösterir. Kapsam bir pazarlık değildir; kullanıcı ayarlardan kapsamı daraltabilir ve uygulama bunu çalışırken görür. Uygulama kapsam daraldığında yapabildiği işlerin azaldığını varsaymalı ve çalışma anında verilen kapsamı yeniden sormalıdır; başlangıçta bir kez okunan kapsam, uygulama çalışırken geçerliliğini yitirebilir.

Kapsamın iki sonucu vardır. Uygulama istediği kapsamı en dar tutarsa, kullanıcı izni daha rahat verir ve uygulama daha az iş için sorumlu olur. Uygulama kapsamı geniş istemişse, platform bunu belirli aralıklarla kullanıcıya hatırlatabilir. Her iki durumda da çözüm kesişimdir: uygulamanın istediği ile kullanıcının verdiği kümenin ortak kısmı.

#### İzin İstemini Ne Zaman Göstermek

İzin isteminin zamanı, izin verilme ihtimalini etkiler. Uygulama açılır açılmaz üç izni birden isteyen bir akış, kullanıcıya henüz neden gerektiklerini göstermemiştir; kullanıcı reddeder ve ret çoğu durumda kalıcıdır. Konum iznini haritada "yakınımdaki yerler" düğmesine dokunulduğunda, kamera iznini ilk fotoğraf çekilirken istemek, isteğin bağlamını kullanıcıya gösterir ve yetki bildirimindeki açıklamayı somutlaştırır.

Reddedilen izin de bir sonuçtur ve uygulama bunu işlemelidir. İzin verilmediğinde uygulamanın geri kalanı çalışmaya devam etmeli, yalnız o özellik devre dışı kalmalıdır. Konum reddedilmişse harita yine açılır, ama kullanıcıdan bir adres girmesi istenir. Kullanıcı kararını sonradan değiştirmek isterse yolu ayarlara yönlendirmektir; platform aynı izni ikinci kez iletişim kutusuyla sormaz. Bu da iznin neden iki taraflı bir buluşma olduğunu gösterir: kullanıcının cevabı, uygulamanın bir sonraki adımını belirler. `yetenek-yok` ölçümündeki ret de buna benzer bir durumdur: ortak bir sonuç çıkmadığında uygulama yedek bir yol sunmalıdır.

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

Konum izni iki kapsamda verilebilir: yalnızca uygulama kullanımdayken ve her zaman. Kapsamın kullanıcı için önemi büyüktür, ama buluşma kuralı için önemi yoktur: kural iki kümenin ortak elemanlarına bakar ve eleman olarak izin adını görür, iznin hangi kapsamda verildiğini görmez. Aşağıdaki ölçüm aynı isteği iki farklı kapsamdaki konum izniyle kurar.

```swift
// main.swift — izin kapsami kesisimi degistirmez
let requested: Set = ["kamera", "konum", "saglik"]
let whenInUse = ["konum": "kullanirken", "mikrofon": "verildi", "saglik": "verildi"]
let always = ["konum": "her-zaman", "mikrofon": "verildi", "saglik": "verildi"]

let scopes = [
    Meeting("konum-kullanirken",
            first: requested,
            second: Set(whenInUse.keys),
            result: granted(requested: requested, grants: whenInUse)),
    Meeting("konum-her-zaman",
            first: requested,
            second: Set(always.keys),
            result: granted(requested: requested, grants: always)),
]

for item in scopes {
    item.report()
}
```

```
konum-kullanirken: ilk kamera,konum,saglik | ikinci konum,mikrofon,saglik | sonuc konum,saglik -> kesisim
konum-her-zaman: ilk kamera,konum,saglik | ikinci konum,mikrofon,saglik | sonuc konum,saglik -> kesisim
```

İki satır etiketi dışında aynıdır: iki ölçüm aynı sonucu ve aynı sınıfı verir. Kapsamın getirdiği fark, yani her zaman verilen konum izninin kullanırken verilene göre daha çok işe yetmesi, iznin anlamındadır ve bu ölçümün dışında kalır. Kuralın sınıfı kapsamdan etkilenmez; kapsamı okumak için sonucu ayrıca incelemek gerekir. Bu yüzden "yalnız kullanımdayken" izni kesişim sayımına girer ama iznin neye yettiğini söylemez; bir sonucu "izin var" diye okumak ile "izin her koşulda var" diye okumak arasındaki fark sınıflandırmanın dışında kalır.

**PY9.** Bu ölçüm yalnız iki kapsam ve tek bir izin adı için. Kapsamın arka planda konum alımı gibi bir davranışı değiştirmesi bu modelde yoktur ve ölçülmedi.

#### Özet

- Alan çerçevesi tek bir alana ayrılmış sistem çerçevesidir: konum, sağlık, medya, grafik, artırılmış gerçeklik ve aygıt üstü model örnekleridir.
- Uygulama izin ister, kullanıcı verir, aygıt yetenek sunar; kullanılabilir olan, istenen ile verilenin ortak kısmıdır.
- İstenen izinler ile verilenler kesişir: kamera düştü, konum ve sağlık kaldı.
- Aygıtta bulunmayan yetenek kesişimi boşaltır ve çözüm ret olur.
- İznin kapsamı (yalnız kullanımdayken ya da her zaman) kuralın sınıfını değiştirmez; kural kümeye bakar, kapsamı okumaz.
- Bu dersin çözümü: **kesişim**.

#### Sonraki Adım

Buraya kadar iki tanım bir kümenin iki yüzüydü. Sıradaki derste ikisi de aynı olayı işleyen iki temsilci olur: uygulama temsilcisi ile sahne temsilcisi. İkisi de aynı olay için bir işleyici tanımlarsa hangisinin çalışacağını ölçeriz; çözüm öncelik.

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

Source: https://academia.sh/tr/kurslar/ios-platformu/uygulama-yasam-dongusu

Sahne temsilcisinin işleyicisinin uygulama temsilcisininkine üstün geldiğinin ve bildirilen arka plan kiplerinin platformun izin verdikleriyle kesiştiğinin ölçümü.

Önceki derste uygulamanın istediği ile kullanıcının verdiği buluştu ve kesişim çıktı. Bu derste iki tanım iki işleyici olur: aynı olayı hem uygulama temsilcisi hem sahne temsilcisi işleyebilir. Soru yine aynı: **bu iki tanım buluşunca nasıl çözülüyor?** Bu derste bakılan çözüm **öncelik**: iki işleyiciden biri çalışır, öbürü çalışmaz.

Uygulama yaşam döngüsü (app lifecycle), uygulamanın başlamasından sonlanmasına kadar geçtiği durumların sıralamasıdır. Uygulama çalışmıyor olabilir, ön planda etkin olabilir, ön planda ama etkin olmayabilir (örneğin bir sistem uyarısı ekranı kapattığında), arka planda çalışıyor ya da askıda olabilir. Her durum geçişi (state transition) bir olaydır ve platform bu olayı uygulamaya bir işleyici çağırarak bildirir. İşleyicilerin nerede tanımlandığı ise iki yerden biridir.

```swift
// Meeting.swift — iki tanim bulusunca nasil cozuldugunu siniflandirir
struct Meeting {
    let label: String
    let first: Set<String>
    let second: Set<String>
    let result: Set<String>?

    init(_ label: String, first: Set<String>, second: Set<String>, result: Set<String>?) {
        self.label = label
        self.first = first
        self.second = second
        self.result = result
    }

    var resolution: String {
        if first.isEmpty || second.isEmpty
            || first.isSubset(of: second) || second.isSubset(of: first) {
            return "olcum-disi"
        }
        guard let result, !result.isEmpty else { return "ret" }
        if result == first.union(second) { return "birlesim" }
        if result == first.intersection(second) { return "kesisim" }
        if result == first || result == second { return "oncelik" }
        return "kural-disi"
    }

    static func list(_ values: Set<String>) -> String {
        values.isEmpty ? "bos" : values.sorted().joined(separator: ",")
    }

    func report() {
        let outcome = result.map(Meeting.list) ?? "yok"
        print("\(label): ilk \(Meeting.list(first)) | ikinci \(Meeting.list(second)) | sonuc \(outcome) -> \(resolution)")
    }
}

func summarize(_ meetings: [Meeting]) {
    for resolution in ["oncelik", "kesisim", "birlesim", "ret"] {
        let count = meetings.filter { $0.resolution == resolution }.count
        print("\(resolution): \(count)")
    }
}
```

#### Uygulama Temsilcisi ve Sahne Temsilcisi

Bir uygulamanın ekranda birden çok penceresi olabilir; her pencere grubu bir sahne (scene) olarak yönetilir ve her sahnenin kendi yaşam döngüsü vardır. Uygulama temsilcisi (app delegate) uygulama genelindeki olaylara bakar: başlatma, bildirim kaydı, sistem düzeyinde uyarılar. Sahne temsilcisi (scene delegate) ise o sahneye ait olayları işler: sahnenin bağlanması, ön plana geçmesi, arka plana inmesi. Sahne kullanan bir uygulamada ön plan ve arka plan geçişleri sahne temsilcisine gelir; bu geçişlere ait eski uygulama düzeyi işleyiciler artık çağrılmaz. İki işleyici de tanımlı olduğunda hangisinin çalışacağı sorusunun cevabı bu kuraldır.

Aşağıdaki parça platform çerçevesine dayandığı için kaynak olarak durur ve çıktısı iddia edilmez. Aynı geçiş için iki temsilci de işleyici tanımlar.

```swift
// Kaynak parca: iki temsilci de arka plana gecisi isleyebilir
import UIKit

final class AppDelegate: UIResponder, UIApplicationDelegate {
    func applicationDidEnterBackground(_ application: UIApplication) {
        // uygulama duzeyindeki isleyici
    }
}

final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    func sceneDidEnterBackground(_ scene: UIScene) {
        // sahne duzeyindeki isleyici
    }
}
```

Aşağıdaki yardımcı iki kuralı modeller. İlki bir olay için işleyici arar: sahne temsilcisinde varsa onu, yoksa uygulama temsilcisindekini verir, ikisi de yoksa sonuç vermez. İkincisi uygulamanın bildirdiği arka plan kiplerini platformun izin verdikleriyle süzer. Dosyanın çıktısı yoktur.

```swift
// Handlers.swift — olay isleyicisini ve arka plan kiplerini cozer
func handler(for event: String, scene: [String: String], app: [String: String]) -> Set<String>? {
    if let found = scene[event] { return [found] }
    if let found = app[event] { return [found] }
    return nil
}

func permittedModes(declared: Set<String>, platform: Set<String>) -> Set<String> {
    declared.filter { platform.contains($0) }
}
```

#### Aynı Olay, İki İşleyici

Aşağıdaki ölçüm `on-plana-gec` olayı için iki işleyici kurar: biri uygulama temsilcisinde, biri sahne temsilcisinde. Uygulama temsilcisi ayrıca yalnız kendisinin işlediği bir olayı da tanımlar. `handler` hangi işleyicinin çalışacağını verir.

```swift
// main.swift — sahne isleyicisi uygulama isleyicisinin onune gecer
let appHandlers = ["on-plana-gec": "uygulama.on-plana-gec", "arka-plana-gec": "uygulama.arka-plana-gec"]
let sceneHandlers = ["on-plana-gec": "sahne.on-plana-gec"]

let event = "on-plana-gec"
let both = Meeting("isleyici",
                   first: [appHandlers[event]!],
                   second: [sceneHandlers[event]!],
                   result: handler(for: event, scene: sceneHandlers, app: appHandlers))
both.report()
```

```
isleyici: ilk uygulama.on-plana-gec | ikinci sahne.on-plana-gec | sonuc sahne.on-plana-gec -> oncelik
```

Sonuç ikinci taraftır, yani sahne işleyicisi: uygulama işleyicisi düştü. Bu öncelik bir kural olarak çalışır, bir yarış gibi değil; hangisinin önce yazıldığına ya da hangisinin önce derlendiğine bağlı değildir. Sahne işleyicisi tanımlı olmasaydı `arka-plana-gec` olayında olduğu gibi uygulama işleyicisi kalırdı; o durumda buluşma kurulmaz, çünkü ortada tek bir tanım vardır.

**PY10.** Bu ölçüm yalnız `on-plana-gec` olayı ve iki temsilcinin ikisinde de tanımlı işleyici için. Model, platformun bu kuralını kurar; gerçek sistemin başka olaylarda (başlatma, bildirim kaydı) aynı sırayı izlediği ölçülmedi.

#### Arka Plan Kipleri

Uygulama arka planda ne yapabileceğini özellik listesinde bildirir: konum güncellemesi almak, ses çalmak, veri getirmek gibi. Bildirim tek başına yetmez; platform her kip için kendi koşullarını uygular ve bildirilen her kipe izin vermeyebilir. Çalışabilen kipler, bildirilenler ile platformun izin verdiklerinin ortak kısmıdır.

```xml
<key>UIBackgroundModes</key>
<array>
    <string>location</string>
    <string>audio</string>
    <string>fetch</string>
</array>
```

Aşağıdaki ölçüm üç kip bildirir: konum, ses ve getirme. Platformun izin verdikleri ise ses, getirme ve bildirimdir. Kip adları model adlarıdır, özellik listesindeki gerçek anahtarlar değildir.

```swift
// main.swift — bildirilen kipler platformun izin verdikleriyle kesisir
let declared: Set = ["konum", "ses", "getirme"]
let platform: Set = ["ses", "getirme", "bildirim"]

let modes = Meeting("arka-plan-kipleri",
                    first: declared,
                    second: platform,
                    result: permittedModes(declared: declared, platform: platform))
modes.report()
```

```
arka-plan-kipleri: ilk getirme,konum,ses | ikinci bildirim,getirme,ses | sonuc getirme,ses -> kesisim
```

`konum` bildirildi ama platform tarafında izin yok, düştü; `bildirim` platformda var ama uygulama bildirmedi, o da düştü. Kalan iki kip her iki tarafta da bulundu. Bu buluşma bir önceki dersteki izin akışıyla aynı biçimdedir ve aynı çözümü verir; farkı, sonucu gösteren tarafın bir kullanıcı değil platform olmasıdır.

**PY11.** Bu ölçüm üç bildirilen kip ve üç izin verilen kip için. Bir kipin ek koşullara (örneğin yetki) bağlı olması bu modelde yoktur ve ölçülmedi.

#### Durum Geçişleri ve Beklenen İş

Çalışmıyor durumunun dışında uygulamanın dört durumu vardır. Etkin değil (inactive) durumunda uygulama ön plandadır ama olay almaz; örneğin bir sistem uyarısı ekranı kapattığında ya da geçiş yapılırken uygulama kısa süre bu durumda kalır. Etkin (active) durumunda uygulama ön plandadır ve olay alır. Arka plan (background) durumunda uygulama ekranda görünmez ama kod çalıştırabilir. Askıda (suspended) durumunda uygulama bellekte durur, ama kod çalıştırmaz.

Her geçişte uygulamadan beklenen iş farklıdır. Etkin durumdan etkin değile geçerken uygulama kullanıcıyı bekleten işleri duraklatır: animasyonlar, oyun döngüleri, süre sayaçları. Arka plana geçerken uygulama kaydedilmemiş veriyi kaydeder, paylaşılan kaynakları serbest bırakır ve ağır belleği boşaltır; bu iş kısa sürmelidir, çünkü platform uygulamaya bu iş için sınırlı bir süre tanır. Ön plana dönerken uygulama arayüzünü güncel veriyle yeniler ve duraklattığı işleri sürdürür. Askıdayken uygulama hiçbir şey yapamaz; platform bellek gerektiğinde askıdaki uygulamayı bildirimsiz sonlandırabilir. Bu yüzden son kayıt, askıya geçmeden önce bitmiş olmalıdır; uygulama sonlanmadan önce bir fırsat tanınacağını varsaymaz.

Geçiş sırasının ölçülmemesi bu yüzden bilinçli bir sınırdır: gerçek sistemde sıra önemlidir ve yanlış sırada yapılan iş veri kaybına yol açabilir. Ama bu dersin sorusu sıra değil buluşmadır; hangi işleyicinin çağrılacağını sıra değil, iki temsilcinin tanımları belirler.

#### Arka Plan Kiplerinin Sınırları

Arka plan kipleri uygulamaya arka planda çalışma hakkı verir, ama bu hak kipin amacıyla sınırlıdır. Ses çalan bir uygulama arka planda çalmaya devam edebilir, ama ses çalmayı bırakırsa arka plandaki çalışma hakkını da yitirir. Konum güncellemesi alan uygulama yalnız konum değiştikçe uyandırılır. Veri getirme kipinde uygulamayı platform kendi seçtiği zamanda uyandırır ve uygulama kısa bir süre içinde işini bitirmek zorundadır; uygulama bu zamanı belirleyemez.

Bu sınırların ortak noktası, hakkın bildirimden gelmemesidir. Önceki ölçümde konum kipi bildirildiği halde platformun izin verdikleri arasında olmadığı için düştü. Kip bildirmek bir istektir, hak değil; platform, gereksiz kaynak tüketimini sınırlamak için kipin gerçekten kullanıldığını denetler ve kullanılmayan kipi kısıtlayabilir. Uygulama bu yüzden yalnız gerçekten kullandığı kipleri bildirmeli ve arka plandaki işini kipin amacına uygun tutmalıdır.

Son bir sınır da kullanıcıya aittir. Kullanıcı arka planda yenilemeyi uygulama için kapatabilir; bu durumda bildirilen ve platformun izin verdiği kip bile çalışmaz. Önceki ölçümdeki kesişim bu yüzden bir alt sınırdır: üçüncü bir taraf, kullanıcının ayarı, kümeyi daha da daraltabilir.

#### Sahne Ömrü ve Durumun Korunması

Sahne temsilcisinin yaşam döngüsü, uygulamanınkinden ayrıdır. Platform, arka plandaki bir sahneyi bellek gerektiğinde sistemden ayırabilir; sahne kapanır ama uygulama süreci çalışmaya devam edebilir. Kullanıcı sahneye döndüğünde platform sahneyi yeniden bağlar ve bu yeni bir bağlantı olarak görünür: sahne temsilcisi bağlantı olayını alır, ama arayüzün önceki durumunu kendisi kurmak zorundadır. Bu yüzden sahnenin kullanıcıya ait durumu, örneğin hangi ekranda olunduğu ve yarım kalan bir giriş, sahne arka plana geçerken saklanmalı ve yeniden bağlanırken geri yüklenmelidir.

Bu iş bölümü, önceki ölçümdeki önceliğin neden kullanışlı olduğunu da gösterir. Çok pencereli bir uygulamada her sahnenin kendi ön plan ve arka plan geçişi vardır: biri ön plandayken öbürü arka planda olabilir. Uygulama temsilcisi bu durumu tek bir olay olarak görür; yalnız sahne temsilcisi hangi sahnenin hangi duruma geçtiğini bilir. Sahne işleyicisinin uygulama işleyicisine üstün gelmesi bu yüzden bir tercih değil, bir zorunluluktur: olayın doğru sahibi, olayı en iyi bilen taraftır.

Uygulama temsilcisi tamamen devre dışı kalmaz. Sistem düzeyindeki işler, örneğin uzak bildirim kaydı ve uygulamanın ilk başlatılması, hâlâ ona aittir. Kural bu sınırı çizer: sahneye ait olay sahneye, uygulamaya ait olay uygulamaya gider. İki işleyicinin gerçekten aynı olay için tanımlı olduğu durumlar, çözümün öncelik çıktığı tek durumdur; geri kalan olaylarda buluşma zaten kurulmaz.

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

Yaşam döngüsünü anlatırken geçişlerin sırası öne çıkar: önce ön plana, sonra arka plana, sonra tekrar ön plana. Bu ölçüm sırayı ölçmez. Ölçülen şey yalnız iki tanımın buluşmasıdır, ve buluşma bir geçişin hangi sırada geldiğinden bağımsızdır. Bunu görmek için aynı iki olayı ters sırayla iki kez çözeriz.

```swift
// main.swift — gecis sirasi bulusmayi degistirmez
let appHandlers = ["on-plana-gec": "uygulama.on-plana-gec", "arka-plana-gec": "uygulama.arka-plana-gec"]
let sceneHandlers = ["on-plana-gec": "sahne.on-plana-gec", "arka-plana-gec": "sahne.arka-plana-gec"]
let forward = ["on-plana-gec", "arka-plana-gec"]
let backward = ["arka-plana-gec", "on-plana-gec"]

func resolved(_ events: [String], scene: [String: String], app: [String: String]) -> Set<String> {
    Set(events.compactMap { handler(for: $0, scene: scene, app: app) }.flatMap { $0 })
}

let orders = [
    Meeting("gecis-ileri",
            first: Set(forward.map { appHandlers[$0]! }),
            second: Set(forward.map { sceneHandlers[$0]! }),
            result: resolved(forward, scene: sceneHandlers, app: appHandlers)),
    Meeting("gecis-geri",
            first: Set(backward.map { appHandlers[$0]! }),
            second: Set(backward.map { sceneHandlers[$0]! }),
            result: resolved(backward, scene: sceneHandlers, app: appHandlers)),
]

for item in orders {
    item.report()
}
```

```
gecis-ileri: ilk uygulama.arka-plana-gec,uygulama.on-plana-gec | ikinci sahne.arka-plana-gec,sahne.on-plana-gec | sonuc sahne.arka-plana-gec,sahne.on-plana-gec -> oncelik
gecis-geri: ilk uygulama.arka-plana-gec,uygulama.on-plana-gec | ikinci sahne.arka-plana-gec,sahne.on-plana-gec | sonuc sahne.arka-plana-gec,sahne.on-plana-gec -> oncelik
```

İki satır etiketi dışında aynıdır: kümeler sıralanarak basılır ve geçişlerin hangi sırada çözüldüğü sonucu değiştirmez. Geçişlerin gerçek sistemde hangi koşullarda ve hangi sırayla tetiklendiği ayrı bir konudur; bu kursun ölçüm eksenine girmez.

**PY12.** Bu ölçüm iki olay ve iki sıra için; her ikisinde de sahne temsilcisi iki olayı da işliyor. Sahnenin yalnız birini işlediği karışık durum ölçülmedi.

#### Özet

- Uygulama yaşam döngüsü, ön plan ve arka plan arasındaki durum geçişlerinin sıralamasıdır ve her geçiş bir işleyiciye bildirilir.
- Sahne kullanan uygulamada ön plan ve arka plan geçişleri sahne temsilcisine gelir.
- Aynı olay için iki işleyici tanımlıysa sahne işleyicisi uygulama işleyicisine üstün gelir: çıktı öncelik.
- Bildirilen arka plan kipleri platformun izin verdikleriyle kesişir; konum düştü, ses ile getirme kaldı.
- Geçişlerin sırası buluşmanın sınıfını değiştirmez; ölçü yalnız iki tanımın buluşmasıdır.
- Bu dersin çözümü: **öncelik**.

#### Sonraki Adım

Sahne ve uygulama arasındaki öncelik, bir tarafın ötekinin yerine geçtiği bir çözümdü. Sıradaki derste aynı çözüm kullanıcıyla uygulama arasında kurulur: kullanıcının yazı boyutu ayarı uygulamanın varsayılanına üstün gelir. Arayüz tasarım kılavuzlarını bu gözle okuruz.

### Arayüz Tasarım Kılavuzları

Source: https://academia.sh/tr/kurslar/ios-platformu/arayuz-tasarim-kilavuzlari

Kullanıcının yazı boyutunun uygulama varsayılanına üstün geldiğinin, ekran yönlerinin kesiştiğinin ve renklerin birleştiğinin ölçümü.

Önceki derste sahne işleyicisi uygulama işleyicisine üstün geldi. Bu derste aynı çözüm iki yeni tarafı buluşturur: uygulamanın yazı boyutu varsayılanı ile kullanıcının ayarı. Soru değişmez: **bu iki tanım buluşunca nasıl çözülüyor?** Üç buluşma ölçülecek: öncelik, kesişim ve birleşim.

Arayüz tasarım kılavuzları (interface design guidelines), platformun arayüz için koyduğu beklentilerdir: dokunma hedefinin büyüklüğü, yazının okunabilirliği, renklerin açık ve koyu görünümde tutarlılığı, ekranın yönü. Bu kılavuzlar bir derleyici denetimi değildir; uygulama kılavuza uymasa da derlenir ve çalışır. Kılavuz bir beklenti koyar, platform ise bu beklentilerin bir bölümünü ayarlar aracılığıyla uygular. Bu dersteki ölçüler kılavuzun kendisini değil, kılavuzun dayandığı üç ayarın buluşmasını ölçer.

```swift
// Meeting.swift — iki tanim bulusunca nasil cozuldugunu siniflandirir
struct Meeting {
    let label: String
    let first: Set<String>
    let second: Set<String>
    let result: Set<String>?

    init(_ label: String, first: Set<String>, second: Set<String>, result: Set<String>?) {
        self.label = label
        self.first = first
        self.second = second
        self.result = result
    }

    var resolution: String {
        if first.isEmpty || second.isEmpty
            || first.isSubset(of: second) || second.isSubset(of: first) {
            return "olcum-disi"
        }
        guard let result, !result.isEmpty else { return "ret" }
        if result == first.union(second) { return "birlesim" }
        if result == first.intersection(second) { return "kesisim" }
        if result == first || result == second { return "oncelik" }
        return "kural-disi"
    }

    static func list(_ values: Set<String>) -> String {
        values.isEmpty ? "bos" : values.sorted().joined(separator: ",")
    }

    func report() {
        let outcome = result.map(Meeting.list) ?? "yok"
        print("\(label): ilk \(Meeting.list(first)) | ikinci \(Meeting.list(second)) | sonuc \(outcome) -> \(resolution)")
    }
}

func summarize(_ meetings: [Meeting]) {
    for resolution in ["oncelik", "kesisim", "birlesim", "ret"] {
        let count = meetings.filter { $0.resolution == resolution }.count
        print("\(resolution): \(count)")
    }
}
```

#### Dinamik Yazı Boyutu

Kullanıcı sistem ayarlarından tercih ettiği yazı boyutunu seçer. Dinamik yazı boyutu (dynamic type) bu tercihin uygulamaya yansımasıdır: uygulama sabit bir punto yazmak yerine bir yazı stili ister ve platform stile kullanıcının boyutunu uygular. Arayüz erişilebilirliği (accessibility) açısından bu, görme güçlüğü olan bir kullanıcının uygulamayı kendi boyutunda okuyabilmesi demektir.

Aşağıdaki parça platform çerçevesine dayandığı için kaynak olarak durur ve çıktısı iddia edilmez.

```swift
import SwiftUI

struct SummaryHeader: View {
    var body: some View {
        Text("Toplanti ozeti")
            .font(.body)
    }
}
```

`.font(.body)` sabit bir boyut yazmaz, bir stil ister. Gerçek puntoyu kullanıcının ayarı belirler; model, platformun bu kuralını kurar.

**PY13.** Aşağıdaki sayılar model değerleridir: gerçek bir stilin gerçek puntosunu göstermez. `Meeting` kümelerle çalıştığı için boyutlar dize olarak taşınır.

Uygulamanın varsayılanı 16, kullanıcının seçtiği 22'dir. Çözüm kullanıcının seçtiğini verir.

```swift
// main.swift — kullanicinin yazi boyutu uygulama varsayilanina ustun gelir
let appDefault = "16"
let userChoice = "22"

let size = Meeting("yazi-boyutu",
                   first: [appDefault],
                   second: [userChoice],
                   result: [userChoice])
size.report()
```

```
yazi-boyutu: ilk 16 | ikinci 22 | sonuc 22 -> oncelik
```

Uygulamanın 16'sı düştü, kullanıcının 22'si kaldı. Bu, ayar kullanıcıya ait olduğu için uygulamanın değil kullanıcının sözünün geçtiği bir öncelik. Uygulama bu önceliği bozmak yerine arayüzünü büyüyen yazıya göre kurar: satırlar uzar, düzen alta kayar.

#### Ekran Yönü

Ekran yönü (orientation), arayüzün hangi yönlerde çevrilebildiğidir. Uygulama desteklediği yönleri bildirir; aygıt ise kendi sunduğu yönleri bildirir. Dönülebilecek yönler ikisinin ortak kısmıdır. Aşağıdaki özellik listesi parçası uygulama tarafını bildirir.

```xml
<key>UISupportedInterfaceOrientations</key>
<array>
    <string>UIInterfaceOrientationPortrait</string>
    <string>UIInterfaceOrientationLandscapeLeft</string>
</array>
```

Aşağıdaki yardımcı iki kümenin ortak yönlerini verir. Dosyanın çıktısı yoktur.

```swift
// Orientations.swift — uygulamanin ve aygitin ortak ekran yonleri
func sharedOrientations(app: Set<String>, device: Set<String>) -> Set<String> {
    app.intersection(device)
}
```

Uygulama dikey, ters dikey ve sol yatay yönü destekler; aygıt dikey, sağ yatay ve sol yatayı sunar. İki küme birbirinin alt kümesi değildir.

```swift
// main.swift — ekran yonleri kesisir
let appSide: Set = ["dikey", "ters-dikey", "yatay-sol"]
let deviceSide: Set = ["dikey", "yatay-sag", "yatay-sol"]

let turns = Meeting("yon-destegi",
                    first: appSide,
                    second: deviceSide,
                    result: sharedOrientations(app: appSide, device: deviceSide))
turns.report()
```

```
yon-destegi: ilk dikey,ters-dikey,yatay-sol | ikinci dikey,yatay-sag,yatay-sol | sonuc dikey,yatay-sol -> kesisim
```

`ters-dikey` yalnız uygulamada, `yatay-sag` yalnız aygıtta vardı; ikisi de düştü. **PY14.** Yön adları model adlarıdır ve bu kümeler gerçek bir aygıtın gerçek yönlerini temsil etmez. Önceki dersin arka plan kipleri ile bu ölçüm aynı çözümü paylaşır: iki taraf birbirinin değerini vermeden, yalnız ortak olanı bırakır.

#### Renk Paleti

Sistem arayüzü kendi adlandırılmış renklerini sunar ve uygulama kendi renklerini ekler. Açık ve koyu görünümde ayrı değer alan sistem renkleri uygulamanın elinde hazır bulunur; uygulama ayrıca kendi marka renklerini tanımlar. Kullanılabilen palet ikisinin toplamıdır. Aşağıdaki yardımcı iki paletin adlarını birleştirir.

```swift
// Palette.swift — sistem ve uygulama renk adlarini birlestirir
func mergedColorNames(system: [String: String], app: [String: String]) -> Set<String> {
    Set(system.keys).union(app.keys)
}
```

```swift
// main.swift — sistem ve uygulama renkleri birlesir
let systemColors = ["arka-plan": "sistem", "metin": "sistem", "vurgu": "sistem"]
let appColors = ["marka": "uygulama", "vurgu": "uygulama"]

let palette = Meeting("renk-paleti",
                      first: Set(systemColors.keys),
                      second: Set(appColors.keys),
                      result: mergedColorNames(system: systemColors, app: appColors))
palette.report()
```

```
renk-paleti: ilk arka-plan,metin,vurgu | ikinci marka,vurgu | sonuc arka-plan,marka,metin,vurgu -> birlesim
```

Dört ad, iki kaynaktan geldi. `vurgu` iki tarafta da var ve birleşimde bir kez görünür. Bu ölçü yalnız adların birleşimini sayar; aynı adın iki tarafta farklı değer taşıması durumunda hangisinin geçerli olacağı ayrı bir öncelik sorusudur ve burada ölçülmedi.

#### Platform Tutarlılığı Beklentisi

Arayüz tasarım kılavuzlarının temel beklentisi tutarlılıktır: bir uygulama platformun alışkın olduğu kalıpları izlerse kullanıcı onu öğrenmeden kullanır. Geri dönme hareketi her uygulamada aynı yerde ve aynı biçimde olur; paylaşma düğmesi aynı simgeyi taşır; liste satırı aynı biçimde kaydırılır. Uygulama bu kalıpları bozarsa kullanıcı o uygulamaya özgü bir kuralı öğrenmek zorunda kalır. Tutarlılık bir yasak değil, bir bedel hesabıdır: kalıbı bozan uygulama, kullanıcının öğrenme yükünü kendisi üstlenir.

Bu beklentinin ayarlarla bağı şudur: platform kalıpları, sistem bileşenlerinin içine yazılmıştır. Uygulama bir sistem bileşenini kullandığında kılavuzun karşıladığı kalıbı, dinamik yazı boyutunu ve görünümü (açık ya da koyu) kendiliğinden alır. Uygulama kendi bileşenini çizdiğinde bu davranışların her birini kendisi karşılamak zorundadır. Önceki bölümlerdeki öncelik ve birleşim ölçümlerinin ikisi de bu iş bölümünün sonucudur: ayarın sahibi kullanıcıdır, bileşen ona uyar.

#### Erişilebilirlik

Arayüz erişilebilirliği yalnız yazı boyutuyla sınırlı değildir. Ekran okuyucu kullanan bir kullanıcı için her denetimin bir adı ve rolü olmalıdır: bir simge düğmesinin yalnız resim olması, okuyucunun ona hiçbir şey söyleyememesi demektir. Renk körlüğü olan bir kullanıcı için bilgi yalnız renkle verilmemelidir; durum bir simge ya da metinle de belirtilmelidir. Hareketi azaltmayı seçen kullanıcı için geçiş animasyonları sadeleşmelidir. Koyu görünümü seçen kullanıcı için renkler hem açık hem koyu zeminde okunmalıdır; bu yüzden sistem renkleri sabit bir renk değil, görünüme göre değişen adlardır.

Bunların hiçbiri uygulama için bir zorunluluk değildir; derleyici denetlemez. Ama hepsi kullanıcıya ait bir ayardır ve ayar uygulamanın varsayılanına üstün gelir. Dinamik yazı boyutunda ölçülen öncelik bu genel kuralın yalnız bir örneğidir: kullanıcının ayarı ne olursa olsun, uygulama o ayarı karşılamak için hazırlıklı olmalıdır.

#### Gezinme ve Dokunma Hedefi

Gezinme (navigation), kullanıcının uygulamanın bölümleri arasında nasıl dolaştığıdır. Kılavuz birkaç kalıbı tanır: üstte geri dönülebilen bir hiyerarşi, altta eşdeğer bölümler arasında geçişi sağlayan bir sekme çubuğu, geçici bir görev için üstten açılan bir sayfa. Kalıp, içeriğin yapısına uymalıdır: birbirine eşdeğer bölümler sekme çubuğuna, adım adım ilerleyen içerik hiyerarşiye aittir. Yapıya uymayan kalıp kullanıcının yönünü kaybettirir.

Dokunma hedefi (touch target), kullanıcının parmağıyla vurduğu alandır. Kılavuz, bu alanın parmağın rahatça isabet edebileceği kadar geniş olmasını bekler: küçük bir simge, görünüşü küçük olsa bile, etrafındaki boşlukla birlikte yeterli bir alan sağlamalıdır. Bitişik iki hedef arasında da boşluk bırakılmalıdır, yoksa kullanıcı yanlış olana dokunur. Dokunma hedefi yazı boyutuyla birlikte büyür: kullanıcı yazıyı büyütmüşse hedeflerin de büyümesi gerekir, aksi hâlde büyüyen yazı sıkışmış bir düzende okunamaz hale gelir.

Bu konuların hepsi aynı şeyi söyler: kılavuz bir çözüm sınıfı değil, bir beklentidir. Dersin sonundaki sınırlayıcı ölçümde 20 gibi bir sonucun `kural-disi` işaretlenecek olmasının nedeni de budur; kılavuz "uygun" demedikçe ölçü bir sınıf atamaz.

#### Açık ve Koyu Görünüm

Platform iki görünüm sunar: açık ve koyu. Kullanıcı birini seçer ya da sistemin gün içinde değiştirmesine izin verir. Uygulama bu seçime uymalıdır, çünkü seçim kullanıcının ayarıdır. Sabit bir renk kodu yazan uygulama, koyu zeminde okunmaz hale gelebilir; sistem renk adlarını kullanan uygulama ise görünüme göre değişen bir değeri kendiliğinden alır. Renk paleti ölçümündeki `arka-plan` ve `metin` adları bu yüzden önemlidir: ad sabittir, ardındaki değer görünümle birlikte değişir.

Uygulamanın kendi renkleri de iki görünüm için ayrı ayrı tanımlanmalıdır. Marka rengi açık zeminde yeterince koyu, koyu zeminde yeterince açık olmalıdır, yoksa metin ile zemin arasındaki karşıtlık düşer ve okunabilirlik kaybolur. Karşıtlık yalnız güzellik değil, erişilebilirliktir: düşük karşıtlıklı bir metin, görme gücü zayıf kullanıcı için okunamaz.

#### Güvenli Alan ve Uyarlanır Yerleşim

Güvenli alan (safe area), ekranın içeriğin güvenle yerleştirilebileceği bölümüdür. Aygıtın yuvarlak köşeleri, kameranın ekrana girdiği bölge ve sistem çubukları içeriğin bir kısmını örtebilir; platform güvenli alanı bu bölgelerin dışında tanımlar. Metin ve denetimler güvenli alanın içine yerleşmelidir. Arka plan rengi ya da görsel ise güvenli alanın dışına, ekranın kenarına kadar uzanabilir, çünkü örtülmesi sorun yaratmaz.

Uyarlanır yerleşim (adaptive layout), düzenin ekran boyutuna ve yönüne göre kendini uyarlamasıdır. Dar bir ekranda tek sütun, geniş bir ekranda iki sütun gibi. Burada ekran yönü ölçümüyle bir bağ vardır: uygulamanın desteklediği ve aygıtın sunduğu yönlerin kesişimi, düzenin hangi yönlerde çalışması gerektiğini söyler. Kesişimde yalnız dikey kalmışsa yatay düzen yazmaya gerek yoktur; kesişim genişse her yön için düzen sınanmalıdır. Yani kesişim aynı zamanda bir iş listesidir.

#### Kılavuzdan Sapmak

Kılavuz bir beklenti olduğu için sapmak mümkündür, ama sapmanın bir gerekçesi olmalıdır. Bir oyun ya da bir çizim uygulaması, standart gezinme kalıplarını izlemeyebilir, çünkü içeriğin kendisi başka bir etkileşim biçimi ister. Bir okuma uygulaması ise standart kalıpları bozmamalıdır, çünkü bozmanın karşılığında kazanılan bir şey yoktur. Sapmanın maliyeti kullanıcının öğrenme yüküdür; bu yükü haklı çıkaran bir kazanç varsa sapmak makuldür.

Ama bazı beklentiler sapmaya açık değildir: kullanıcının yazı boyutu ayarına, erişilebilirlik ayarlarına ve görünüm seçimine uymak. Bunlar kullanıcıya ait ayarlardır ve uygulama tarafından geçersiz sayılamaz. Öncelik ölçümünün gösterdiği de budur: kullanıcının ayarı uygulamanın varsayılanından önce gelir; uygulama kılavuzdan sapabilir, ama kullanıcının ayarının aleyhine sapamaz.

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

Kılavuz bir beklentidir, bir çözüm sınıfı değildir. Bu yüzden kılavuza uymayan bir sonuç, dört çözümden biri olarak sayılmaz. Aşağıdaki ölçümde uygulama 16, kullanıcı 22 der; sonuç ise 20 çıkar. Bu, ne uygulamanın ne kullanıcının değeridir, ikisinin ortası bir değerdir.

```swift
// main.swift — kural disi sonuc dortten birine girmez
let clamped = Meeting("sinirlanmis-boyut",
                      first: ["16"],
                      second: ["22"],
                      result: ["20"])
let inside = Meeting("sinir-icinde",
                     first: ["16"],
                     second: ["22"],
                     result: ["22"])
clamped.report()
inside.report()
```

```
sinirlanmis-boyut: ilk 16 | ikinci 22 | sonuc 20 -> kural-disi
sinir-icinde: ilk 16 | ikinci 22 | sonuc 22 -> oncelik
```

20 dört çözümün hiçbirine girmedi ve `kural-disi` oldu. Bir kılavuzun "yazı boyutunu sınırla" demesi, bu çözümün dört sınıfa eklendiği anlamına gelmez; sonuç kümeler üzerindeki dört işlemin çıktısı olmayan bir değerse ölçü bunu sınıflamaz, olduğu gibi işaretler.

**PY15.** Bu ölçüm yalnız model değerleriyle 16, 22 ve 20 için. Gerçek bir sistemin gerçekten ara değer üretip üretmediği ölçülmedi.

#### Özet

- Arayüz tasarım kılavuzları platformun arayüz için koyduğu beklentilerdir, derleyici denetimi değildir.
- Dinamik yazı boyutunda kullanıcının ayarı uygulamanın varsayılanına üstün gelir: çıktı öncelik.
- Uygulamanın desteklediği ve aygıtın sunduğu ekran yönleri kesişir; yalnız ortak yönler kalır.
- Sistem ve uygulama renkleri birleşir; ortak ad bir kez görünür.
- Kural dışı bir sonuç dört çözümden biri sayılmaz ve ayrı işaretlenir.
- Bu dersin çözümü: **öncelik**.

#### Sonraki Adım

Önceki derslerde buluşan taraflar ayarlardı. Sıradaki derste buluşan taraflar sürüm aralıklarıdır: uygulamanın desteklediği sürümler ile bir arayüzün bulunduğu sürümler kesişir ve ayrık kaldığında sonuç ret olur.

### Sürüm ve Uyumluluk

Source: https://academia.sh/tr/kurslar/ios-platformu/surum-ve-uyumluluk

Uygulamanın desteklediği sürümlerin bir arayüzün bulunduğu sürümlerle kesiştiğinin, ayrık kaldığında reddedildiğinin ve asgari sürüm kararının ölçümü.

Önceki derste kullanıcı, aygıt ve uygulama değerleri buluştu. Bu derste buluşan taraflar sürüm aralıklarıdır: uygulamanın desteklediği sürümler ile bir platform arayüzünün bulunduğu sürümler. Soru yine aynı: **bu iki tanım buluşunca nasıl çözülüyor?** Bu dersin çözümü **kesişim**, ve kesişim boş kaldığında **ret**.

Asgari desteklenen sürüm (deployment target), uygulamanın çalışacağı en eski platform sürümüdür. Uygulama bu sürümden eski bir sistemde kurulamaz. Üst sınır ise bildirilmez; uygulama kendi derlendiği sürümden yenisinde de çalışmaya devam eder. Bu yüzden modeldeki ilk küme, uygulamanın sınandığı sürümlerdir: asgari sürümden uygulamanın sınandığı en yeni sürüme kadar. Bir platform arayüzü ise belirli bir sürümde eklenmiş olur ve o sürümden önce yoktur; kaldırılmışsa kaldırıldığı sürümden itibaren de yoktur. Uygulamanın desteklediği sürümlerden bazılarında arayüz bulunur, bazılarında bulunmaz. Bu dersteki soru işte bu bölünmenin nasıl çözüldüğüdür.

```swift
// Meeting.swift — iki tanim bulusunca nasil cozuldugunu siniflandirir
struct Meeting {
    let label: String
    let first: Set<String>
    let second: Set<String>
    let result: Set<String>?

    init(_ label: String, first: Set<String>, second: Set<String>, result: Set<String>?) {
        self.label = label
        self.first = first
        self.second = second
        self.result = result
    }

    var resolution: String {
        if first.isEmpty || second.isEmpty
            || first.isSubset(of: second) || second.isSubset(of: first) {
            return "olcum-disi"
        }
        guard let result, !result.isEmpty else { return "ret" }
        if result == first.union(second) { return "birlesim" }
        if result == first.intersection(second) { return "kesisim" }
        if result == first || result == second { return "oncelik" }
        return "kural-disi"
    }

    static func list(_ values: Set<String>) -> String {
        values.isEmpty ? "bos" : values.sorted().joined(separator: ",")
    }

    func report() {
        let outcome = result.map(Meeting.list) ?? "yok"
        print("\(label): ilk \(Meeting.list(first)) | ikinci \(Meeting.list(second)) | sonuc \(outcome) -> \(resolution)")
    }
}

func summarize(_ meetings: [Meeting]) {
    for resolution in ["oncelik", "kesisim", "birlesim", "ret"] {
        let count = meetings.filter { $0.resolution == resolution }.count
        print("\(resolution): \(count)")
    }
}
```

#### Sürüm Denetimi

Sürüm denetimi (availability check), bir arayüzün çalışma anında var olup olmadığını sınayan yapıdır. Uygulama eski sürümleri de destekliyorsa, yeni bir arayüzü çağırmadan önce sürümü sınamak gerekir; sınanmadan çağrılan arayüz, eski sürümde bulunmadığı için derleme aşamasında reddedilir. Aşağıdaki parça platform arayüzlerine dayandığı için kaynak olarak durur ve çıktısı iddia edilmez.

```swift
import SwiftUI

struct DetailView: View {
    var body: some View {
        if #available(iOS 16, *) {
            Text("Yeni arayuz")
        } else {
            Text("Eski arayuz")
        }
    }
}
```

`#available` iki dalı ayırır: yeni arayüz yalnız yeni sürümde çalışır, eski sürümde yedek dal çalışır. Model, platformun bu kuralını kurar. Aşağıdaki sayılar model değerleridir: gerçek sürüm numaralarını ya da gerçek arayüzlerin gerçek sürümlerini göstermez. **PY16.**

#### Kesişen Sürümler

Aşağıdaki yardımcı iki sürüm aralığını alıp ortak sürümleri verir. Aralıklar tam sayı ile ifade edilir, `Meeting` kümeyle çalıştığı için sürümler dize olarak taşınır. Dosyanın çıktısı yoktur.

```swift
// Versions.swift — iki surum araliginin ortak surumleri
func commonVersions(app: ClosedRange<Int>, api: ClosedRange<Int>) -> Set<String> {
    Set(app.filter { api.contains($0) }.map(String.init))
}

func versionNames(_ range: ClosedRange<Int>) -> Set<String> {
    Set(range.map(String.init))
}
```

Uygulama 14'ten 17'ye kadar destekler; arayüz 16'dan 19'a kadar bulunur.

```swift
// main.swift — desteklenen ve bulunan surumler kesisir
let supported = 14...17
let available = 16...19

let overlap = Meeting("surum-kesisimi",
                      first: versionNames(supported),
                      second: versionNames(available),
                      result: commonVersions(app: supported, api: available))
overlap.report()
```

```
surum-kesisimi: ilk 14,15,16,17 | ikinci 16,17,18,19 | sonuc 16,17 -> kesisim
```

14 ve 15 uygulamada var ama arayüzde yok; 18 ve 19 arayüzde var ama uygulama desteklemiyor. Çalışan sürümler 16 ve 17'dir. **PY17.** Bu ölçüm yalnız bu iki aralık için; aralıkların sınırları model değerleridir.

Kesişimin iki ucu iki ayrı soruyu cevaplar. Alt uç, arayüzün ilk bulunduğu sürümdür: uygulamanın desteklediği daha eski sürümlerde arayüz çağrılamaz ve bu sürümler için yedek dal gerekir. Üst uç ise uygulamanın sınandığı en yeni sürümdür; uygulama daha yeni sürümlerde de çalışır ve arayüz orada da bulunur, ama sınanmamış sürümler modelin ilk kümesine girmediği için kesişimde görünmez. Üst uç bir destek sınırı değil, ölçümün sınırıdır. Yani yedek dalın kapsadığı sürümler, uygulamanın aralığından kesişimin çıkarılmasıyla bulunur: burada 14 ve 15.

#### Ayrık Sürümler

Aralıklar hiç kesişmezse ortak sürüm yoktur. Bunun gerçekten olduğu durum, kaldırılmış bir arayüzdür: arayüz 10'da eklenmiş, 13'te kaldırılmıştır ve yalnız 10'dan 12'ye bulunur. Uygulama 13'ten 15'e destekler; hiçbir sürümde ikisi birlikte yok.

```swift
// main.swift — ayrik surum araliklari reddedilir
let newApp = 13...15
let removedApi = 10...12

let apart = Meeting("ayrik-surum",
                    first: versionNames(newApp),
                    second: versionNames(removedApi),
                    result: commonVersions(app: newApp, api: removedApi))
apart.report()
```

```
ayrik-surum: ilk 13,14,15 | ikinci 10,11,12 | sonuc bos -> ret
```

Sonuç boş küme olunca çözüm ret çıkar. Gerçek bir projede bu, arayüzün bu uygulamada hiç kullanılamayacağı anlamına gelir: `#available` koşulu da işe yaramaz, çünkü desteklenen hiçbir sürümde arayüzü çağıran dal doğru dönmez. Tek yol, arayüzü yerine gelen yenisiyle değiştirmektir. Bu, retin çözümsüzlük değil, bir sınır bildirimi olduğunu gösterir.

#### Asgari Sürüm Kararının Ölçütleri

Asgari desteklenen sürüm bir buluşma sonucu değil, bir karardır ve üç ölçüte dayanır.

Birincisi kullanıcı tabanıdır. Asgari sürümü yükseltmek, o sürümden eski sistemde kalan kullanıcıları dışarıda bırakır. Karar için kullanıcıların hangi sürümlerde olduğunun bilinmesi gerekir; bu bilgi uygulamanın kendi kullanım verisinden ve platformun sunduğu dağıtım bilgisinden gelir. Eski sürümde kalan kullanıcı oranı küçükse yükseltmenin bedeli düşüktür, büyükse yüksektir. Bu dersin ölçümü bu oranı bilmez ve bilemez; ölçülen yalnız aralıkların kesişimidir.

İkincisi API erişimidir. Her sürüm yeni arayüzler getirir ve asgari sürümü yükseltmek, o sürümdeki arayüzlerin koşulsuz çağrılabilmesini sağlar. Dersin sonundaki sınırlayıcı ölçümde `asgari-16` denemesinde uygulamanın aralığı arayüzün aralığının alt kümesi olur ve ölçüm dışı çıkar: bu durumda `#available` denetimine gerek kalmaz, çünkü uygulamanın desteklediği her sürümde arayüz vardır. API erişimi ölçütü, bu sadeleşmenin değeridir.

Üçüncüsü bakım yüküdür. Desteklenen her sürüm, sınanması gereken bir koşuldur. Eski ve yeni sürümler için ayrı davranış yazan kod, her dalı ayrı ayrı sınamayı gerektirir ve hata ihtimali dal sayısıyla artar. Asgari sürümü yükseltmek dalları azaltır, ama kullanıcı tabanını daraltır. Karar, bu iki bedelin arasındadır ve bedelin ağırlığı uygulamadan uygulamaya değişir.

Üç ölçüt birbirine karşıdır: kullanıcı tabanı sürümü düşük tutmayı, API erişimi ve bakım yükü yüksek tutmayı ister. Bu gerilimin çözümü bir küme işlemi değildir. Dersin sonundaki ölçüm de bunu gösterir: 14, 15 ve 16 üç ayrı karardır ve kural yalnız her birinin sonucunu sayar, birini seçmez.

#### Kullanım: `#available` ve `@available`

Sürüm sınırını kodda ifade etmenin iki yolu vardır. `#available`, bir ifadenin içinde çalışma anında sürümü sınayan koşuldur. Dersin başındaki parça bunu kullanır: koşul doğruysa yeni arayüz, yanlışsa yedek dal çalışır. Koşulun sonundaki yıldız, listelenmeyen diğer platformlar için varsayılanı bildirir ve zorunludur.

`@available`, bir bildirimin üzerine yazılan işarettir ve o bildirimin hangi sürümden itibaren kullanılabildiğini söyler. İşaretli bir işlev ya da tür, yalnız o sürümde ya da sonrasında kullanılabilir; işareti taşımayan bir kod parçası onu çağırmak istediğinde ya kendisi aynı işareti taşımalı ya da çağrıyı bir `#available` koşuluna sarmalıdır. Böylece sürüm gereksinimi çağrı zinciri boyunca yukarı doğru taşınır ve en sonunda ya bir koşulla karşılanır ya da asgari sürümle zaten sağlanır.

İkisi arasında iş bölümü açıktır. `#available` kararı verir: şu an çalışılan sistemde arayüz var mı. `@available` gereksinimi bildirir: bu bildirim hangi sürümü ister. Asgari sürüm, `@available` gereksinimlerinin tümünü karşılıyorsa hiçbir `#available` koşulu gerekmez. Kesişim kısmiyse, yani arayüz desteklenen sürümlerin yalnız bir bölümünde varsa, `#available` gerekir ve yedek dal kesişimin dışında kalan sürümleri karşılar. Kesişimin boş olduğu ret durumunda ise `#available` de yetmez: arayüzü çağıran dal desteklenen hiçbir sürümde çalışmaz. `#available` kısmi kesişimin kod tarafındaki karşılığıdır; ret ise arayüzün bırakılması gerektiğini söyleyen bildirimdir.

**PY19.** Bu bölümdeki anlatım kod ölçümü içermez; `#available` ve `@available` davranışı yalnız anlatılmıştır, çalıştırılmamıştır.

#### Derleme Sürümü ile Asgari Sürümün Farkı

İki sürüm kavramı sık karıştırılır. Derleme sürümü, uygulamanın derlendiği platform kitinin sürümüdür ve uygulamanın hangi arayüzleri bildiğini belirler. Asgari sürüm ise uygulamanın çalışacağı en eski sistemdir. Derleme sürümü yeni, asgari sürüm eski olabilir; uygulama yeni arayüzleri bilir ama eski sistemlerde de çalışır. İşte `#available` bu boşluğu kapatmak için vardır: derleyici arayüzü tanır, ama çalışma anında sistemin onu taşıyıp taşımadığı belli değildir.

Bu fark ölçüme şöyle yansır: ilk küme uygulamanın asgari sürümü ile sınandığı en yeni sürüm arasındaki aralıktır, ikinci küme arayüzün bulunduğu sürümlerdir. Derleme sürümü bu kümelerden hiçbirine girmez; yalnız arayüzün kodda yazılabilmesini sağlar. Kesişim ise çalışma anında hangi sürümlerde arayüzün gerçekten var olduğunu söyler.

#### Kullanımdan Kaldırılan Arayüzler

Platform bir arayüzü zamanla yenisiyle değiştirir. Eski arayüz önce kullanımdan kaldırılmış (deprecated) olarak işaretlenir: çalışmaya devam eder ama derleyici uyarı verir. Sonra, uzun bir süre sonra, tamamen kaldırılabilir. Kaldırılmış bir arayüzün bulunduğu sürümler, kaldırıldığı sürümden itibaren destekleyen bir uygulamanın aralığıyla hiç kesişmez; yukarıdaki ayrık sürümler ölçümü bu durumu kurar. Asgari sürüm kararı bu geçişi etkiler. Yeni arayüz yalnız yeni sürümlerde varsa ve uygulama eski sürümleri de destekliyorsa, ikisini birlikte tutmak gerekir: yeni sürümde yeni arayüz, eski sürümde eski arayüz, ikisinin arasında bir `#available` koşulu.

Bu iki yolun bakım yükü açıktır. Asgari sürüm yeni arayüzün bulunduğu sürüme yükseltildiği anda eski yol silinebilir ve koşul gereksiz kalır. Aşağıdaki sınırlayıcı ölçümdeki `asgari-16` denemesi tam olarak budur: uygulamanın aralığı arayüzün aralığının içinde kalır ve buluşma ortadan kalkar. Silinen yedek dalın kodu da bakım yükünden çıkar.

#### Sürüm Sınırında Sınama

Her `#available` koşulu iki yol yaratır ve ikisi de sınanmalıdır. Yeni yol yeni sürümlü sistemde, yedek yol asgari sürümlü sistemde çalıştırılmalıdır. Sınanmamış bir yedek yol, tam da ihtiyaç duyulduğu anda, yani eski sürümlü bir kullanıcıda bozulur. Bu nedenle desteklenen en eski sürüm, geliştirme sırasında düzenli olarak sınanan bir hedef olmalıdır; yalnız en yeni sürümde sınanan bir uygulama, desteklediğini söylediği aralığı gerçekte desteklemiyor olabilir.

Sınama maliyeti, asgari sürüm kararının dördüncü ölçütü gibi çalışır: her desteklenen sürüm, sınanması gereken bir ortamdır. Kesişimin boş çıktığı ret durumu bu yüzden bir uyarı olarak da okunabilir: ortak sürüm yoksa, arayüzü kullanan yolu sınayacak bir ortam da yoktur; o yol desteklenen hiçbir sistemde çalışmaz ve koddan çıkarılmalıdır.

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

Asgari sürüm kararı bir buluşma değildir: hangi sürümün asgari olacağına kural değil karar verir. Kural yalnız kümeyi sayar. Aşağıdaki ölçümde aynı arayüz için asgari sürüm 14, 15 ve 16 olarak denenir; ilk ikisinde uygulamanın aralığı arayüzün aralığıyla kısmen örtüşür, üçüncüsünde tamamen onun içinde kalır.

```swift
// main.swift — asgari surum karari kuralin disinda kalir
let api = 16...19
let attempts = [
    Meeting("asgari-14", first: versionNames(14...17), second: versionNames(api),
            result: commonVersions(app: 14...17, api: api)),
    Meeting("asgari-15", first: versionNames(15...17), second: versionNames(api),
            result: commonVersions(app: 15...17, api: api)),
    Meeting("asgari-16", first: versionNames(16...17), second: versionNames(api),
            result: commonVersions(app: 16...17, api: api)),
]
for attempt in attempts {
    attempt.report()
}
```

```
asgari-14: ilk 14,15,16,17 | ikinci 16,17,18,19 | sonuc 16,17 -> kesisim
asgari-15: ilk 15,16,17 | ikinci 16,17,18,19 | sonuc 16,17 -> kesisim
asgari-16: ilk 16,17 | ikinci 16,17,18,19 | sonuc 16,17 -> olcum-disi
```

Üç denemede de ortak sürümler aynı: 16 ve 17. İlk ikisi kesişim, üçüncüsü ise uygulamanın aralığı arayüzün aralığının alt kümesi olduğu için ölçüm dışıdır. Yani asgari sürümü 14 ya da 15 yapmak sonucu değiştirmez, 16 yapmak ise buluşmayı ortadan kaldırır: artık bir `#available` denetimi gerekmez. Bu karar ölçülerek değil, destek maliyetiyle verilir; kural yalnız her seçimin sonucunu sayar, hangisinin seçileceğini söylemez.

**PY18.** Bu ölçüm üç asgari değer ve tek bir arayüz aralığı için. Her seçimin destek maliyeti ölçülmedi.

#### Özet

- Asgari desteklenen sürüm, uygulamanın çalışacağı en eski sürümdür ve bir karardır.
- Uygulamanın desteklediği ve arayüzün bulunduğu sürümler kesişir: 16 ve 17 kaldı.
- Aralıklar ayrıksa (ör. kaldırılmış bir arayüz) ortak sürüm kalmaz ve çözüm ret çıkar; `#available` da işe yaramaz, arayüz yenisiyle değiştirilir.
- Asgari sürümü 14 ya da 15 seçmek aynı kesişimi verir; 16 seçmek buluşmayı ölçüm dışına çıkarır.
- Kural kümeyi sayar, alt sınırı seçmez.
- Bu dersin çözümü: **kesişim**.

#### Sonraki Adım

Platformun yapısı burada bitiyor: katmanlar, çerçeveler, alan çerçeveleri, yaşam döngüsü, tasarım kılavuzları ve sürümler. Sıradaki konuda proje ile hedef buluşur: aynı ayar proje düzeyinde de hedef düzeyinde de tanımlanabilir ve geliştirme ortamı hangisinin geçerli olacağını bu buluşmayla çözer.

## Geliştirme Ortamı

Proje, hedef ve şema, proje dosya yapısı ve yapılandırma, paket yöneticisi ve sürüm kısıtları, simülatör ve aygıt farkı, kesme noktası, adımlama ve günlükle hata ayıklama temelleri, Objective-C ile birlikte çalışma.

### Geliştirme Ortamı

Source: https://academia.sh/tr/kurslar/ios-platformu/gelistirme-ortami

Proje, hedef ve çalıştırma şeması kavramlarında aynı derleme ayarının iki düzeyde tanımlanmasının, arama yollarının genişletilmesinin ve sürüm aralıklarının buluşmasının ölçümü.

Önceki konunun son dersi iki tanımı yan yana koydu: uygulamanın desteklediği asgari sürüm bir kümeyi, platform arayüzünün sürümü bir başka kümeyi tanımlıyordu ve geçerli olan, ikisinin kesişimiydi. Bu ders aynı soruyu geliştirme ortamının kendi katmanlarına taşır. Bir uygulamanın derlenmesi birden çok yerde yazılmış ayara dayanır: tek bir **proje (project)**, projenin altında bir ya da birkaç **hedef (target)** ve bunları belirli bir amaçla çalıştıran **çalıştırma şeması (scheme)**. Aynı ayar hem projede hem hedefte yazılabilir. İkisi çelişirse hangisi geçerlidir, ikisi birlikte mi geçerlidir, yoksa derleme hiç başlamaz mı?

Kursun ölçü ekseni bu sorudur: **iki tanım buluşunca nasıl çözülüyor?** Dört çözüm vardır. Tanımlardan biri ötekini ezerse çözüm öncelik, yalnız ortak kısım kalırsa kesişim, ikisi birleşirse birleşim, ikisi birlikte kabul edilemezse ret. Kursun açılış dersinde sekiz buluşmayı tek bir sınıflandırıcıyla ölçmüş, dağılımı 2/2/2/2 bulmuştuk. Bu ders o sınıflandırıcıyı değiştirmeden kullanır; yalnız bu dersin kendi buluşmalarını ölçer. Sınıflandırıcı kuralı sayar, niyeti okumaz: iki tanımın biri ötekinin alt kümesiyse ya da biri boşsa ortada bir buluşma yoktur ve ölçüm dışıdır.

#### Sınıflandırıcı

`Meeting` iki tanımı küme olarak tutar, çözümün sonucunu üçüncü bir küme olarak alır ve sonucu iki tanıma bakarak adlandırır. Sonuç elle yazılmaz; her ölçümde dersin kurduğu küçük bir modelden gelir. Çıktı satırı hep aynı biçimdedir: etiket, ilk tanım, ikinci tanım, sonuç ve çözüm.

```swift
// Meeting.swift — iki tanim bulusunca nasil cozuldugunu siniflandirir
struct Meeting {
    let label: String
    let first: Set<String>
    let second: Set<String>
    let result: Set<String>?

    init(_ label: String, first: Set<String>, second: Set<String>, result: Set<String>?) {
        self.label = label
        self.first = first
        self.second = second
        self.result = result
    }

    var resolution: String {
        if first.isEmpty || second.isEmpty
            || first.isSubset(of: second) || second.isSubset(of: first) {
            return "olcum-disi"
        }
        guard let result, !result.isEmpty else { return "ret" }
        if result == first.union(second) { return "birlesim" }
        if result == first.intersection(second) { return "kesisim" }
        if result == first || result == second { return "oncelik" }
        return "kural-disi"
    }

    static func list(_ values: Set<String>) -> String {
        values.isEmpty ? "bos" : values.sorted().joined(separator: ",")
    }

    func report() {
        let outcome = result.map(Meeting.list) ?? "yok"
        print("\(label): ilk \(Meeting.list(first)) | ikinci \(Meeting.list(second)) | sonuc \(outcome) -> \(resolution)")
    }
}

func summarize(_ meetings: [Meeting]) {
    for resolution in ["oncelik", "kesisim", "birlesim", "ret"] {
        let count = meetings.filter { $0.resolution == resolution }.count
        print("\(resolution): \(count)")
    }
}
```

#### Aynı Ayar İki Düzeyde

**Derleme ayarı (build setting)**, derleyiciye ve bağlayıcıya ne yapacağını söyleyen bir anahtar ve değer çiftidir: en iyileme düzeyi, asgari desteklenen sürüm, uyarıların açık ya da kapalı olması. Proje bu çiftleri tüm hedefler için varsayılan olarak tanımlar. Her hedef kendi derleme ayarlarını da taşır. Bir anahtar iki düzeyde de yazılmışsa platformun kuralı bellidir: hedefin değeri projeninkini ezer. Bir anahtar yalnız projede yazılmışsa hedef onu olduğu gibi devralır.

Bu davranışı küçük bir modelle kurarız. Model iki sözlüğü birleştirir ve çakışan anahtarlarda hedefin değerini tutar. Üç anahtar seçtik: iki düzeyde farklı değerlerle yazılan en iyileme düzeyi, iki düzeyde farklı değerlerle yazılan asgari sürüm ve yalnız projede yazılan uyarı düzeyi. Her anahtar için bir buluşma kurulur: ilk tanım projenin değeri, ikinci tanım hedefin değeri, sonuç modelin geçerli saydığı değerdir.

```swift
// main.swift — ayni anahtar iki duzeyde tanimliysa hedef duzeyi kazanir
func resolveSettings(project: [String: String], target: [String: String]) -> [String: String] {
    project.merging(target) { _, targetValue in targetValue }
}

func single(_ value: String?) -> Set<String> {
    value.map { [$0] } ?? []
}

func settingMeeting(_ key: String, project: [String: String], target: [String: String]) -> Meeting {
    let effectiveValues = resolveSettings(project: project, target: target)
    return Meeting(key, first: single(project[key]), second: single(target[key]), result: single(effectiveValues[key]))
}

let projectSettings = ["optimization": "none", "deployment": "15.0", "warnings": "all"]
let targetSettings = ["optimization": "speed", "deployment": "17.0"]

let settingMeetings = ["optimization", "deployment", "warnings"].map {
    settingMeeting($0, project: projectSettings, target: targetSettings)
}
for meeting in settingMeetings { meeting.report() }
summarize(settingMeetings)
```

```
optimization: ilk none | ikinci speed | sonuc speed -> oncelik
deployment: ilk 15.0 | ikinci 17.0 | sonuc 17.0 -> oncelik
warnings: ilk all | ikinci bos | sonuc all -> olcum-disi
oncelik: 2
kesisim: 0
birlesim: 0
ret: 0
```

İki farklı değer buluştuğunda hedefinki kazandı: iki buluşma **öncelik** çıktı. Üçüncü satır ise bir buluşma sayılmadı. Uyarı düzeyi hedefte hiç yazılmadığı için ikinci tanım boştu ve sınıflandırıcı bunu ölçüm dışı bıraktı. Bu doğru bir sonuçtur: yazılmamış bir ayar bir tanım değildir, yalnızca devralınmanın zeminidir.

Bu bulgunun sonucu pratik bir kuraldır. Projede yazılan bir ayarı hedefte yeniden yazmak, projedeki değeri yok sayar. Bir ayarı tüm hedeflerde tek yerden değiştirmek istiyorsanız hedeflerdeki aynı anahtarı kaldırmanız gerekir; aksi halde projedeki değişiklik hiçbir hedefe ulaşmaz.

**GO1.** Bu ölçüm anahtarı düz bir değer taşıyan ayarlar içindir. Değeri liste olan ayarlar bir sonraki bölümde ayrıca ölçülür. Değerin başka bir ayara referans veren biçimi ölçülmedi.

#### Devralma İşareti

Liste değerli ayarlar farklı davranır. Arama yolları buna iyi bir örnektir: derleyici başlık ve modül ararken bir dizin listesine bakar ve projeyle hedef bu listeye ayrı ayrı dizin ekleyebilir. Hedef kendi listesini yazarsa projenin listesini ezer. Ama hedef listesine devralma işaretini koyarsa, yani `$(inherited)` ifadesini yazarsa, platform bu işareti projenin listesiyle değiştirir. Aynı yazım yeri iki ayrı çözüme gider: işaret yoksa öncelik, işaret varsa birleşim.

Model bu değiştirmeyi bir düzleştirme olarak kurar: hedef listesindeki her eleman, işaretse projenin listesine, değilse kendisine açılır. Buluşmada ilk tanım projenin dizinleridir, ikinci tanım hedefin kendi yazdığı dizinler, yani işaretin dışındakiler.

```swift
// main.swift — devralma isareti ust duzeyin listesini hedef listesine katar
func expandPaths(project: [String], target: [String]) -> [String] {
    target.flatMap { $0 == "$(inherited)" ? project : [$0] }
}

func pathMeeting(_ label: String, project: [String], target: [String]) -> Meeting {
    let own = Set(target.filter { $0 != "$(inherited)" })
    return Meeting(label, first: Set(project), second: own, result: Set(expandPaths(project: project, target: target)))
}

let projectPaths = ["libs/shared", "libs/vendor"]
let withInherited = pathMeeting("with-inherited", project: projectPaths, target: ["$(inherited)", "src/local"])
let withoutInherited = pathMeeting("without-inherited", project: projectPaths, target: ["src/local"])
withInherited.report()
withoutInherited.report()
```

```
with-inherited: ilk libs/shared,libs/vendor | ikinci src/local | sonuc libs/shared,libs/vendor,src/local -> birlesim
without-inherited: ilk libs/shared,libs/vendor | ikinci src/local | sonuc src/local -> oncelik
```

Hedef listesinin içeriği iki durumda da `src/local` idi; fark yalnız işaretti. İşaretle birlikte sonuç üç dizindir ve çözüm **birleşim** çıktı. İşaret olmadan sonuç hedefin listesidir ve çözüm yine öncelik. Aynı alanda iki çözümün yan yana durabilmesi, ayarın türüne bakmadan çözümü tahmin etmenin yanıltıcı olacağını gösterir: düz değerde öncelik tek seçenektir, listede işaret iki seçenek arasında seçim yapar.

İşaretin yokluğu sessiz bir hata kaynağıdır. Proje düzeyinde eklenen bir üçüncü taraf dizini, hedefin listesi işaretsiz yazıldığı için derlemeye hiç girmez ve bunun belirtisi derleyicinin başlığı bulamamasıdır. Hata iletisi hedefin listesini değil eksik başlığı gösterir; bu yüzden bu tür sorunlar çoğu zaman yanlış yerde aranır.

**GO2.** Ölçüm arama yolları için ve iki düzeyli devralma için. Daha çok katmanlı devralma zincirinde işaretin her katmanda ayrı genişlediği ölçülmedi. İşaretin birden çok kez yazıldığı liste de ölçülmedi.

#### Platform Sürümleri

Asgari sürüm bir küme değil, tek bir değerdir. Dersin başındaki ölçüm bunu zaten gösterdi: proje 15.0, hedef 17.0 yazdığında geçerli değer 17.0 oldu ve çözüm öncelik çıktı. Proje ile hedef arasında sürümler kesişmez; hedefin asgari sürümü projeninkini ezer.

Kesişim başka bir buluşmada ortaya çıkar. Önceki konunun son dersinde uygulamanın desteklediği sürümleri, uygulamanın sınandığı sürümler olarak tanımlamıştık. Bu kümenin alt ucu hedefin geçerli asgari sürümüdür. Öteki tanım geliştirme ortamının elindeki **çalıştırma hedefleri (run destination)**, yani uygulamanın üzerinde çalıştırılabileceği simülatörlerin ve aygıtların sürümleridir. Uygulama ancak iki kümede birden bulunan bir sürümde çalıştırılabilir: desteklenmeyen sürümdeki bir simülatör çalıştırma için seçilemez, elde bulunmayan bir sürüm de sınanamaz. Model bu yüzden iki kümenin kesişimini alır.

```swift
// main.swift — uygulamanin destekledigi surumlerle calistirma hedeflerinin surumleri kesisir
func runnableVersions(supported: Set<String>, destinations: Set<String>) -> Set<String> {
    supported.intersection(destinations)
}

let appSupported: Set<String> = ["17", "18"]
let destinationVersions: Set<String> = ["15", "16", "17"]
let rangeMeeting = Meeting(
    "deployment-range",
    first: appSupported,
    second: destinationVersions,
    result: runnableVersions(supported: appSupported, destinations: destinationVersions)
)
rangeMeeting.report()
```

```
deployment-range: ilk 17,18 | ikinci 15,16,17 | sonuc 17 -> kesisim
```

Çözüm **kesişim**. Uygulama on sekizi destekliyor ama elde on sekiz sürümlü bir çalıştırma hedefi yok; elde on beş ve on altı sürümlü hedefler var ama uygulama onları desteklemiyor. İki küme yalnız on yedide buluşuyor. Bu kümenin boş çıkması da mümkündür: çalıştırma hedeflerinin hiçbiri desteklenen bir sürümde değilse sonuç boş olur ve çözüm retten başka bir şey olamaz. Böyle bir durumda uygulama derlenebilir, ama çalıştırılacak bir hedef bulunmadığı için hata ayıklama oturumu açılmaz.

**GO3.** Ölçüm sürümleri kesin değerlerden oluşan bir küme olarak ele aldı. Sürüm aralıklarının üst sınırla yazıldığı ve ondalık sürümlerin karşılaştırıldığı biçim bu derste ölçülmedi; paket yöneticisi dersinin konusudur.

#### Çalıştırma Şeması

Şema bu üç ayardan farklı bir şey yapar. Proje ve hedef ayar tanımlar; şema ise bir eylem için, örneğin çalıştırma ya da arşivleme için, hangi **derleme yapılandırmasının (build configuration)** kullanılacağını söyler. Yapılandırma, ayarların bir paketidir: hata ayıklama yapılandırması en iyilemeyi kapatır, yayın yapılandırması açar. Şema ayar tanımlamaz; hedeflerin zaten taşıdığı yapılandırmalardan birini seçer.

Bu fark ölçülebilir. Şemanın seçimini de iki küme olarak yazabiliriz: ilk tanım projenin sunduğu yapılandırmalar, ikinci tanım şemanın seçtiği. Seçim, sunulanın bir parçasıdır.

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

Şema bir tanım değil seçim olduğu için bu iddia bir ölçümle sınanmalıdır. Aşağıdaki modelde proje iki yapılandırma sunar ve şema iki eylem için birer yapılandırma seçer. Her eylem bir buluşma olarak kurulur; sınıflandırıcı seçimi bir tanım gibi ele alırsa bir çözüm adı bulması gerekir.

```swift
// main.swift — sema bir tanim degil secimdir; iki tanim yoksa bulusma yoktur
func pickConfiguration(action: String, scheme: [String: String]) -> String {
    scheme[action, default: "yok"]
}

let available: Set<String> = ["debug", "release"]
let scheme = ["run": "debug", "archive": "release"]

let schemeMeetings = ["run", "archive"].map { action -> Meeting in
    let chosen = pickConfiguration(action: action, scheme: scheme)
    return Meeting("scheme-" + action, first: available, second: [chosen], result: [chosen])
}
for meeting in schemeMeetings { meeting.report() }
summarize(schemeMeetings)
```

```
scheme-run: ilk debug,release | ikinci debug | sonuc debug -> olcum-disi
scheme-archive: ilk debug,release | ikinci release | sonuc release -> olcum-disi
oncelik: 0
kesisim: 0
birlesim: 0
ret: 0
```

İki eylem de ölçüm dışı kaldı ve dört sayaç da sıfırda durdu. Seçilen yapılandırma her seferinde sunulanın bir alt kümesiydi; bu yüzden sınıflandırıcı bir buluşma görmedi. Şemanın geçerli ayarlar üzerindeki etkisi gerçektir, ama bu etki bir buluşmanın çözümü değil, bir seçimdir: bir yapılandırmayı seçmek, o yapılandırmanın ayarlarını devreye sokar ve ayarların kendi buluşmaları bu bölümün başındaki kurallarla çözülür.

Bu, dersin kuralının sınırını çizer. Kural iki tanım bulunduğunda çözümü adlandırır; tanım bulunmayan yerde sessiz kalır. Şemada bir ayarın yazılması mümkündür, örneğin bir ortam değişkeni, ancak o durumda şema bir tanım sahibi olur ve yeni bir buluşma doğar. Ortam değişkenleri hata ayıklama konusunun işidir.

**GO4.** Bu ölçüm şemanın seçim yapan hâli içindir. Şemanın kendi ayarlarını taşıdığı hâl ve bir eylemin birden çok yapılandırmayı birlikte kullandığı durum ölçülmedi.

#### Özet

- Aynı derleme ayarı projede ve hedefte tanımlıysa hedef düzeyi kazanır; yalnız projede yazılan ayar hedefe devralınır ve buluşma doğurmaz.
- Liste değerli ayarlarda devralma işareti çözümü belirler: işaretle birleşim, işaretsiz öncelik.
- Asgari sürüm tek değerlidir ve hedefte ezilir; uygulamanın desteklediği sürümlerle çalıştırma hedeflerinin sürümlerinin kesişimi uygulamanın çalıştırılabildiği sürümlerdir, kesişim boşsa sonuç ret olur.
- Çalıştırma şeması bir tanım değil bir seçimdir; seçilen yapılandırma sunulanın alt kümesi olduğu için buluşma saymaz.
- Bu dersin çözümü: **öncelik**. Aynı derleme ayarı iki düzeyde tanımlandığında hedef düzeyi projenin değerini ezer.

#### Sonraki Adım

Bu derste projenin ve hedefin ayarları buluştu. Bir uygulamanın ayarları yalnız bu iki yerde durmaz: şablondan gelen bir özellik listesi, derlemenin kendi ürettiği anahtarlar ve aynı hedefe eklenen kaynak grupları da aynı soruyu doğurur. Bir sonraki ders proje dosya yapısında şablonun yazdığı anahtarlarla derlemenin ürettiği anahtarların buluşmasını ölçer.

### Proje Dosya Yapısı

Source: https://academia.sh/tr/kurslar/ios-platformu/proje-dosya-yapisi

Şablonun yazdığı özellik listesi anahtarları ile derlemenin ürettiği anahtarların, aynı adlı kaynak dosyaların, aygıta özgü varlıkların ve yerelleştirmelerin buluşmasının ölçümü.

Önceki ders projenin ve hedefin derleme ayarlarını buluşturdu: aynı anahtar iki yerde yazıldığında hedef kazandı, liste değerlerinde devralma işareti birleşimi seçti. Bir uygulamanın dosya yapısı bu iki katmandan daha fazlasını taşır. Proje bir şablondan doğar ve şablon bir **özellik listesi (Info.plist)** yazar. Derleme bu listeye kendi anahtarlarını ekler. Kaynak dosyalar ve görseller hedeflere eklenir, ayrı bir dil için ayrı bir klasör açılır. Dosya sistemindeki her düzen yeni bir buluşma yeri doğurur.

Ölçü ekseni aynı kalır: **iki tanım buluşunca nasıl çözülüyor?** Çözüm dört biçimden biridir: öncelik, kesişim, birleşim ya da ret. Bu derste dosya yapısının dört yerini ölçeriz ve her birinin hangi çözüme düştüğünü sınıflandırıcıya bırakırız. Sınıflandırıcı önceki dersle aynıdır; kuralı değişmemiştir. Biri ötekinin alt kümesi olan ya da biri boş olan iki tanım bir buluşma sayılmaz.

#### Sınıflandırıcı

Aşağıdaki sınıf iki tanımı küme olarak alır, çözümün sonucunu üçüncü küme olarak alır ve çözümü adlandırır. Sonuç elle yazılmaz; her ölçümde bir modelden gelir.

```swift
// Meeting.swift — iki tanim bulusunca nasil cozuldugunu siniflandirir
struct Meeting {
    let label: String
    let first: Set<String>
    let second: Set<String>
    let result: Set<String>?

    init(_ label: String, first: Set<String>, second: Set<String>, result: Set<String>?) {
        self.label = label
        self.first = first
        self.second = second
        self.result = result
    }

    var resolution: String {
        if first.isEmpty || second.isEmpty
            || first.isSubset(of: second) || second.isSubset(of: first) {
            return "olcum-disi"
        }
        guard let result, !result.isEmpty else { return "ret" }
        if result == first.union(second) { return "birlesim" }
        if result == first.intersection(second) { return "kesisim" }
        if result == first || result == second { return "oncelik" }
        return "kural-disi"
    }

    static func list(_ values: Set<String>) -> String {
        values.isEmpty ? "bos" : values.sorted().joined(separator: ",")
    }

    func report() {
        let outcome = result.map(Meeting.list) ?? "yok"
        print("\(label): ilk \(Meeting.list(first)) | ikinci \(Meeting.list(second)) | sonuc \(outcome) -> \(resolution)")
    }
}

func summarize(_ meetings: [Meeting]) {
    for resolution in ["oncelik", "kesisim", "birlesim", "ret"] {
        let count = meetings.filter { $0.resolution == resolution }.count
        print("\(resolution): \(count)")
    }
}
```

#### Şablon ve Üretilen Anahtarlar

Yeni bir proje bir şablondan doğar ve şablon, uygulamanın özellik listesine bir dizi anahtar yazar: görünen ad, açılış ekranı, desteklenen ekran yönleri. Dosya, anahtar ve değer çiftlerinden oluşan bir XML belgesidir. Derleme ise listeye kendi anahtarlarını ekler: çalıştırılabilir dosyanın adı, hedef platform, derleme sürümü. Kaynak olarak bu iki yazarın katkısı tek bir dosyada görünmez; şablonun yazdığı kısım proje dizininde durur, derlemenin ürettiği kısım ise paketin içinde sonradan belirir.

Aşağıdaki parça şablonun yazdığı bir özellik listesinin kısaltılmış biçimidir. Dosyanın adı `Info.plist` olur ve ilk üç anahtar şablondan gelir.

```xml
<dict>
    <key>CFBundleDisplayName</key>
    <string>Demo</string>
    <key>UILaunchScreen</key>
    <dict/>
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
    </array>
</dict>
```

Dosya bir ölçüm sonucu üretmediği için yalnız kaynak olarak durur. Model bu dosyanın anahtarlarını düz adlarla yeniden kurar: şablon `display-name`, `launch-screen` ve `orientation` anahtarlarını yazar; derleme `executable`, `platform` ve `version` anahtarlarını üretir. İki küme ortak anahtar taşımıyor. Soru, sonuçta paketin içinde hangi anahtarların bulunduğudur.

```swift
// main.swift — sablonun yazdigi anahtarlar ile derlemenin urettigi anahtarlar birlesir
func mergeProperties(template: [String: String], generated: [String: String]) -> [String: String] {
    template.merging(generated) { _, generatedValue in generatedValue }
}

let templateEntries = ["display-name": "Demo", "launch-screen": "main", "orientation": "portrait"]
let generatedEntries = ["executable": "demo", "platform": "ios", "version": "1.0.42"]
let combinedEntries = mergeProperties(template: templateEntries, generated: generatedEntries)

let propertyMeeting = Meeting(
    "properties",
    first: Set(templateEntries.keys),
    second: Set(generatedEntries.keys),
    result: Set(combinedEntries.keys)
)
propertyMeeting.report()
```

```
properties: ilk display-name,launch-screen,orientation | ikinci executable,platform,version | sonuc display-name,executable,launch-screen,orientation,platform,version -> birlesim
```

Sonuç iki kümenin birleşimidir ve çözüm **birleşim** çıktı. Şablon ile derleme birbirinin yerini almaz; ikisi aynı dosyada yan yana durur. Bu, ayarlardaki öncelikten farklıdır: bir önceki derste hedef projenin değerini eziyordu, burada iki yazar farklı alanlara yazdığı için kimse kimseyi ezmez.

**GO5.** Ölçüm iki yazarın ayrık anahtar kümeleriyle yazdığı durumu kapsar. Ayrık olmayan durum, yani aynı anahtarın iki yazarda bulunması, bu dersin sınırlayıcı ölçümünde ayrıca ele alınır.

#### Aynı Adlı Kaynak Dosya

Kaynak dosyalar hedeflere üyelikle bağlanır. Aynı dosya iki hedefin üyesi olabilir; bu bir paylaşımdır ve sorun çıkarmaz, çünkü dosya tek bir dosyadır. Her hedef kendi paketine kopyalandığı için, iki ayrı hedefte aynı adı taşıyan iki ayrı dosya da çakışmaz. Sorun, adları aynı olan iki ayrı dosyanın aynı hedefe eklenmesiyle çıkar; örneğin projenin iki ayrı klasöründe duran iki `strings.txt`. Hedefin pakete kopyalanan kaynakları düz bir dizinde durur; iki dosya aynı adı taşırsa ikisi de aynı çıktı yolunu ister ve ikincisi birincinin üstüne yazılamaz. Derleme bu durumu kopyalama adımında, aynı çıktıyı birden çok komutun ürettiğini söyleyen bir hata olarak bildirir.

Model bu davranışı şöyle kurar: aynı hedefe eklenen iki kaynak grubunun adları ayrıksa sonuç iki kümenin birleşimidir; kesişiyorlarsa sonuç yoktur. Sonuç yokluğu, sınıflandırıcıda ret demektir. Aşağıda aynı hedefe eklenen üç kaynak grubu var. Ana grup ile karşılama ekranı grubu `strings.txt` adını paylaşıyor; ayarlar grubu ise öbür iki grupla hiçbir adı paylaşmıyor.

```swift
// main.swift — ayni hedefe ayni adla eklenen iki kaynak dosya kopyalamada cakisir
func copyResources(_ first: Set<String>, _ second: Set<String>) -> Set<String>? {
    first.isDisjoint(with: second) ? first.union(second) : nil
}

let mainGroup: Set<String> = ["logo.png", "strings.txt"]
let onboardingGroup: Set<String> = ["badge.png", "strings.txt"]
let settingsGroup: Set<String> = ["icon.png", "legal.txt"]

let clash = Meeting("same-name", first: mainGroup, second: onboardingGroup, result: copyResources(mainGroup, onboardingGroup))
let distinct = Meeting("distinct-names", first: mainGroup, second: settingsGroup, result: copyResources(mainGroup, settingsGroup))
clash.report()
distinct.report()
```

```
same-name: ilk logo.png,strings.txt | ikinci badge.png,strings.txt | sonuc yok -> ret
distinct-names: ilk logo.png,strings.txt | ikinci icon.png,legal.txt | sonuc icon.png,legal.txt,logo.png,strings.txt -> birlesim
```

Ortak ad olduğunda sonuç `yok` yazıldı ve çözüm **ret** çıktı. Ortak ad olmadığında kümeler birleşti. Ret ile birleşim arasındaki fark tek bir koşula bağlı: adların ayrık olması. Tanımların ne kadar benzediği ya da iki dosyanın içeriğinin aynı olması sonucu değiştirmez. Aynı içeriğe sahip iki dosya yine reddedilir, çünkü kopyalama içeriğe değil ada bakar.

Çözüm bellidir: ya dosyalardan biri yeniden adlandırılır ya da hedeften çıkarılır. İki dosyanın içeriği aynıysa ikisi tek bir dosyada birleştirilir; başka bir hedefin de o dosyaya ihtiyacı varsa dosya üyelik yoluyla paylaşılır. Bu, dosyayı tek bir yerde tutmanın doğal sonucudur ve çakışmayı kaynağında ortadan kaldırır.

**GO6.** Ölçüm ad çakışmasını dosya düzeyinde ele aldı. Aynı adlı dosyaların farklı alt dizinlerde durduğu ve paketin bu dizinleri koruduğu hâl ölçülmedi.

#### Aygıta Özgü Varlık

**Varlık kataloğu (asset catalog)**, görselleri ve renkleri adlandırılmış kümeler halinde tutar. Bir görsel kümesi birden çok biçimi tek adın altında toplayabilir: genel bir biçim ve belirli bir aygıt sınıfı için özel bir biçim. Platform çalışma zamanında aygıta uyan biçimi seçer; özel bir biçim varsa genel biçimin yerine o seçilir. Telefon için yazılmış özel biçim, telefonda genel biçimin yerini alır.

Model bu seçimi bir arama olarak kurar: özel biçim varsa onu, yoksa genel olanı döndürür. İki buluşma yazarız: telefon için özel bir biçim tanımlıdır, tablet için tanımlı değildir.

```swift
// main.swift — aygita ozgu varlik genel varliga ustun gelir
func assetMeeting(_ idiom: String, variants: [String: String]) -> Meeting {
    let general: Set<String> = ["icon-universal"]
    let specific: Set<String> = variants[idiom].map { [$0] } ?? []
    let picked = variants[idiom] ?? "icon-universal"
    return Meeting("icon-" + idiom, first: general, second: specific, result: [picked])
}

let iconVariants = ["phone": "icon-phone"]
let assetMeetings = ["phone", "tablet"].map { assetMeeting($0, variants: iconVariants) }
for meeting in assetMeetings { meeting.report() }
```

```
icon-phone: ilk icon-universal | ikinci icon-phone | sonuc icon-phone -> oncelik
icon-tablet: ilk icon-universal | ikinci bos | sonuc icon-universal -> olcum-disi
```

Telefon için özel biçim tanımlı olduğunda iki tanım buluştu ve özel olan kazandı: çözüm **öncelik**. Tablet için özel biçim yok; ikinci tanım boş kaldığı için sınıflandırıcı bunu bir buluşma saymadı. Tablet yine de bir simge gösterir, çünkü genel biçim devreye girer. Bir tanımın yokluğu bir çözüm doğurmaz, yalnızca öteki tanımın tek başına geçerli olmasına izin verir.

Bu düzen, genel bir biçimin her zaman bulunması gerektiği sonucunu getirir. Yalnız özel biçimleri olan bir küme, tanımlı olmayan bir aygıt sınıfında görsel bulamaz. Genel biçim bir güvenlik ağıdır ve özel biçimler onun üstüne yazılan istisnalardır.

**GO7.** Ölçüm iki aygıt sınıfı ve tek bir özel biçim için. Birden çok özel biçimin aynı aygıt sınıfında bulunduğu durum, örneğin ölçek çarpanına göre ayrılan biçimler, ölçülmedi.

#### Yerelleştirme

**Yerelleştirme (localization)**, bir uygulamanın metinlerini dil başına ayrı klasörlerde tutar. Uygulama hangi dilleri sunduğunu bu klasörlerin adlarıyla bildirir. Kullanıcı ise ayarlarında tercih ettiği dilleri bir sırayla belirtir. Platform bu iki tanımı buluşturur ve uygulamada gerçekten bulunan, kullanıcının da tercih ettiği dillerden birini seçer. İkisinden birinde olmayan dil seçilemez.

Model bu buluşmayı kümelerin kesişimi olarak kurar. Sıralamayı bir kenara bırakır, yalnız hangi dillerin ortak olduğuna bakar.

```swift
// main.swift — kullanicinin tercih ettigi diller ile uygulamanin yerellestirmeleri kesisir
func matchingLanguages(preferred: Set<String>, localized: Set<String>) -> Set<String> {
    preferred.intersection(localized)
}

let preferredLanguages: Set<String> = ["de", "fr", "tr"]
let localizedLanguages: Set<String> = ["en", "tr", "de"]

let languageMeeting = Meeting(
    "languages",
    first: preferredLanguages,
    second: localizedLanguages,
    result: matchingLanguages(preferred: preferredLanguages, localized: localizedLanguages)
)
languageMeeting.report()
```

```
languages: ilk de,fr,tr | ikinci de,en,tr | sonuc de,tr -> kesisim
```

Kullanıcı Fransızcayı tercih ediyor ama uygulamada Fransızca klasörü yok; uygulama İngilizceyi sunuyor ama kullanıcı onu tercih listesine yazmamış. Geçerli olan yalnız ikisinin ortak dilleridir ve çözüm **kesişim** çıktı. Kesişim boş olursa uygulamanın geliştirme dili devreye girer; sınıflandırıcı için bu bir retten farklı değildir, çünkü sonuçta iki tanımın ortak bir değeri yoktur.

**GO8.** Ölçüm yalnız hangi dillerin ortak olduğunu sayar. Kullanıcının tercih sırası ve bölgeye bağlı dil varyantları (aynı dilin iki bölgesel biçimi) ölçülmedi.

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

İlk ölçümde şablon ile derlemenin anahtar kümeleri ayrıktı. Gerçek projelerde ayrık olmayabilirler: şablon sürüm anahtarına bir değer yazar, derleme aynı anahtara kendi değerini yazar. Birleşim bu durumda ne yapar? Sınıflandırıcının kuralı anahtarları sayar. Aynı veriyi iki gözle ölçelim: biri anahtarlara, biri değerlere bakar.

```swift
// main.swift — birlesim anahtarlari sayar, ayni anahtarin degerlerini saymaz
let templateValues = ["display-name": "Demo", "version": "1.0"]
let generatedValues = ["executable": "demo", "version": "1.0.42"]
let combined = templateValues.merging(generatedValues) { _, generatedValue in generatedValue }

let keyView = Meeting(
    "key-view",
    first: Set(templateValues.keys),
    second: Set(generatedValues.keys),
    result: Set(combined.keys)
)
let valueView = Meeting(
    "value-view",
    first: Set(templateValues.values),
    second: Set(generatedValues.values),
    result: Set(combined.values)
)
keyView.report()
valueView.report()
print("version:", combined["version"] ?? "yok")
```

```
key-view: ilk display-name,version | ikinci executable,version | sonuc display-name,executable,version -> birlesim
value-view: ilk 1.0,Demo | ikinci 1.0.42,demo | sonuc 1.0.42,Demo,demo -> kural-disi
version: 1.0.42
```

Anahtarlara bakıldığında sonuç iki kümenin birleşimidir: çözüm birleşim. Değerlere bakıldığında aynı veri hiçbir çözüme uymuyor ve sınıflandırıcı `kural-disi` yazıyor. Şablonun yazdığı `1.0` değeri sonuçta yok; çakışan anahtarda derlemenin değeri kaldı ve şablonun değeri kayboldu. Birleşim yalnız anahtarların birleşimini garanti eder; çakışan bir anahtar için hangi değerin kalacağı ayrı bir öncelik kuralıdır.

Sonuç şu: bir dosyada birleşim gördüğünüzde bunun anahtarlar için doğru olduğunu, çakışan anahtarların değerleri için ise ayrıca bir öncelik arandığını bilmeniz gerekir. Önceki ders bu önceliği aynı anahtarın iki düzeyinde ölçmüştü; burada aynı kural özellik listesinin içinde yeniden karşımıza çıkıyor.

#### Özet

- Şablonun yazdığı anahtarlar ile derlemenin ürettiği anahtarlar ayrık olduğunda iki küme birleşir; hiçbiri ötekini ezmez.
- Aynı hedefe aynı adla eklenen iki kaynak dosya kopyalamada çakışır ve sonuç reddedilir; ayrı hedeflerdeki aynı ad çakışmaz. Ret yalnız adların ayrık olup olmadığına bağlıdır.
- Aygıta özgü varlık genel varlığa üstündür; özel biçim yoksa genel olan tek başına geçerlidir ve buluşma doğmaz.
- Kullanıcının tercih ettiği diller ile uygulamanın yerelleştirmeleri kesişir.
- Bu dersin çözümü: **birleşim**. Şablonun yazdığı ve derlemenin ürettiği anahtarlar aynı özellik listesinde birleşir, ancak birleşim anahtarları sayar, çakışan anahtarın değerini değil.

#### Sonraki Adım

Bu derste yerel dosyaların ve anahtarların buluşması ölçüldü. Uygulamaya giren her şey yerel değildir: dışarıdan paketler gelir ve her paket kendi bağımlılıkları için sürüm aralığı ister. İki paketin aynı bağımlılık için farklı sürüm aralıkları istemesi, bu dersteki ayrık ve ortak küme sorusunun bir benzeridir. Bir sonraki ders iki paketin sürüm aralığının buluşmasını ölçer.

### Paket Yöneticisi

Source: https://academia.sh/tr/kurslar/ios-platformu/paket-yoneticisi

İki paketin aynı bağımlılık için istediği sürüm aralıklarının kesişiminin, yerel paketin uzak paketin yerini almasının ve paket ürünlerinin birleşiminin ölçümü.

Önceki ders yerel dosyaların ve anahtarların buluşmasını ölçtü. Bir uygulama yalnız kendi dosyalarından oluşmaz; başkalarının yazdığı kodu **paket (package)** olarak alır. Platformun **paket yöneticisi (package manager)** her paketin **bağımlılık (dependency)** listesini okur, bu bağımlılıkları indirir ve her biri için hangi sürümün kullanılacağına karar verir. Karar zor bir yerde çıkar: iki paket aynı üçüncü paketi ister ve her biri o paketin farklı sürümlerini kabul eder.

Ölçü ekseni değişmez: iki tanım buluşunca nasıl çözülüyor? Bu derste buluşan tanımlar paketlerin sürüm kısıtlarıdır. Bir sürüm kısıtı, kabul edilen sürümlerin bir kümesini anlatır; iki kısıt buluştuğunda sınıflandırıcı sonucun hangi çözüme düştüğünü söyler.

#### Sınıflandırıcı

```swift
// Meeting.swift — iki tanim bulusunca nasil cozuldugunu siniflandirir
struct Meeting {
    let label: String
    let first: Set<String>
    let second: Set<String>
    let result: Set<String>?

    init(_ label: String, first: Set<String>, second: Set<String>, result: Set<String>?) {
        self.label = label
        self.first = first
        self.second = second
        self.result = result
    }

    var resolution: String {
        if first.isEmpty || second.isEmpty
            || first.isSubset(of: second) || second.isSubset(of: first) {
            return "olcum-disi"
        }
        guard let result, !result.isEmpty else { return "ret" }
        if result == first.union(second) { return "birlesim" }
        if result == first.intersection(second) { return "kesisim" }
        if result == first || result == second { return "oncelik" }
        return "kural-disi"
    }

    static func list(_ values: Set<String>) -> String {
        values.isEmpty ? "bos" : values.sorted().joined(separator: ",")
    }

    func report() {
        let outcome = result.map(Meeting.list) ?? "yok"
        print("\(label): ilk \(Meeting.list(first)) | ikinci \(Meeting.list(second)) | sonuc \(outcome) -> \(resolution)")
    }
}

func summarize(_ meetings: [Meeting]) {
    for resolution in ["oncelik", "kesisim", "birlesim", "ret"] {
        let count = meetings.filter { $0.resolution == resolution }.count
        print("\(resolution): \(count)")
    }
}
```

Bu derste sürüm kümelerini kurmak için küçük bir yardımcı daha gerekir. Kısıt, bir alt sınır ve bir üst sınırdan oluşur; alt sınır dâhil, üst sınır hariçtir. Yardımcı bir sürüm metnini sayılara ayırır, iki sürümü sırayla karşılaştırır ve bir kısıtın kabul ettiği sürümleri mevcut sürümler arasından süzer. Dosya yalnız tanım içerir, çıktısı yoktur.

```swift
// Requirement.swift — bir surum kisitinin kabul ettigi surumleri sayar
func versionKey(_ text: String) -> [Int] {
    text.split(separator: ".").compactMap { Int($0) }
}

func isBefore(_ left: String, _ right: String) -> Bool {
    versionKey(left).lexicographicallyPrecedes(versionKey(right))
}

struct Requirement {
    let from: String
    let upTo: String

    func accepts(_ version: String) -> Bool {
        !isBefore(version, from) && isBefore(version, upTo)
    }
}

func accepted(_ requirement: Requirement, among available: [String]) -> Set<String> {
    Set(available.filter { requirement.accepts($0) })
}

func rangeMeeting(_ label: String, _ first: Requirement, _ second: Requirement, available: [String]) -> Meeting {
    let left = accepted(first, among: available)
    let right = accepted(second, among: available)
    return Meeting(label, first: left, second: right, result: left.intersection(right))
}
```

#### Sürüm Aralıkları

Paketler **anlamlı sürümleme (semantic versioning)** ile numaralanır: üç sayı, nokta ile ayrılmış. İlk sayı uyumsuz değişiklikte, ikincisi uyumlu yeni özellikte, üçüncüsü düzeltmede artar. Bir paket başka bir paketi istediğinde genellikle bir **sürüm aralığı (version range)** yazar: "bire eşit ya da büyük, ikiden küçük". Üst sınır, uyumsuz değişikliğin geldiği sürümden önce durur.

Paket tanımı `Package.swift` dosyasında yazılır. Dosya platformun paket tanım çerçevesini kullandığı için yalnız kaynak olarak durur. Bağımlılık satırı bir adres ve bir aralık taşır.

```swift
import PackageDescription

let package = Package(
    name: "Formatter",
    dependencies: [
        .package(url: "https://example.com/parser.git", "1.0.0"..<"2.0.0")
    ],
    targets: [
        .target(name: "Formatter", dependencies: [.product(name: "Parser", package: "parser")])
    ]
)
```

Bu dosya bir ölçüm sonucu üretmez. İkinci bir paket aynı bağımlılığı `"1.2.0"..<"3.0.0"` aralığıyla ister. İkisini ölçmek için paketin yayımladığı sürümleri sabit bir liste olarak alırız: `1.0.0` ile `2.1.0` arasında altı sürüm. Her aralık bu listeden süzülür ve iki kümenin buluşması sınıflandırıcıya verilir. Önce kesişen iki aralık, sonra ayrık iki aralık.

```swift
// main.swift — iki paketin ayni bagimlilik icin istedigi surum araliklari
let published = ["1.0.0", "1.1.0", "1.2.0", "1.3.0", "2.0.0", "2.1.0"]

let overlapping = rangeMeeting(
    "overlapping",
    Requirement(from: "1.0.0", upTo: "2.0.0"),
    Requirement(from: "1.2.0", upTo: "3.0.0"),
    available: published
)
let disjoint = rangeMeeting(
    "disjoint",
    Requirement(from: "1.0.0", upTo: "1.2.0"),
    Requirement(from: "2.0.0", upTo: "3.0.0"),
    available: published
)
overlapping.report()
disjoint.report()
```

```
overlapping: ilk 1.0.0,1.1.0,1.2.0,1.3.0 | ikinci 1.2.0,1.3.0,2.0.0,2.1.0 | sonuc 1.2.0,1.3.0 -> kesisim
disjoint: ilk 1.0.0,1.1.0 | ikinci 2.0.0,2.1.0 | sonuc bos -> ret
```

**GO9.** Ölçüm alt sınırı dâhil, üst sınırı hariç bir aralık biçimi için yapıldı. Tek sürüme sabitlenen kısıt ve yalnız alt sınır taşıyan kısıt ölçülmedi. Sürüm listesi yayımlanmış altı sürümle sınırlıdır.

İlk buluşmada iki aralığın ortak sürümleri `1.2.0` ve `1.3.0`: çözüm **kesişim**. İkinci buluşmada hiçbir ortak sürüm yok: sonuç boş ve çözüm **ret**. Platformun paket yöneticisi bu durumda çözümlemeyi durdurur ve hangi iki kısıtın çeliştiğini bildirir.

Ret bir hata değil, bir bilgidir. Bağımlılık listesinde çelişen iki kısıt vardır ve ikisinden birinin değişmesi gerekir. Paket yöneticisi hangisinin gevşetileceğini seçemez; bu karar paket yazarına aittir.

#### Yerel Paket

Geliştirme sırasında bir bağımlılığı yayımlanmış sürümüyle değil, diskteki bir dizinle kullanmak mümkündür. Böyle bir **yerel paket (local package)** aynı ada sahip uzak paketin yerini alır. Platform yerel olanı seçer; uzak sürümü hiç indirmez. Bağımlılığın kaynağını değiştirip hemen sonucu görmek için yeni bir sürüm yayımlamaya gerek kalmaz.

Model bunu bir seçim işleviyle kurar: yerel kaynak varsa onu, yoksa uzak kaynağı döndürür. İki paket için ölçeriz: biri yerel olarak da bulunuyor, diğeri yalnız uzakta.

```swift
// main.swift — yerel paket ayni adli uzak paketin yerini alir
func chooseSource(remote: String, local: String?) -> String {
    local ?? remote
}

func sourceMeeting(_ label: String, local: String?) -> Meeting {
    let chosen = chooseSource(remote: "remote", local: local)
    return Meeting(label, first: ["remote"], second: local.map { [$0] } ?? [], result: [chosen])
}

let sourceMeetings = [
    sourceMeeting("parser-package", local: "local"),
    sourceMeeting("lexer-package", local: nil)
]
for meeting in sourceMeetings { meeting.report() }
```

```
parser-package: ilk remote | ikinci local | sonuc local -> oncelik
lexer-package: ilk remote | ikinci bos | sonuc remote -> olcum-disi
```

Yerel kaynak tanımlı olduğunda iki kaynak buluştu ve yerel olan kazandı: **öncelik**. Tanımlı olmadığında ikinci tanım boştu ve ortada bir buluşma yoktu. Aynı ad iki kaynakta bulunduğunda uzak sürümün kısıtları da devre dışı kalır; yerel paket hangi sürümde olursa olsun kabul edilir. Bu yüzden unutulmuş bir yerel paket, kısıtların beklenmedik biçimde göz ardı edilmesine yol açabilir.

**GO10.** Ölçüm iki kaynak çeşidini, yerel ve uzak olanı karşılaştırdı. İki farklı uzak adresin aynı paket adını taşıdığı durum ölçülmedi.

#### Paket Ürünleri

Bir paket yalnız kodunu değil, **ürün (product)** adı verilen bağlanabilir çıktıları da bildirir: bir kütüphane, bir çalıştırılabilir dosya. Uygulama hedefi bağlanacak ürünleri adıyla seçer. İki paketin ürünleri uygulama için tek bir kullanılabilir ürün kümesi oluşturur. Bir ürün adını iki paket de sunuyorsa küme o adı iki kez taşımaz.

Model iki paketin ürün kümesinin birleşimini alır. Birinci paket `parser` ve `tokens` ürünlerini, ikinci paket `formatter` ve `tokens` ürünlerini sunar.

```swift
// main.swift — iki paketin urunleri tek kumede birlesir
func availableProducts(_ first: Set<String>, _ second: Set<String>) -> Set<String> {
    first.union(second)
}

let parserProducts: Set<String> = ["parser", "tokens"]
let formatterProducts: Set<String> = ["formatter", "tokens"]

let productMeeting = Meeting(
    "products",
    first: parserProducts,
    second: formatterProducts,
    result: availableProducts(parserProducts, formatterProducts)
)
productMeeting.report()
```

```
products: ilk parser,tokens | ikinci formatter,tokens | sonuc formatter,parser,tokens -> birlesim
```

Üç ürün çıktı ve çözüm **birleşim**. Ortak `tokens` adı çakışma doğurmadı, çünkü bu ölçümde ürün adı bir kimliktir ve küme aynı kimliği iki kez saymaz. Gerçek bir projede iki ayrı paketin aynı adı taşıyan iki farklı ürünü olması başka bir sorundur; model bu durumu ayırt etmez.

**GO11.** Ölçüm ürün adını kimlik olarak ele aldı. Aynı adı taşıyan iki farklı ürünün çakışması ve bir ürünün yalnız belirli bir platformda sunulması ölçülmedi.

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

İlk ölçümde kesişim iki sürüm verdi: `1.2.0` ve `1.3.0`. Uygulama ise tek bir sürümle derlenir. Çözücü kesişimden bir sürüm seçer ve genellikle en yüksek olanı seçer; seçilen sürüm bir kilit dosyasına yazılır ve sonraki çözümlemelerde yeniden kullanılır. Aynı buluşmaya bir kez kabul edilen kümeyi, bir kez de seçilen sürümü sonuç olarak verelim.

```swift
// main.swift — cozucu kesisimden en yuksek surumu secer
func highest(_ versions: Set<String>) -> String {
    versions.max { isBefore($0, $1) } ?? "none"
}

let catalog = ["1.0.0", "1.1.0", "1.2.0", "1.3.0", "2.0.0", "2.1.0"]
let firstRange = Requirement(from: "1.0.0", upTo: "2.0.0")
let secondRange = Requirement(from: "1.2.0", upTo: "3.0.0")
let leftSet = accepted(firstRange, among: catalog)
let rightSet = accepted(secondRange, among: catalog)
let commonSet = leftSet.intersection(rightSet)

let acceptedSet = Meeting("accepted-set", first: leftSet, second: rightSet, result: commonSet)
let chosenVersion = Meeting("chosen-version", first: leftSet, second: rightSet, result: [highest(commonSet)])
acceptedSet.report()
chosenVersion.report()
```

```
accepted-set: ilk 1.0.0,1.1.0,1.2.0,1.3.0 | ikinci 1.2.0,1.3.0,2.0.0,2.1.0 | sonuc 1.2.0,1.3.0 -> kesisim
chosen-version: ilk 1.0.0,1.1.0,1.2.0,1.3.0 | ikinci 1.2.0,1.3.0,2.0.0,2.1.0 | sonuc 1.3.0 -> kural-disi
```

Kabul edilen küme verildiğinde çözüm kesişim; çözücünün seçtiği tek sürüm verildiğinde ise sınıflandırıcı `kural-disi` yazdı. Tek sürüm ne iki kümenin birleşimidir ne kesişimidir ne de birinin kendisidir. Kural yalnız kabul edilen kümeyi tarif eder; o kümeden hangi öğenin seçileceği ayrı bir karardır.

Pratik sonuç: kısıtlar çözümlenebilir göründüğünde bile uygulamanın hangi sürümle derleneceğini kısıtlar değil, çözücünün seçim kuralı ve kilit dosyası belirler. Kilit dosyası olmadan yapılan yeni bir çözümleme daha yüksek bir sürüm seçebilir; kısıtlar bunu yasaklamaz, çünkü seçilen sürüm hâlâ kesişimin içindedir.

**GO12.** Model seçimi her zaman en yüksek sürüm olarak aldı. Çözücünün kilit dosyasındaki bir sürümü koruduğu durum ölçülmedi.

#### Özet

- İki paketin aynı bağımlılık için istediği sürüm aralıkları, kabul edilen sürüm kümelerinin kesişimiyle çözülür.
- Ortak sürümü olmayan ayrık aralıklar reddedilir ve çözümleme durur.
- Yerel paket aynı adlı uzak paketin yerini alır; yerel kaynak yoksa buluşma doğmaz.
- İki paketin ürünleri uygulamanın kullanabileceği ürün kümesinde birleşir.
- Bu dersin çözümü: **kesişim**. Kural kabul edilen kümeyi sayar, çözücünün seçtiği tek sürümü saymaz.

#### Sonraki Adım

Bu derste paketlerin kısıtları buluştu ve ortak bir sürüm kümesi çıktı. Uygulama derlendikten sonra bir yerde çalışmak zorundadır: geliştirme sırasında bu yer çoğunlukla simülatördür, ama gerçek aygıtın kabul ettiği her şeyi simülatör kabul etmez. Bir sonraki ders uygulamanın gerektirdiği yeteneklerle simülatörün sunduğu yeteneklerin buluşmasını ölçer.

### Simülatör ve Aygıt

Source: https://academia.sh/tr/kurslar/ios-platformu/simulator-ve-aygit

Uygulamanın gerektirdiği yeteneklerin simülatörde bulunmaması, simülatör ile aygıtın ortak yetenekleri ve simüle edilen konumun gerçek konumun yerini alması ölçülür.

Önceki ders paketlerin sürüm kısıtlarını buluşturdu. Derlenen uygulamanın çalışacağı yer ise ayrı bir sorudur. Geliştirme sırasında uygulama çoğunlukla bir **simülatörde (simulator)** çalışır: bilgisayarda koşan, aygıtı taklit eden bir ortam. Gerçek bir **aygıt (device)** ise kendi donanımını taşır. İkisi aynı kodu çalıştırır ama aynı yetenekleri sunmaz.

Ölçü ekseni aynı: iki tanım buluşunca nasıl çözülüyor? Bu derste bir tanım uygulamanın gerektirdiği yetenekler, öteki bir ortamın sunduğu yeteneklerdir. Yetenek burada bir ad taşır: kamera, konum, ağ, hareket sensörü gibi.

#### Sınıflandırıcı

```swift
// Meeting.swift — iki tanim bulusunca nasil cozuldugunu siniflandirir
struct Meeting {
    let label: String
    let first: Set<String>
    let second: Set<String>
    let result: Set<String>?

    init(_ label: String, first: Set<String>, second: Set<String>, result: Set<String>?) {
        self.label = label
        self.first = first
        self.second = second
        self.result = result
    }

    var resolution: String {
        if first.isEmpty || second.isEmpty
            || first.isSubset(of: second) || second.isSubset(of: first) {
            return "olcum-disi"
        }
        guard let result, !result.isEmpty else { return "ret" }
        if result == first.union(second) { return "birlesim" }
        if result == first.intersection(second) { return "kesisim" }
        if result == first || result == second { return "oncelik" }
        return "kural-disi"
    }

    static func list(_ values: Set<String>) -> String {
        values.isEmpty ? "bos" : values.sorted().joined(separator: ",")
    }

    func report() {
        let outcome = result.map(Meeting.list) ?? "yok"
        print("\(label): ilk \(Meeting.list(first)) | ikinci \(Meeting.list(second)) | sonuc \(outcome) -> \(resolution)")
    }
}

func summarize(_ meetings: [Meeting]) {
    for resolution in ["oncelik", "kesisim", "birlesim", "ret"] {
        let count = meetings.filter { $0.resolution == resolution }.count
        print("\(resolution): \(count)")
    }
}
```

#### Farklar ve Sınırlamalar

Simülatör ile aygıt arasındaki fark, kodun değil çalıştığı ortamın farkıdır. Aynı derlenmiş uygulama iki yerde koşar, ama altındaki makine aynı değildir. Simülatör geliştirme bilgisayarında bir süreç olarak çalışır: bilgisayarın işlemcisini, belleğini ve ağ bağlantısını kullanır. Aygıt ise kendi işlemcisi, sınırlı belleği, pili ve ısı sınırı olan ayrı bir makinedir. Bu yüzden simülatörde akıcı görünen bir ekran, aygıtta takılabilir; simülatörde sorunsuz biten bir işlem, aygıtta bellek baskısı altında sonlandırılabilir.

Farkları üç sınıfa ayırmak işe yarar.

- **Donanım farkı:** Simülatör bir donanım taklididir. Kamera, hareket ve yönelim sensörleri, titreşim motoru, biyometrik doğrulama ve kablosuz bağlantı birimleri gibi donanıma bağlı yetenekler ya hiç sunulmaz ya da geliştiricinin elle verdiği değerlerle taklit edilir.
- **Ortam farkı:** Ağ, dosya sistemi ve izin akışları iki ortamda aynı biçimde görünür ama aynı koşullarda çalışmaz. Simülatör bilgisayarın hızlı ve kararlı bağlantısını kullanır; aygıt zayıf, kesintili ya da pahalı bir bağlantıda olabilir.
- **Yaşam döngüsü farkı:** Aygıtta kullanıcı uygulamayı arka plana alır, aygıtı kilitler, başka bir uygulamaya geçer, arama alır. Simülatörde bu olayları tetiklemek mümkündür ama kendiliğinden oluşmazlar, bu yüzden geliştirici çoğu zaman onları denemeyi unutur.

Sınırlama, bir yeteneğin yokluğu kadar, varlığının yanıltıcı olması da olabilir. Simülatörde konum her zaman hazırdır, izin akışı kısa sürer ve doğruluk mükemmeldir. Aygıtta konum önce uyduyu bulmak zorundadır, kapalı bir mekânda doğruluğu düşer ve kullanıcı izni reddedebilir. Simülatörde sorunsuz geçen bir ekran bu üç durumu hiç görmemiş olabilir.

Bu bölümdeki cümleler modelin ölçtüğü şeyler değildir; ortamlar arasındaki farkın kaba bir haritasıdır. Ölçüm, bu derste aşağıda, yetenek adlarının kümeleriyle yapılır.

#### Gerekli Yetenek Simülatörde Yoksa

Bir uygulama kamerayı kullanıyorsa, bunu proje ayarlarında bildirir ve ilk kullanımda kullanıcıdan izin ister. Simülatör bilgisayarda koştuğu için bilgisayarın kamerasını aygıtın kamerası gibi sunmaz; kamera çağrısı simülatörde ya hata verir ya da boş döner. Konum için durum farklıdır: simülatör, geliştiricinin seçtiği bir konumu uygulamaya aygıtın konumu gibi verebilir.

Konum iznini isteyen kod platformun konum çerçevesine bağlıdır, bu yüzden yalnız kaynak olarak durur.

```swift
// konum izni — KAYNAK
import CoreLocation

let manager = CLLocationManager()
manager.requestWhenInUseAuthorization()
```

İlk ölçüm, bir uygulamanın gerektirdiği yeteneklerle simülatörün sunduğu yeteneklerin buluşmasıdır. Uygulama kamerayı ve hareket sensörünü ister; simülatör konum, ağ ve anahtar zinciri sunar. İki küme ayrıktır.

```swift
// main.swift — uygulamanin gerektirdigi yetenek simulatorde yok
let required: Set<String> = ["camera", "motion"]
let simulator: Set<String> = ["location", "network", "keychain"]

let missing = Meeting(
    "required-vs-simulator",
    first: required,
    second: simulator,
    result: required.intersection(simulator)
)
missing.report()
```

```
required-vs-simulator: ilk camera,motion | ikinci keychain,location,network | sonuc bos -> ret
```

Ortak yetenek yok ve çözüm **ret**: uygulama bu simülatörde gereken yeteneklerini bulamaz. Pratikte bu, kamera ekranına gelindiğinde simülatörde sınanamayan bir akışa karşılık gelir. Çözüm kodu değiştirmek değil, o akışı gerçek aygıtta sınamaktır.

**GO13.** Ölçüm iki ayrık yetenek kümesi için yapıldı. Gerekli yeteneklerin yalnız bir kısmının simülatörde bulunduğu karışık durum bu tabloda ayrıca ölçülmedi.

#### Simülatör ve Aygıtın Ortak Yetenekleri

Aynı uygulama hem simülatörde hem aygıtta çalışacaksa, ikisinin ortak yeteneklerine güvenmek güvenlidir. Simülatör konum, ağ ve anahtar zinciri sunar; aygıt kamera, konum, ağ ve hareket sensörü sunar. İkisinin buluşması iki ortamda da aynı davranan yetenekleri verir.

```swift
// main.swift — simulator ve aygitin ortak yetenekleri
let simulatorSet: Set<String> = ["location", "network", "keychain"]
let deviceSet: Set<String> = ["camera", "location", "network", "motion"]

let shared = Meeting(
    "simulator-vs-device",
    first: simulatorSet,
    second: deviceSet,
    result: simulatorSet.intersection(deviceSet)
)
shared.report()
```

```
simulator-vs-device: ilk keychain,location,network | ikinci camera,location,motion,network | sonuc location,network -> kesisim
```

Çözüm **kesişim**: konum ve ağ iki ortamda da vardır. Anahtar zinciri yalnız simülatör kümesinde göründü; çünkü bu modelde aygıt kümesine yazılmadı. Bu bir gerçeklik iddiası değil, ölçümün girdisidir: kümelere yazılanı sayarız, platformun gerçek donanımını değil.

**GO14.** Yetenek kümeleri modelin girdisidir; platformun gerçek yetenek listesini yansıttığı iddia edilmez. Üçüncü bir ortamın, örneğin farklı bir donanımın, kümesi ölçülmedi.

#### Simüle Edilen Konum

Simülatörde konum, gerçek bir uydu okumasından gelmez; geliştirici bir konum seçer ya da bir güzergâh oynatır. Uygulama iki konum bilgisine sahip olduğunda, simüle edilen konum gerçek konumun yerini alır. Bu yerine koyma bir **öncelik (precedence)** ilişkisidir: biri kazanır, öteki görünmez.

```swift
// main.swift — simule edilen konum gercek konumun yerini alir
let realFix = ["real-fix"]
let simulatedFix = ["simulated-fix"]

let fixMeeting = Meeting(
    "location-source",
    first: Set(realFix),
    second: Set(simulatedFix),
    result: Set(simulatedFix)
)
fixMeeting.report()
```

```
location-source: ilk real-fix | ikinci simulated-fix | sonuc simulated-fix -> oncelik
```

Çözüm **öncelik**: sonuç ikinci tanımla aynıdır. Uygulama simülatörde konum okuduğunda sahadaki bir konumu değil, seçilen konumu görür. Bu yüzden konuma bağlı hatalar yalnız simülatörde sınanınca gizli kalabilir: gerçek konum gelmediği için doğruluk, gecikme ve izin değişiklikleri gibi durumlar hiç oluşmaz.

**GO15.** Ölçüm tek bir konum kaynağı çiftini kapsar. Simüle edilen konum olmadığında gerçek konumun nasıl geldiği ve iki kaynağın aynı anda ne kadar ayrışabileceği ölçülmedi.

Bu farklar yüzünden aygıtta mutlaka denenmesi gereken bir liste çıkar. Kamera ve mikrofon gibi donanıma bağlı akışlar, kesintili ya da yavaş bağlantıda ağ davranışı, uzun süre çalışırken bellek kullanımı ve ısınma, arka plana alınıp geri dönülen uygulamanın durumu, dokunma ve kaydırmanın gerçek parmakla hissedilen akıcılığı bu listenin başındadır. Bunların hiçbiri simülatörde güvenle sınanamaz; çünkü simülatör ya yeteneği sunmaz ya da bilgisayarın gücüyle kusursuz çalıştırır. Pratik kural basittir: simülatör hızlı geri bildirim içindir, aygıt ise yayından önceki son sınavdır.

#### Aygıta Kurulum

Simülatöre uygulama kurmak için kimseye bir şey kanıtlamak gerekmez; aygıta kurmak için gerekir. Platform, aygıtta çalışacak her uygulamanın kimin tarafından üretildiğini ve neye izin verildiğini imzayla doğrular. Hazırlık dört parçadan oluşur.

- **Geliştirici kimliği (certificate):** Uygulamayı imzalayan kişi ya da takımın dijital kimliğidir. Bir sertifika ile ona bağlı bir özel anahtardan oluşur; özel anahtar yoksa imza atılamaz, bu yüzden anahtarın başka bir bilgisayara taşınması gerekebilir.
- **Uygulama kimliği (app identifier):** Uygulamayı dünyada tekil olarak tanıtan, çoğu zaman ters alan adı biçiminde yazılan paket adıdır. İki uygulama aynı kimliği taşıyamaz.
- **Kayıtlı aygıt (registered device):** Geliştirme imzalı bir uygulama yalnız önceden kaydedilmiş aygıtlarda açılır. Aygıtın benzersiz tanımlayıcısı takımın aygıt listesine eklenir.
- **Yetkiler (entitlements):** Uygulamanın bildirim, anahtar zinciri paylaşımı ya da arka planda çalışma gibi özel yetenekleri kullanma hakkıdır. Bir yetenek kodda kullanılsa da yetki profilde yoksa aygıtta çalışmaz.

Bu dört parçayı bir **hazırlık profili (provisioning profile)** tek dosyada bağlar: hangi uygulama kimliği, hangi sertifikayla imzalandığında, hangi aygıtlarda, hangi yetkilerle çalışabilir. Hedefin ayarları bu profile işaret eder. İmza ayarları da proje ve hedef düzeyinde tanımlanır; hedefteki değer projedekinin önüne geçer.

Aygıtta ayrıca iki adım gerekebilir. Birincisi, aygıtın geliştirme için hazır hâle getirilmesidir: platformun ilgili sürümlerinde aygıt bir geliştirici kipinin açılmasını ister. İkincisi, aygıta ilk kez kurulan geliştirme imzalı bir uygulamanın geliştiricisine aygıt sahibinin ayarlardan güvenmesidir. Her ikisi de bir kez yapılır ama atlandığında uygulama kurulur, yine de açılmaz; bu da yeni başlayanları en çok şaşırtan durumlardan biridir.

Bu hazırlığın sonucu da bir buluşmadır, ama bu dersin modeli onu ölçmez: sertifikanın, profilin ve aygıt listesinin birbirine uyup uymadığı platformun imza denetimine aittir. Derste ölçülen tek şey, hazırlık tamamlandıktan sonra uygulamanın ve ortamın yetenek adlarıdır.

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

Model yeteneğin var olup olmadığını sayar; bu yeteneğin ne kadar iyi çalıştığını sayamaz. Simülatörde koşan bir kod aygıtta koşmayabilir: bellek, işlemci ve sensör gürültüsü iki ortamda farklıdır. Bunu görmek için simülatör ve aygıt aynı yetenek kümesini sunsun; biri hızlı, öteki yavaş olsun. Model kümeleri karşılaştırır ve iki kümenin aynı olduğunu görür.

```swift
// main.swift — ayni yetenek kumeleri: basarim farki modelde gorunmez
let capabilities: Set<String> = ["location", "network"]

let sameCapabilities = Meeting(
    "same-capabilities",
    first: capabilities,
    second: capabilities,
    result: capabilities
)
sameCapabilities.report()
```

```
same-capabilities: ilk location,network | ikinci location,network | sonuc location,network -> olcum-disi
```

İki tanım aynı olduğunda sınıflandırıcı bir buluşma görmez: sonuç **ölçüm dışı**. Bu, iki ortamın aynı davrandığı anlamına gelmez; yalnız yetenek adlarının aynı olduğunu söyler. Başarım, süre ve doğruluk farkı bu modelin dışındadır.

Pratik kural şudur: simülatörde geçen bir sınama, yeteneğin var olduğunu gösterir, yeteneğin gerçek donanımda yeterince hızlı ve doğru çalıştığını göstermez. Süre ve bellek kaygısı taşıyan akışlar, yayından önce en az bir gerçek aygıtta sınanmalıdır. Aynı şekilde sensör verisine bağlı akışlar da simülatörde yalnız seçilen değerlerle sınanabilir; gerçek sensörün gürültüsü ya da kesintisi orada oluşmaz.

**GO16.** Ölçüm yeteneğin varlığını saydı. Aynı yeteneğin iki ortamda farklı hızda ya da farklı doğrulukta çalışması, bellek ve işlemci farkı ölçülmedi.

#### Özet

- Uygulamanın gerektirdiği yetenek simülatörde yoksa buluşma reddedilir; akış gerçek aygıtta sınanır.
- Simülatör ile aygıtın ortak yetenekleri kümelerin kesişimidir.
- Simüle edilen konum gerçek konumun yerini alır; çözüm önceliktir.
- Model yeteneğin varlığını sayar; başarım farkı ölçümün dışında kalır.
- Bu dersin çözümü: **ret**. Gerekli yetenek ortamda yoksa uygulama o ortamda bulunamaz.

#### Sonraki Adım

Bu derste uygulamanın nerede çalışacağı ve neyin eksik kalacağı ölçüldü. Çalışırken bir şey ters gittiğinde sorunu bulmak gerekir. Bir sonraki ders hata ayıklamanın temellerini ele alır: iki kaynağın kesme noktalarının birleşimi, süzgeçlerin kesişimi ve şema ayarının kodun varsayılanına üstünlüğü.

### Hata Ayıklama Temelleri

Source: https://academia.sh/tr/kurslar/ios-platformu/hata-ayiklama-temelleri

Kesme noktalarının birleşimi, günlük süzgeçlerinin kesişimi, şema ortam değişkeninin üstünlüğü ve kodu olmayan satıra konan kesme noktasının reddi ölçülür.

Uygulama simülatörde ya da aygıtta çalışırken beklenmedik bir şey olduğunda sorunu bulmak için **hata ayıklayıcı (debugger)** kullanılır. En temel araç **kesme noktasıdır (breakpoint)**: çalışmayı belirli bir kod satırında durdurur ve o andaki değerleri incelemeyi sağlar. Durduktan sonra kod satır satır **adımlanır**, çıktı ise **günlüğe (log)** yazılan iletilerden okunur.

Ölçü ekseni değişmez: iki tanım buluşunca nasıl çözülüyor? Bu derste buluşan tanımlar kesme noktası kümeleri, günlük süzgeçleri ve hata ayıklama ayarlarıdır.

#### Sınıflandırıcı

```swift
// Meeting.swift — iki tanim bulusunca nasil cozuldugunu siniflandirir
struct Meeting {
    let label: String
    let first: Set<String>
    let second: Set<String>
    let result: Set<String>?

    init(_ label: String, first: Set<String>, second: Set<String>, result: Set<String>?) {
        self.label = label
        self.first = first
        self.second = second
        self.result = result
    }

    var resolution: String {
        if first.isEmpty || second.isEmpty
            || first.isSubset(of: second) || second.isSubset(of: first) {
            return "olcum-disi"
        }
        guard let result, !result.isEmpty else { return "ret" }
        if result == first.union(second) { return "birlesim" }
        if result == first.intersection(second) { return "kesisim" }
        if result == first || result == second { return "oncelik" }
        return "kural-disi"
    }

    static func list(_ values: Set<String>) -> String {
        values.isEmpty ? "bos" : values.sorted().joined(separator: ",")
    }

    func report() {
        let outcome = result.map(Meeting.list) ?? "yok"
        print("\(label): ilk \(Meeting.list(first)) | ikinci \(Meeting.list(second)) | sonuc \(outcome) -> \(resolution)")
    }
}

func summarize(_ meetings: [Meeting]) {
    for resolution in ["oncelik", "kesisim", "birlesim", "ret"] {
        let count = meetings.filter { $0.resolution == resolution }.count
        print("\(resolution): \(count)")
    }
}
```

#### Kesme Noktaları

Bir kesme noktası bir dosyanın bir satırına konur. Gerçekte kesme noktaları iki yerden gelebilir: geliştirici ortamının kenar boşluğuna tıklanarak konanlar ve hata ayıklayıcı komut satırından `breakpoint set` ile eklenenler. İkisi aynı projede bir araya geldiğinde etkin olan kesme noktaları kümesi iki kaynağın birleşimidir. Aynı satıra iki kaynaktan da konmuş bir nokta bir kez sayılır.

Model her kesme noktasını `dosya:satır` kimliğiyle gösterir. İlk kaynak sepet dosyasında on ikinci ve otuzuncu satırlara, ikinci kaynak sepet dosyasının otuzuncu ve sipariş dosyasının sekizinci satırına nokta koyar.

```swift
// main.swift — iki kaynagin kesme noktalari birlesir
let gutter: Set<String> = ["Cart.swift:12", "Cart.swift:30"]
let console: Set<String> = ["Cart.swift:30", "Order.swift:8"]

let breakpoints = Meeting(
    "breakpoint-sources",
    first: gutter,
    second: console,
    result: gutter.union(console)
)
breakpoints.report()
```

```
breakpoint-sources: ilk Cart.swift:12,Cart.swift:30 | ikinci Cart.swift:30,Order.swift:8 | sonuc Cart.swift:12,Cart.swift:30,Order.swift:8 -> birlesim
```

Çözüm **birleşim**: üç ayrı nokta etkin. Ortak `Cart.swift:30` bir kez sayıldı. Durma sayısı bu yüzden kaynak sayısının toplamı değildir: iki kaynakta da tanımlı bir nokta tek kez durdurur.

**GO17.** Ölçüm kesme noktalarını dosya ve satır kimliği olarak ele aldı. Koşullu kesme noktaları, hata ayıklayıcıya özgü eylemler ve noktanın devre dışı bırakılması ölçülmedi.

#### Kesme Noktası Türleri

Düz bir kesme noktası her ulaşıldığında durur. Döngü içinde ya da sık çağrılan bir işlevde bu, yüzlerce gereksiz durmaya yol açar. İki tür bunu çözer.

- **Koşullu kesme noktası (conditional breakpoint):** Yalnız verilen ifade doğru olduğunda durur. Komut satırında koşul `-c` bayrağıyla verilir: `breakpoint set -f Cart.swift -l 30 -c 'count > 3'`. Koşul her ulaşıldığında hesaplanır; bu yüzden çok sık geçilen bir satırda koşulun kendisi yavaşlığa neden olabilir.
- **Eylemli kesme noktası (action breakpoint):** Durmadan önce ya da durmak yerine bir eylem yürütür. Eylem bir ifadenin değerini yazdırmak, bir komut çalıştırmak ya da bir ses çalmak olabilir. Noktaya otomatik devam seçeneği eklenirse uygulama durmaz; nokta, koda dokunmadan geçici bir günlük satırı gibi davranır. Komut satırında `breakpoint command add` bu işi görür.

İki kesme noktası çeşidi daha vardır. **Özel durum noktası (exception breakpoint)** uygulama bir özel durum fırlattığı anda, çökmeden önce durur; çökme izinde yalnızca sonucu değil nedeni görmeyi sağlar. **Sembolik nokta (symbolic breakpoint)** satır yerine bir işlevin adına konur, böylece kaynağı elde olmayan bir sistem işlevinin her çağrılışında durulabilir. İkisi de bir dosya ve satır kimliği taşımadığı için aşağıdaki ölçümün dışındadır.

Bir noktayı silmeden **devre dışı bırakmak (disable)** da mümkündür. Devre dışı nokta tanımını korur ama durdurmaz; sorun çözülünce silinir, tekrar gerekirse yeniden açılır.

Bu çeşitlerin hiçbiri yukarıdaki ölçümün konusu değildir. Ölçüm noktanın yalnızca konumuna bakar; koşulun doğru çıkıp çıkmadığı çalışma anına aittir ve bir kümeyle ifade edilemez.

#### Günlük Süzgeçleri

Uygulama çalışırken çok sayıda ileti yazar. Okunabilir kalması için iki süzgeç birlikte kullanılır: **düzey (level)** süzgeci belirli bir ciddiyetin altındaki iletileri eler, **kategori (category)** süzgeci belirli bir alt sistemin iletilerini tutar. Bir ileti ancak iki süzgeçten de geçerse görünür. Günlüğe yazan kod platformun günlük çerçevesine bağlıdır, bu yüzden yalnız kaynak olarak durur.

```swift
// günlük — KAYNAK
import os

let logger = Logger(subsystem: "com.example.shop", category: "cart")
logger.info("sepet guncellendi")
```

Model her iletiyi bir kimlikle gösterir. Düzey süzgeci uyarı ve üstü iletileri tutar; kategori süzgeci sepet alt sisteminin iletilerini tutar.

```swift
// main.swift — duzey ve kategori suzgeclerinin kesisimi
let levelPass: Set<String> = ["m2", "m3", "m5", "m6"]
let categoryPass: Set<String> = ["m3", "m4", "m5"]

let filters = Meeting(
    "log-filters",
    first: levelPass,
    second: categoryPass,
    result: levelPass.intersection(categoryPass)
)
filters.report()
```

```
log-filters: ilk m2,m3,m5,m6 | ikinci m3,m4,m5 | sonuc m3,m5 -> kesisim
```

Çözüm **kesişim**: yalnız iki süzgeçten de geçen iki ileti görünür. Süzgeçlerden biri gevşetilirse görünen küme genişler; biri sıkılaştırılırsa daralır. Bir iletinin görünmemesi, yazılmadığı anlamına gelmez; yalnız süzgeçlerden birini geçemediğini söyler.

**GO18.** Ölçüm iki süzgeci ayrı kümeler olarak aldı. Süzgeçlerin birbirine bağlı olduğu durumlar ve iletinin birden çok kategoriye ait olması ölçülmedi.

Süzgeçler tek başına yeterli değildir; iletinin hangi düzeyde yazıldığı da önemlidir. Platformun günlük çerçevesi iletileri ciddiyet sırasına göre ayırır.

- **Hata ayıklama düzeyi (debug):** Yalnız geliştirme sırasında işe yarayan ayrıntılar. Yayın sürümünde genellikle saklanmaz.
- **Bilgi düzeyi (info):** Olağan akışta bilinmesi yararlı olan olaylar.
- **Bildirim düzeyi (notice):** Varsayılan düzeydir; sorun olmayan ama kayda değer durumlar.
- **Hata düzeyi (error):** Bir işlemin başarısız olduğu durumlar.
- **Ciddi hata düzeyi (fault):** Uygulamanın ya da sistemin tutarlılığını bozan, hemen müdahale isteyen durumlar.

Kategori ise bir alt sistemin adıdır: ağ, veri tabanı, arayüz gibi. Uygulama kendi alt sistemlerini ve kategorilerini tanımlar. İyi bir adlandırma, sorun çıktığında yalnız ağ iletilerine bakmayı mümkün kılar. Kişisel veri içeren iletiler varsayılan olarak gizlenir; geliştirici açıkça belirtmedikçe değerleri günlükte görünmez.

#### Şemadaki Ortam Değişkeni

Çalıştırma ayarları şemada tutulur. Şema, uygulama başlarken ortam değişkenleri verebilir. Kod bir ayar için varsayılan bir değer taşıyorsa ve şema aynı ayar için değer veriyorsa, çalışma sırasında şemadaki değer kodunkinin yerine geçer. Bu yerine koyma bir **önceliktir (precedence)**: ayrıntılı günlüğü kodu değiştirmeden açıp kapatmak bu yüzden mümkündür.

```swift
// main.swift — sema ortam degiskeni kodun varsayilanina ustun gelir
func effectiveLogging(codeDefault: String, schemeValue: String?) -> String {
    schemeValue ?? codeDefault
}

let chosen = effectiveLogging(codeDefault: "verbose-off", schemeValue: "verbose-on")
let environment = Meeting(
    "scheme-environment",
    first: ["verbose-off"],
    second: ["verbose-on"],
    result: [chosen]
)
environment.report()
```

```
scheme-environment: ilk verbose-off | ikinci verbose-on | sonuc verbose-on -> oncelik
```

Çözüm **öncelik**: sonuç ikinci tanımla aynı. Şemadaki değer olmasaydı kodun varsayılanı geçerli olurdu. Şema geliştiriciye özgü olabildiği için, paylaşılmayan bir şemadaki ortam değişkeni başka bir geliştiricide görünmez; "bende çalışıyor" türü farklar bazen buradan çıkar.

**GO19.** Ölçüm tek bir ayar çiftini kapsadı. Birden çok ortam değişkeninin birbirini etkilediği durumlar ve uygulamanın bir ayarı kendi içinde yeniden okuması ölçülmedi.

#### Kodu Olmayan Satır

Her satıra kesme noktası konamaz. Boş bir satır, yorum satırı ya da tek başına bir süslü parantez çalıştırılabilir kod üretmez; hata ayıklayıcı noktayı en yakın kod satırına kaydırır ya da hiç etkin yapmaz. Model bu davranışı kesin bir kural olarak değil, bir buluşma olarak ölçer: istenen satır ile kod içeren satırların kümesi buluşur.

```swift
// main.swift — kodu olmayan satira konan kesme noktasi
let codeLines: Set<String> = ["10", "11", "13"]
let requested: Set<String> = ["12"]

let emptyLine = Meeting(
    "breakpoint-on-empty-line",
    first: requested,
    second: codeLines,
    result: requested.intersection(codeLines)
)
emptyLine.report()
```

```
breakpoint-on-empty-line: ilk 12 | ikinci 10,11,13 | sonuc bos -> ret
```

Ortak satır yok ve çözüm **ret**: on ikinci satırda durulacak bir kod bulunmuyor. Hata ayıklayıcının noktayı komşu satıra kaydırması bu modelin dışında kalır; model noktanın istenen satırda kalıp kalmadığını sayar.

#### Adımlama Komutları ve Değişken İnceleme

Uygulama bir noktada durduğunda üç temel komut ilerlemeyi yönetir. Hata ayıklayıcı komut satırındaki karşılıkları ters tırnak içinde verilmiştir.

- **İçine gir (step into):** `step` komutu. Geçerli satırdaki çağrının gövdesine girer ve ilk satırında durur.
- **Üzerinden atla (step over):** `next` komutu. Çağrıyı tek adımda yürütür, bir sonraki satırda durur. Çağrının içinde bir kesme noktası varsa orada yine durulur.
- **Dışına çık (step out):** `finish` komutu. Geçerli işlevin dönmesine kadar çalışır ve çağıranın satırında durur.

Bir çağrının içeriğiyle ilgileniyorsanız içine girersiniz; ilgilenmiyorsanız üzerinden atlarsınız; yanlışlıkla çok derine indiyseniz dışına çıkarsınız. Standart kitaplık gibi bilmediğiniz kodun içine girmek çoğu zaman zaman kaybettirir.

Durduktan sonra değer incelenir. `frame variable` geçerli çerçevedeki yerel değişkenleri listeler. `print` bir ifadeyi değerlendirir. `po` ifadenin nesne olarak yazdırılabilir betimlemesini verir; tür kendi betimlemesini sağlıyorsa bu daha okunur olur. Değişken penceresi aynı bilgiyi grafik olarak gösterir. Çağrı zinciri gezilerek üst çerçevelerin değişkenleri de görülür. Değer değiştirmek de mümkündür; `expression count = 0` gibi bir ifade uygulamayı yeniden derlemeden bir varsayımı sınar.

Bu komutların hepsi çalışan bir süreçle konuşur, bu nedenle modelde karşılığı yoktur. Aşağıdaki ölçüm yalnız adımlamanın bıraktığı izi, yani hangi satırların ziyaret edildiğini sayar.

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

Adımlama kodu satır satır ilerletir. **Üzerinden atlamak** bir çağrıyı tek adımda yürütür, **içine girmek** çağrının gövdesine iner. Aynı satırları ziyaret eden iki adımlama, farklı bir sırayla ilerleyebilir. Model ziyaret edilen satırların kümesini sayar; sırayı sayamaz. İki ziyaret dizisini basalım, sonra kümelerini buluşturalım.

```swift
// main.swift — adimlama sirasi modelde gorunmez
let overOrder = ["10", "11", "13"]
let intoOrder = ["13", "10", "11"]
print("step-over: " + overOrder.joined(separator: ","))
print("step-into: " + intoOrder.joined(separator: ","))

let stepping = Meeting(
    "stepping-order",
    first: Set(overOrder),
    second: Set(intoOrder),
    result: Set(overOrder)
)
stepping.report()
```

```
step-over: 10,11,13
step-into: 13,10,11
stepping-order: ilk 10,11,13 | ikinci 10,11,13 | sonuc 10,11,13 -> olcum-disi
```

İki dizi farklı sırada ilerledi ama aynı üç satırı ziyaret etti. Kümeler aynı olduğu için sınıflandırıcı bir buluşma görmedi: sonuç **ölçüm dışı**. Sıra, bir hatanın nerede ortaya çıktığını belirlemede önemli olabilir; model onu saymaz.

**GO20.** Ölçüm ziyaret edilen satırların kümesini saydı. Adımların sırası, tekrar eden ziyaretler ve çağrı içinde geçen süre ölçülmedi.

#### Özet

- İki kaynaktan gelen kesme noktaları birleşir; ortak nokta bir kez sayılır.
- Düzey ve kategori süzgeçlerinden ikisini de geçen iletiler görünür; çözüm kesişimdir.
- Şemadaki ortam değişkeni kodun varsayılanının yerini alır.
- Kodu olmayan satıra istenen kesme noktası model içinde reddedilir.
- Bu dersin çözümü: **birleşim**. Adımlama sırası ise ölçümün dışında kalır.

#### Sonraki Adım

Bu derste çalışan uygulama içinde hata arandı. Son derste iki dilin aynı projede buluşması ele alınacak: aynı Objective-C seçicisine düşen iki Swift yönteminin sonucu ölçülür.

### Objective-C ile Birlikte Çalışma

Source: https://academia.sh/tr/kurslar/ios-platformu/objective-c-ile-birlikte-calisma

Aynı Objective-C seçicisine düşen iki Swift yönteminin reddini, köprüleme başlığı ile üretilen başlığın adlarının birleşimini ve NS_SWIFT_NAME ile verilen adın önceliğini ölçer; kursu kapatır.

Eski projelerin büyük bölümü hâlâ **Objective-C** ile yazılıdır ve yeni kod çoğu zaman Swift'tir. İki dil aynı uygulamada yan yana yaşar. Bu mümkündür çünkü Swift, Objective-C'nin çalışma zamanına kendi bildirimlerinin bir bölümünü açabilir ve Objective-C başlıklarını içeri alabilir. Karşılığında bir sınır vardır: Objective-C yöntemleri **seçici (selector)** denen bir adla tanınır ve aynı sınıfta iki yöntem aynı seçiciyi taşıyamaz. Swift ise yöntemleri tür bilgisiyle de ayırabildiği için bu iki kuralın karşılaşması bir sonuç doğurur.

Ölçü ekseni son kez aynı: iki tanım buluşunca nasıl çözülüyor? Bu derste buluşan tanımlar seçici adları, başlıkların açtığı sınıf adları ve bir yöntemin iki farklı kaynaktan gelen Swift adlarıdır. Ders aynı zamanda kursun kapanışıdır.

#### Sınıflandırıcı

```swift
// Meeting.swift — iki tanim bulusunca nasil cozuldugunu siniflandirir
struct Meeting {
    let label: String
    let first: Set<String>
    let second: Set<String>
    let result: Set<String>?

    init(_ label: String, first: Set<String>, second: Set<String>, result: Set<String>?) {
        self.label = label
        self.first = first
        self.second = second
        self.result = result
    }

    var resolution: String {
        if first.isEmpty || second.isEmpty
            || first.isSubset(of: second) || second.isSubset(of: first) {
            return "olcum-disi"
        }
        guard let result, !result.isEmpty else { return "ret" }
        if result == first.union(second) { return "birlesim" }
        if result == first.intersection(second) { return "kesisim" }
        if result == first || result == second { return "oncelik" }
        return "kural-disi"
    }

    static func list(_ values: Set<String>) -> String {
        values.isEmpty ? "bos" : values.sorted().joined(separator: ",")
    }

    func report() {
        let outcome = result.map(Meeting.list) ?? "yok"
        print("\(label): ilk \(Meeting.list(first)) | ikinci \(Meeting.list(second)) | sonuc \(outcome) -> \(resolution)")
    }
}

func summarize(_ meetings: [Meeting]) {
    for resolution in ["oncelik", "kesisim", "birlesim", "ret"] {
        let count = meetings.filter { $0.resolution == resolution }.count
        print("\(resolution): \(count)")
    }
}
```

#### Aynı Seçici

Bir Swift sınıfı `NSObject`'ten türüyorsa ya da `@objc` ile işaretlenmişse yöntemleri Objective-C'ye görünür. Görünür her yöntem bir seçici kazanır. Seçici, yöntemin adından ve parametre etiketlerinden kurulur: `save(_:)` yöntemi `save:` seçicisine, `save(name:)` yöntemi `saveWithName:` seçicisine, parametresiz `reset()` yöntemi `reset` seçicisine dönüşür. Gerçek kural ilgi edatlarını ve birkaç özel durumu da gözetir; ders modeli bunları sadeleştirir ve yalnızca etiketsiz ile etiketli parametreyi ayırır.

Seçicide parametrenin tipi yoktur. Bu yüzden Swift'te meşru olan iki aşırı yükleme, Objective-C'de aynı seçiciye düşer.

```swift
@objc class Store: NSObject {
    @objc func save(_ value: Int) { }
    @objc func save(_ value: String) { }
}
```

Bu iki yöntem Swift'te birbirinden ayrılır, çünkü parametre tipleri farklıdır. Derleyici bu sınıfı yine de kabul etmez: ikisi de `save:` seçicisini ister. Derleyici reddini basmıyoruz; ders yalnızca model düzeyinde çarpışmayı sayar. Model her yöntemden seçici üretir, iki grubun seçicilerini karşılaştırır ve çarpışma varsa sınıfı hiçbir seçici açmadan bırakır. İkinci denemede çakışan yöntem `save(name:)` olarak etiketlenir ve aynı kural bu kez sonuç üretir.

```swift
// main.swift — ayni secici iki Swift yontemini reddettirir
struct SwiftMethod {
    let name: String
    let labels: [String]
}

func selector(for method: SwiftMethod) -> String {
    var text = method.name
    for (index, label) in method.labels.enumerated() {
        if index == 0 {
            text += label == "_" ? ":" : "With" + label.prefix(1).uppercased() + label.dropFirst() + ":"
        } else {
            text += (label == "_" ? "" : label) + ":"
        }
    }
    return text
}

func exposed(_ first: Set<String>, _ second: Set<String>) -> Set<String> {
    first.isDisjoint(with: second) ? first.union(second) : []
}

let intSave = SwiftMethod(name: "save", labels: ["_"])
let stringSave = SwiftMethod(name: "save", labels: ["_"])
let namedSave = SwiftMethod(name: "save", labels: ["name"])
let reset = SwiftMethod(name: "reset", labels: [])
let clear = SwiftMethod(name: "clear", labels: [])

let groupA: Set<String> = [selector(for: intSave), selector(for: reset)]
let groupB: Set<String> = [selector(for: stringSave), selector(for: clear)]
let groupC: Set<String> = [selector(for: namedSave), selector(for: clear)]

Meeting("same-selector", first: groupA, second: groupB, result: exposed(groupA, groupB)).report()
Meeting("labelled-overload", first: groupA, second: groupC, result: exposed(groupA, groupC)).report()
```

```
same-selector: ilk reset,save: | ikinci clear,save: | sonuc bos -> ret
labelled-overload: ilk reset,save: | ikinci clear,saveWithName: | sonuc clear,reset,save:,saveWithName: -> birlesim
```

İlk buluşma **ret**: iki grup da `save:` seçicisini istiyor, çarpışma var ve sınıf hiçbir seçici açmıyor, sonuç boş. İkinci buluşmada çakışan yöntemin etiketi `name` olunca seçici `saveWithName:` oldu, çarpışma kalmadı ve dört seçicinin hepsi açıldı: **birleşim**. Yani reddi doğuran Swift'in aşırı yüklemesi değil, Objective-C'nin türü görmeyen seçicisidir. Çözüm çoğu zaman yöntemlerden birine etiket vermektir.

**GO21.** Ölçüm seçiciyi ad ve etiketlerden kurdu. İlgi edatlarının özel kuralı, `@objc(name:)` ile seçicinin elle yazılması ve derleyicinin gerçek hata iletisi ölçülmedi.

#### Köprüleme Başlığı

Objective-C kodu Swift'e **köprüleme başlığı (bridging header)** ile açılır. Başlık, Swift'in görmesi gereken Objective-C başlıklarını tek yerden içeri alır. Ters yön ise derleyicinin ürettiği bir başlıkla çözülür: Swift'te `@objc` ile açılmış sınıflar, `<ModülAdı>-Swift.h` adlı üretilmiş başlıkta Objective-C'ye görünür.

```objc
// Bridging-Header.h
#import "LegacyStore.h"
#import "LegacyLogger.h"
```

```objc
// Objective-C tarafı üretilmiş başlığı içeri alır
#import "App-Swift.h"
```

Karma bir modülde ikisi aynı anda geçerlidir. Köprüleme başlığı Swift'e `LegacyStore` ve `LegacyLogger` adlarını açar. Üretilmiş başlık Objective-C'ye `Router` ve `Formatter` adlarını açar. Modülün iki dilde birden kullanabildiği ad kümesi, her iki başlığın açtığı adların birleşimidir; hiçbir ad kendi başlığının dışında bir başlığın yerini almaz.

```swift
// main.swift — iki basligin actigi adlar birlesir
let bridgingHeader: Set<String> = ["LegacyStore", "LegacyLogger"]
let generatedHeader: Set<String> = ["Router", "Formatter"]
let moduleNames = bridgingHeader.union(generatedHeader)

Meeting(
    "mixed-module",
    first: bridgingHeader,
    second: generatedHeader,
    result: moduleNames
).report()
```

```
mixed-module: ilk LegacyLogger,LegacyStore | ikinci Formatter,Router | sonuc Formatter,LegacyLogger,LegacyStore,Router -> birlesim
```

Dört ad iki başlıktan geldi ve dördü de modülün kümesinde. İki başlığın adları kesişmediği için birleşim ile ayrık küme aynı sonucu verir; çözüm yine **birleşim**dir. Başlık değişmeden bir sınıf `@objc` işaretini kaybederse üretilmiş başlığın kümesi küçülür ve Objective-C tarafındaki kullanım derleme anında kırılır.

**GO22.** Ölçüm başlıkların açtığı adları küme olarak aldı. Üretilmiş başlıkta yer alan ama `@objc` ile işaretlenmediği için açılmayan üyeler, iki başlığın aynı adı farklı tanımlaması ve derleme sırası ölçülmedi.

#### Verilen Ad ve Öncelik

Objective-C yöntemleri Swift'e içeri alınırken derleyici onlara kuralla bir Swift adı verir. Çoğu zaman ortaya çıkan ad Swift'in kendi üslubuna uymaz. Başlıktaki **NS_SWIFT_NAME** niteliği geliştiriciye Swift adını kendisi vermeyi sağlar.

```objc
// LegacyLoader.h
- (void)fetchItemsWithCompletion:(void (^)(NSArray *))completion
    NS_SWIFT_NAME(fetch(then:));
```

Burada iki ad vardır: kuralla üretilen `fetchItems(completion:)` ve geliştiricinin yazdığı `fetch(then:)`. Swift tarafında görünen yalnızca biridir. Verilen ad üretilen adın yerine geçer; ikisi birden görünmez. Model, bir yöntem için görünür adı verilen ad varsa ondan, yoksa üretilenden seçer.

```swift
// main.swift — NS_SWIFT_NAME ile verilen ad uretilen adin onune gecer
struct ImportedMethod {
    let generated: String
    let given: String?
    var visible: String { given ?? generated }
}

let loader = ImportedMethod(generated: "fetchItems(completion:)", given: "fetch(then:)")

Meeting(
    "swift-name",
    first: [loader.generated],
    second: [loader.given ?? loader.generated],
    result: [loader.visible]
).report()
```

```
swift-name: ilk fetchItems(completion:) | ikinci fetch(then:) | sonuc fetch(then:) -> oncelik
```

Verilen ad tek başına göründü: çözüm **öncelik**. Üretilen ad `fetchItems(completion:)` artık Swift tarafında yazılamaz; yalnız `fetch(then:)` geçerlidir. Bu kuralın bir bedeli vardır: Objective-C ve Swift kodu aynı yöntemi iki adla anar, bu yüzden adı ekibin iki dili okuyan herkesi hesaba katarak vermek gerekir.

**GO23.** Ölçüm tek bir yöntemin iki adını aldı. Verilen adın geçerli bir Swift adı olmaması, aynı sınıfta iki yöntemin aynı adı alması ve eski adın uyarıyla korunması ölçülmedi.

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

Son sınır dağıtımdır. Bir yöntem `@objc dynamic` ile işaretlendiğinde çağrı Swift'in doğrudan çağrısıyla değil, Objective-C çalışma zamanının ileti gönderimiyle çözülür. Aynı seçiciyi bir Objective-C sınıfı da bir alt sınıfta yeniden yazabileceği için, çağrının hangi gövdeye gittiği koşum anında belirlenir. Bu bir ad sorusu değil, bir gövde seçimi sorusudur ve bu kursun ölçüsü değildir. Çağrının gövdesini hangi tipin belirlediği önceki bir kursta (M08/K02) ölçülen eksendir.

Modele sormak için iki tarafın seçicisini yazalım: Swift gövdesi `refresh` der, Objective-C'deki yeniden yazım da `refresh` der.

```swift
// main.swift — dinamik dagitim ad kumesinde gorunmez
let swiftBody: Set<String> = ["refresh"]
let objcOverride: Set<String> = ["refresh"]

Meeting(
    "dynamic-dispatch",
    first: swiftBody,
    second: objcOverride,
    result: swiftBody
).report()
```

```
dynamic-dispatch: ilk refresh | ikinci refresh | sonuc refresh -> olcum-disi
```

İki taraf aynı seçiciyi taşıyor ve sonuç **ölçüm dışı**. Kümeler aynı olduğu için sınıflandırıcı bir buluşma görmedi; hangi gövdenin çalışacağı bu kümelerden okunamaz.

**GO24.** Ölçüm iki gövdenin seçici adını saydı. Hangi gövdenin çalıştığı, yeniden yazımın `super` çağırıp çağırmadığı ve `dynamic` olmayan çağrının doğrudan dağıtımı ölçülmedi.

#### Özet

- Aynı Objective-C seçicisine düşen iki Swift yöntemi çarpışır; model bu durumda sınıfı reddeder ve etiket değişince çarpışma biter.
- Köprüleme başlığı Swift'e, üretilmiş başlık Objective-C'ye ad açar; karma modülün adları iki kümenin birleşimidir.
- `NS_SWIFT_NAME` ile verilen ad kuralla üretilen adın önüne geçer.
- Dinamik dağıtım ölçüm dışıdır: çağrının hangi gövdeye gittiği bir ad sorusu değil, bir gövde seçimi sorusudur.
- Bu dersin çözümü: **ret**.

#### Kurs Kapanışı

Bu kursun on iki dersi tek bir soruyu yanıtladı: iki tanım buluşunca nasıl çözülüyor. Her ders bir buluşma kurdu, sonucu ölçtü ve dört addan biriyle söyledi. Tabloda ders, buluşma, çözüm ve o dersin sınırlayıcı ölçümü yan yana durur. İlk altı ders platformun yapısını, son altısı geliştirme ortamını kapsar.

| Ders | Buluşma | Çözüm | Sınırlayıcı ölçüm |
|---|---|---|---|
| Platform Katmanları | Platformun dört katmanı | çekirdeğin dördü (2/2/2/2) | İç içe iki tanım `olcum-disi` kalır |
| Çerçeveler ve Kütüphaneler | Çerçeve ve kütüphane adları | birleşim | Nitelenmiş ad ayrı bir ad sayılır |
| Alan Çerçeveleri | Alan izinleri | kesişim | İznin kapsamı okunmaz |
| Uygulama Yaşam Döngüsü | Yaşam döngüsü olayları | öncelik | Geçiş sırası ölçülmez |
| Arayüz Tasarım Kılavuzları | Kılavuz ve uygulama değerleri | öncelik | Kılavuz bir beklentidir, `kural-disi` kalır |
| Sürüm ve Uyumluluk | Sürüm aralıkları | kesişim | Asgari sürüm bir seçimdir |
| Geliştirme Ortamı | Aynı ayarın iki düzeyi | öncelik | Şema seçimi ölçüm dışıdır |
| Proje Dosya Yapısı | Şablon ve derleme anahtarları | birleşim | Anahtar sayılır, değer değil |
| Paket Yöneticisi | Sürüm kısıtları | kesişim | Seçilen sürüm değil, küme sayılır |
| Simülatör ve Aygıt | Gerekli ve sunulan yetenekler | ret | Başarım ölçülmez |
| Hata Ayıklama Temelleri | Kesme noktası kaynakları | birleşim | Adımlama sırası ölçülmez |
| Objective-C ile Birlikte Çalışma | Aynı seçiciye düşen yöntemler | ret | Dinamik dağıtım ölçüm dışıdır |

Açılış dersi dışındaki on bir dersin dağılımı şöyledir: üçü öncelik (Geliştirme Ortamı, Uygulama Yaşam Döngüsü, Arayüz Tasarım Kılavuzları), üçü kesişim (Alan Çerçeveleri, Sürüm ve Uyumluluk, Paket Yöneticisi), üçü birleşim (Çerçeveler ve Kütüphaneler, Proje Dosya Yapısı, Hata Ayıklama Temelleri), ikisi ret (Simülatör ve Aygıt, Objective-C ile Birlikte Çalışma). Bu sayım tablonun çözüm sütunundan çıkar. Kursun açılışındaki çekirdek ise sekiz buluşmayı 2/2/2/2 dağıtmıştı. İkisi aynı şey değildir: çekirdek sekiz buluşmayı, tablonun dağılımı açılış dışındaki on bir dersi sayar.

Kursun bıraktığı alışkanlık, iki tanımın karşılaştığı yeri önce adlandırmak, sonra çözümü ölçüme bırakmaktır. Platform, proje ve araç aynı soruyu farklı yüzlerden sorar; cevabın hangi kümeden çıktığını bilmek, yanlış bir kuralı sezgiyle uygulamaktan daha güvenilirdir. Sırada bu platformun üzerine kurulan arayüz var: M30/K03 bildirimsel arayüzü, görünüm ağacını ve durumu ele alır.

