Ders 08 / 12
Proje Dosya Yapısı
Ş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ü.
Yayın Güncelleme
Bu derste
- Şablonun yazdığı anahtarlar ile derlemenin ürettiği anahtarların birleşimle çözüldüğünü ölçer
- Aynı hedefe aynı adla eklenen iki kaynak dosyanın neden reddedildiğini gösterir
- Aygıta özgü varlığın genel varlığa üstün geldiğini ve kullanıcı dilleriyle yerelleştirmelerin kesiştiğini ölçer
- Birleşimin değerleri değil anahtarları saydığını sınırlayıcı bir ölçümle gösterir
İçindekiler
Ö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.
// 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.
<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.
// 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.
// 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.
// 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.
// 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.
// 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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.