Ders 10 / 21
İsteğe Bağlı Açma
if let ve guard let sağ tarafının, ?? zincirinin ve isteğe bağlı zincirlemenin ne zaman değerlendirildiğinin ölçümü.
Yayın Güncelleme
Bu derste
- if let ile guard let sağ tarafının bir kez ve hemen değerlendirildiğini ölçer
- ?? zincirinin ilk dolu adayda durduğunu ve sonrakini hiç değerlendirmediğini gösterir
- İsteğe bağlı zincirlemenin boş alıcıda argümanı da değerlendirmediğini ölçer
- Zorla açmanın neden bir yokluk kontrolünü yerinden çıkardığını açıklar
İçindekiler
Önceki ders kutuyu açmadan çalıştı: map ve flatMap boş kutuda dönüştürücüyü hiç koşturmadı, dolu kutuda her çağrıda yeniden koşturdu. Sonuç yine bir kutuydu. Programın bir yerinde gerçek bir değere ihtiyaç duyulur: ekrana yazılacak bir sayı, bir dosyanın adı, bir toplamın terimi. Bu ders kutuyu açmanın yollarını sıralar ve her yol için aynı soruyu sorar: açılan ifade ne zaman değerlendiriliyor, açıldıktan sonraki kod ne zaman koşuyor?
Swift kutuyu açmak için birbirinden farklı araçlar sunar; bu ders beşini ölçer. Koşullu bağlama (optional binding), yani if let ve guard let, kutuyu bir kez açar ve içindekini bir ada bağlar. ?? işleci boş kutu yerine bir yedek değer koyar. İsteğe bağlı zincirleme (optional chaining), yani ?., bir çağrıyı yalnızca kutu doluysa yapar. Zorla açma, yani ünlem işaretiyle yazılan sonek işleç, yokluğu çalışma zamanında bir hataya çevirir. Beşincisi, map ve flatMap, önceki derste ölçüldü. Her aracın değerlendirme anı farklıdır ve fark hata yazılacak yeri belirler.
Kursun ölçü ekseni bu derste de aynıdır: bu ifade ne zaman değerlendiriliyor? Dört an vardır: hemen, ilk kullanımda, her kullanımda, hiç. Kuralı sayan Probe sınıfı aşağıda olduğu gibi verilir.
Çekirdek
Her ders aynı çekirdeği kullanır. Probe bir ifadenin kaç kez değerlendirildiğini (eval) ve kaç kez kullanıldığını (use) sayar; moment özelliği bu iki sayıdan anı çıkarır. Açılış dersindeki bağlayıcı ölçümde sekiz ifadenin dağılımı 2/1/3/2 idi: ikisi hemen, biri ilk kullanımda, üçü her kullanımda, ikisi hiç. Bu derste yalnız kendi kesitini ölçeriz.
// Probe.swift — bir ifadenin ne zaman degerlendirildigini kaydeder
final class Probe {
let label: String
private(set) var evaluations = 0
private(set) var uses = 0
private var evaluatedBeforeUse = false
init(_ label: String) {
self.label = label
}
func eval<T>(_ value: T) -> T {
evaluations += 1
if uses == 0 { evaluatedBeforeUse = true }
return value
}
func use<T>(_ access: () -> T) -> T {
uses += 1
return access()
}
var moment: String {
if evaluations == 0 { return "hic" }
if evaluations > 1 { return "her-kullanimda" }
return evaluatedBeforeUse ? "hemen" : "ilk-kullanimda"
}
func report() {
print("\(label): \(evaluations) degerlendirme, \(uses) kullanim -> \(moment)")
}
}
func tally(_ probes: [Probe]) {
for moment in ["hemen", "ilk-kullanimda", "her-kullanimda", "hic"] {
let count = probes.filter { $0.moment == moment }.count
print("\(moment): \(count)")
}
}
Ölçümlerde adı değiştirilebilen küçük bir sınıfa ihtiyaç var. Sınıfın kendisi bir ölçüm değildir, yalnızca zincirleme örneğine malzeme sağlar; bu yüzden çıktısı yoktur.
// User.swift — adi degistirilebilen kucuk bir sinif
final class User {
var name: String
var renames = 0
init(name: String) {
self.name = name
}
func rename(_ newName: String) {
name = newName
renames += 1
}
}
Koşullu Bağlama
if let number = lookup("12", probe: source) ifadesinde eşitliğin sağ tarafı bir isteğe bağlı değerdir. Swift bu ifadeyi bir kez değerlendirir, sonucu kontrol eder, kutu doluysa içindekini number adına bağlar ve blok gövdesini koşturur. Kutu boşsa gövde hiç çalışmaz. Aşağıdaki ölçümde sağ taraf source.eval ile sarılıdır ve bağlanan ad gövdede iki kez kullanılır.
// main.swift — if let sag tarafi bir kez degerlendirir, acilan ad iki kez kullanilir
let source = Probe("if-let-sag-taraf")
func lookup(_ text: String, probe: Probe) -> Int? {
probe.eval(Int(text))
}
if let number = lookup("12", probe: source) {
_ = source.use { number + 1 }
_ = source.use { number * 2 }
}
source.report()
if-let-sag-taraf: 1 degerlendirme, 2 kullanim -> hemen
Bir değerlendirme, iki kullanım, an hemen. Sağ taraf bloğa girilmeden önce koştu; ad bağlandıktan sonra number sıradan bir tamsayıdır ve onu kullanmak sağ tarafı yeniden çalıştırmaz. Bu, önceki dersteki map ile bir tezat oluşturur: map dönüştürücüyü her çağrıda koşturuyordu, koşullu bağlama ise ifadeyi bir kez koşturup sonucu adda saklar. Aynı sağ tarafı sonradan iki kez okumak istediğimizde bağlanan ad ek bir değerlendirme gerektirmez.
Bağlanan ad yalnızca blok içinde geçerlidir; blok bittiğinde number yoktur. Aynı işlev çağrısını ikinci bir if let içinde yeniden yazmak ise ikinci bir değerlendirme demektir, çünkü if let sağ tarafını bağlama anında koşturur ve sonucu başka bir if let ile paylaşmaz. Değerin birden çok yerde gerekeceği durumda çağrıyı bir kez yazıp sonucu bir adda tutmak gerekir.
TS5. Bu ölçüm sağ tarafı tek bir işlev çağrısı olan if let için. Birden çok koşulun virgülle bağlandığı biçim (if let a, let b) ve where koşulu ölçülmedi.
guard let ve Kısa Biçim
guard let aynı bağlamayı ters yönden yapar. Kutu boşsa else bloğu koşar ve bloğun kapsamı terk etmesi (return, throw, break, continue ya da fatalError gibi dönmeyen bir çağrı) zorunludur; kutu doluysa ad bağlanır ve bağlanan ad guard satırından sonra, işlevin kalan kısmında geçerlidir. Bu yüzden erken çıkış gerektiren kontroller guard ile yazılır: asıl iş girintisiz kalır, hata durumu en üstte toplanır. Kısa biçim olan if let maybe ise adı yeniden yazmadan aynı adla bağlar.
// main.swift — guard let ve kisa if let
func label(for text: String) -> String {
guard let number = Int(text) else {
return "gecersiz"
}
return "sayi \(number)"
}
let maybe: Int? = Int("5")
if let maybe {
print("kisa bicim", maybe)
}
print(label(for: "30"))
print(label(for: "otuz"))
kisa bicim 5 sayi 30 gecersiz
Birinci satır kısa biçimi gösterir: maybe adlı isteğe bağlı değer, aynı adla ve isteğe bağlı olmayan bir tamsayı olarak blok içinde kullanılır. İkinci ve üçüncü satır guard let içeren label(for:) işlevinin iki yolunu gösterir: "30" bağlamayı geçer ve işlevin sonundaki dize oluşur, "otuz" ise else kolundan döner. Bağlanan number adı guard satırından sonra Int tipindedir; kutuyu bir daha açmaya gerek kalmaz.
İki bağlama biçimi arasındaki fark ağırlık dağılımındadır, yoksa ölçülebilen bir değerlendirme farkında değil. if let başarılı yolu bir blokta tutar, guard let hata yolunu bir blokta tutar. Hangi yol uzunsa onu girintisiz bırakan seçim daha okunur sonuç verir.
Yedek Değer: ??
?? işleci iki yandan oluşur. Soldaki isteğe bağlı değer doluysa içindeki değer sonuç olur; boşsa sağdaki yedek değer sonuç olur. Sağdaki ifade, otomatik kapanış (autoclosure) olarak yazılmış bir parametredir: Swift onu bir kapanışa sarar ve işleç yalnızca gerektiğinde çağırır. Bu, önceki dersteki dönüştürücünün bir başka örneğidir: kaynakta yazılı bir ifade, bir yöntem onu çağırana kadar hiç koşmayabilir.
Zincirleme yazıldığında bu davranış bir adım ileri gider. Aşağıda üç aday var: boş bir kutu, second probu ile işaretlenmiş dolu bir kutu ve third probu ile işaretlenmiş bir yedek sayı. Zincir ilk dolu adayda durur. Döngü iki tur döner ve her turda sonuç iki kez kullanılır.
// main.swift — ?? zinciri ilk dolu degerde durur
let second = Probe("ikinci-aday")
let third = Probe("ucuncu-aday")
let nothing: Int? = nil
let found: Int? = 8
for _ in 1...2 {
let result = nothing ?? second.eval(found) ?? third.eval(0)
_ = second.use { result }
_ = third.use { result }
}
second.report()
third.report()
ikinci-aday: 2 degerlendirme, 2 kullanim -> her-kullanimda ucuncu-aday: 0 degerlendirme, 2 kullanim -> hic
İkinci aday her turda değerlendirildi: iki tur, iki değerlendirme, an her kullanımda. Üçüncü aday hiç değerlendirilmedi, çünkü ikinci aday doluydu ve zincir orada kesildi. Yani a ?? b ?? c yazıldığında c yalnızca hem a hem b boşsa hesaplanır. Pahalı bir yedek değeri sağa koymak bu yüzden boşa iş üretmez: ilk aday dolu olduğu sürece o ifade hiç değerlendirilmez.
Bu davranışın bir yan sonucu vardır. Sağ taraftaki ifadenin yan etkisi, örneğin bir sayaç artırımı ya da bir günlük satırı, yalnızca soldaki kutu boşken gerçekleşir. Yan etkinin her durumda olmasını isteyen kod, yan etkiyi ?? ifadesinin içine değil, öncesine yazmalıdır.
TS6. Bu ölçüm üç adaylı bir zincir ve sabit dolu ikinci aday için. Soldaki kutunun dolu olduğu iki adaylı biçim ve yedek değerin bir işlev çağrısı olduğu durum ayrıca ölçülmedi.
İsteğe Bağlı Zincirleme
presentUser?.rename("y") ifadesindeki ? bir soru işaretidir ve bir yokluk kontrolünü işaretler. Alıcı doluysa çağrı yapılır; alıcı boşsa çağrı yapılmaz ve ifadenin değeri nil olur. Çağrının dönüş tipi Int olsaydı zincirleme onu Int? yapardı; bu yüzden zincirlemenin sonucu her zaman isteğe bağlı bir değerdir; dönüş tipi olmayan bir çağrıda bile sonuç Void? olur.
Asıl soru çağrının argümanıdır. absentUser?.rename(skipped.eval("x")) yazıldığında alıcı boştur ve çağrı yapılmaz. Argüman ifadesi çağrıdan önce hesaplanan bir şeydir; peki alıcı boşken de hesaplanır mı? İki alıcıyla ölçelim: biri boş, biri dolu. Her biri için argümanı eval ile işaretli bir probe ve döngü iki tur.
// main.swift — zincir nil'de durunca arguman da degerlendirilmez
let skipped = Probe("nil-alici")
let reached = Probe("dolu-alici")
let absentUser: User? = nil
let presentUser: User? = User(name: "ada")
for _ in 1...2 {
absentUser?.rename(skipped.eval("x"))
presentUser?.rename(reached.eval("y"))
_ = skipped.use { absentUser?.renames }
_ = reached.use { presentUser?.renames }
}
skipped.report()
reached.report()
print(presentUser?.name ?? "yok", presentUser?.renames ?? -1)
nil-alici: 0 degerlendirme, 2 kullanim -> hic dolu-alici: 2 degerlendirme, 2 kullanim -> her-kullanimda y 2
Boş alıcıda argüman hiç değerlendirilmedi. Zincirleme, alıcının boşluğunu gördüğü anda ifadenin geri kalanını atlar; argüman da bu “geri kalan”ın içindedir. Dolu alıcıda argüman her turda bir kez değerlendirildi. Son satır sonucu doğrular: dolu kullanıcının adı y oldu ve iki turda iki yeniden adlandırma sayıldı.
Bu davranışın uzun zincirlerde de sürmesi beklenir: a?.b?.c(expensive()) yazıldığında a ya da b boşsa expensive() koşmamalıdır; bu ders yalnız iki halkalı zinciri ölçtü (TS8). Argümana yazılan yan etkili bir çağrıyı zincirin boş gelebilecek bir halkasının sağına koymak, o yan etkiyi koşullu yapar.
Araçları Birleştirmek
Beş araç tek başına nadiren kullanılır. Yukarıdaki son çıktı satırı iki aracı birlikte gösterir: presentUser?.name ?? "yok" ifadesinde zincirleme kutuyu açıp adı okur, sonuç yine bir kutu olduğu için ?? boş kalan durumda bir yedek dize koyar. Alıcı dolu olduğundan çıktı y oldu; alıcı boş olsaydı yok yazılacaktı. Aynı şekilde presentUser?.renames ?? -1 ifadesi sayaç değerini verir ve boş alıcıda -1 döndürür.
Birleşimin değerlendirme anı bileşenlerinin anlarından çıkar. Zincirleme boş alıcıda geri kalanı atladığı için ?? yalnızca bir kez, zincirin döndürdüğü kutuya bakar. Yedek değer sağdadır ve soldaki dolu olduğu sürece hiç değerlendirilmez. Bu iki kural birbirini tamamlar: yokluk yalnızca bir yerde ele alınır ve ele alındığı noktadan sonra kod isteğe bağlı olmayan bir değerle çalışır.
TS7. Bu birleşim yalnız iki aracın yan yana olduğu durum için ve çıktıdaki son satırdan okundu. Üç ya da daha çok aracın iç içe yazıldığı biçimler ayrıca ölçülmedi.
Sınırlayıcı Ölçüm
Zincirleme için kurduğumuz kural şu: boş alıcıda argüman hiç değerlendirilmez. Önceki ölçümde alıcı doğrudan bir sabitti ve en baştan boştu. Peki yokluk zincirin ortasında, bir yöntem çağrısının sonucunda ortaya çıkarsa ne olur? Aşağıda find yöntemi her zaman nil döndürür. Bir ifade find("ada")?.rename(...) ile boş kutuya çağrı yapar; aynı satırdaki ikinci eleman ise zincirin parçası değil, ayrı bir ifadedir.
// main.swift — zincirin ortasinda bos donen halka, sonraki cagrinin argumanini atlatir
struct Directory {
func find(_ name: String) -> User? {
nil
}
}
let tail = Probe("orta-bos-sonraki")
let free = Probe("orta-bos-bagimsiz")
let directory = Directory()
for _ in 1...2 {
_ = (directory.find("ada")?.rename(tail.eval("z")), free.eval("w"))
_ = tail.use { 0 }
_ = free.use { 0 }
}
tail.report()
free.report()
orta-bos-sonraki: 0 degerlendirme, 2 kullanim -> hic orta-bos-bagimsiz: 2 degerlendirme, 2 kullanim -> her-kullanimda
Zincirin ortasında doğan yokluk da sonraki çağrının argümanını atlattı: find boş döndüğünde rename çağrısı yapılmadı ve onun argümanı hiç değerlendirilmedi. Aynı satırda, virgülle ayrılmış ikinci elemandaki bağımsız ifade ise her turda değerlendirildi. Atlanan yer, ? işaretinden sonra gelen zincirin kendisidir; aynı satırda yazılmış olmak bir ifadeyi zincirin parçası yapmaz.
Bu sonuç dersin kuralının geçerli olduğu alanı daraltır. “Boş alıcı argümanı değerlendirmez” cümlesi, zincirin boş kalan halkasından sonra gelen ifadeler için geçerlidir ve ölçülen iki halkada yokluğun nerede doğduğuna bağlı değildi. Zincirin dışındaki ifadeler bu kuralın kapsamına girmez.
TS8. Bu ölçüm iki halkalı bir zincir için. Üç ya da daha çok halkalı zincirlerde ortadaki bir ? işaretinin davranışı ve alıcının bir özellik olduğu durum ayrıca ölçülmedi.
Zorla Açma
Zorla açma işleci bu derste ölçülmez. Çünkü ölçülecek bir değerlendirme anı yoktur: kutu doluysa içindeki değer alınır, boşsa program durur. Aşağıdaki parça derlenir ama çalıştırıldığında çalışma zamanı hatası verir; çıktı iddiası taşımadığı için burada yalnız kaynak olarak durur.
let text = "otuz" let broken = Int(text)! // calisma zamaninda coker: kutu bos print(broken)
Zorla açma, bir yokluk kontrolünü derleme zamanından çalışma zamanına taşır. Derleyicinin tip sistemiyle sağladığı güvence burada kalkar: Int? tipindeki değer, yazarın “bu kutu dolu” sözüne bağlı kalarak Int olarak kullanılır. Söz tutulmazsa program durur. Yokluğun kanıtlanmış biçimde imkânsız olduğu yerlerde, örneğin kaynak kodda sabit yazılmış ve geçerli olduğu bilinen bir metnin dönüştürülmesinde, zorla açma savunulabilir; kullanıcıdan ya da dışarıdan gelen veride savunulamaz.
Özet
if letsağ tarafı bir kez ve bloğa girmeden önce değerlendirir; bağlanan ad bir daha hesap yaptırmaz.guard letaynı bağlamayı erken çıkışla yazar; bağlanan adguardsatırından sonra geçerlidir.??zinciri ilk dolu adayda durur; sağdaki aday yalnızca soldaki boşsa hesaplanır.- İsteğe bağlı zincirleme boş alıcıda
?işaretinden sağdaki çağrıyı ve argümanını atlar. - Zorla açma yokluk kontrolünü çalışma zamanına taşır ve ölçülecek bir an taşımaz.
- Bu dersin anı: hemen.
if letsağ tarafı bloğa girmeden önce bir kez değerlendirilir.
Sonraki Adım
Bu derste kutular açıldı ve içlerindeki değerler kullanıldı. Değerlerin kendisi iki yoldan biriyle tanımlanır: kopyalanan yapılarla ya da paylaşılan sınıflarla. Bir sonraki ders yapılar ve sınıflar arasındaki farkı, kopyanın tam olarak hangi anda oluştuğunu ölçerek inceler.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.