Bu döküman, Swift programlama dili ve iOS uygulama geliştirme süreçlerindeki temel kavramları, mülakatlarda sıkça sorulan soruları ve modern çözüm yaklaşımlarını içermektedir.
- Variables: let vs var
- Optionals Nedir?
- Unwrapping Yöntemleri
- Optional Chaining
- Value Type vs Reference Type
- Class ve Struct Farkları
- Array / Dictionary / Set
- Equatable ve Hashable
- Swift Access Control
- View Lifecycle (Yaşam Döngüsü)
- Extension Nedir?
- Frame ve Bounds Farkı
- Layout Lifecycle (viewDidLayoutSubviews)
- ARC (Hafıza Yönetimi)
- Closure & Escaping & Completion Handler
- Alamofire ile Gerçek Senaryo
- Enum & Raw Values
- let: Immutable (değişmez) değerler tanımlar. Bir kez değer atandığında (initialize edildiğinde) hafızadaki o değer değiştirilemez.
- var: Mutable (değişebilir) değerler tanımlar. İstediğin zaman yeni bir değer atayabilirsin.
"Swift'te let immutability (değişmezlik) sağlar. Ancak class gibi referans tiplerinde let sadece referans adresini tutar, nesnenin iç durumu (state) değişebilir; struct gibi değer tiplerinde ise nesnenin tamamını kilitler."
Optional, Swift’te bir değişkenin değer içerebileceğini ya da nil olabileceğini ifade eden bir tiptir.
- Amaç:
nilkaynaklı hataları (crash) daha kod yazarken önlemektir. - Neden var? → Runtime crash olmasını engeller.
var name: String? -> Swift: "Değer verilmezse nil kabul ederim, sorun yok."
class HomeVC: UIViewController {
var name: String?
override func viewDidLoad() {
super.viewDidLoad()
print(name)
}
}
// Çıktı -> nil
// crash olmaz, sorun çıkarmaz.let name: String? -> Swift: "Bu bir sabit. Değerini açıkça sen vermelisin, ben kafama göre nil atamam."
class HomeVC: UIViewController {
let name: String?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
print(name)
}
}
// Hata verir compile error -> Class 'HomeVC' has no initializersBir Optional değişkenimiz olsun: var gelenVeri: String?
Bu veriyi kullanmanın 4 temel yolu vardır:
- Mantık: "Bak bakalım
gelenVeridolu mu? Eğer doluysa, veriyi al yeni bir isme ata ve süslü parantez içinde kullan.nilise hiçbir şey yapma (veya else bloğuna git)." - Sonuç: Uygulama asla çökmez (Safe).
Doluysa:
var myData: String? = "Ugur hm"
// 'myData' dolu olduğu için if bloğuna girer
if let safeData = myData {
print(safeData) // Burada artık 'safeData' normal String'dir, ? kalkmıştır.
}Nil ise:
var name: String? // Swift otomatik nil atar.
if let myName = name {
print(myName)
}
// if let içine hiç girmez. Hata vermez, uygulama çalışmaya devam eder.- Mantık: "
gelenVeridolu mu? Doluysa onu kullan. Boşsa (nil), sağ taraftaki varsayılan değeri kullan." - Sonuç: Elinde her zaman kesin bir veri olur.
// Veri yoksa otomatik olarak "Bilinmiyor" yazar.
let sonuc = gelenVeri ?? "Bilinmiyor"Genelde fonksiyonların başında kullanılır. Hatalı durumu en başta eleyip (return), doğru durumu aşağıda kullanmaya odaklanır.
Veri nil ise → guard let içine (else bloğuna) girer:
class HomeVC: UIViewController {
var name: String? // Swift otomatik nil atar
override func viewDidLoad() {
super.viewDidLoad()
guard let myName = name else {
print("Hello World") // Kod buraya düşer, çünkü name başlangıçta nil
return // Fonksiyondan çıkar
}
print(myName) // Burası çalışmaz
}
}
// Ekran Çıktısı -> Hello WorldVeri dolu ise → guard let else bloğuna girmez, aşağı devam eder:
class HomeVC: UIViewController {
var name: String? = "Ugur Hamzaoglu"
override func viewDidLoad() {
super.viewDidLoad()
// name'i optionalliktan çıkarıp myName'e atar.
// myName artık bu scope'un geri kalanında kullanılabilir.
guard let myName = name else {
print("Hata")
return
}
print(myName)
}
}
// Ekran Çıktısı -> Ugur Hamzaoglu- Optional bir değeri kontrol etmeden zorla açar.
- Bu değerin kesinlikle
nilolmadığını garanti etmiş oluruz (programcı garantisi). ⚠️ Risk: Eğer değernilise, uygulama run-time'da crash olur (çöker).- Öneri: Kullanımından mümkün olduğunca kaçınılmalıdır.
Zincirleme olarak iç içe geçmiş optional değerlere güvenli bir şekilde erişmeyi sağlar.
Örnek: user?.profile?.email
uservar mı?- Varsa →
profilevar mı? - Varsa →
emaildeğerine ulaş.
Eğer zincirdeki herhangi bir halka nil ise:
- ❌ Crash olmaz.
- ✅ Tüm işlemin sonucu
nilolur.
class User {
var profile: Profile?
}
class Profile {
var email: String?
}
class HomeVC: UIViewController {
var user: User? // Başlangıç değeri atanmadığı için nil
override func viewDidLoad() {
super.viewDidLoad()
// Optional Chaining Kullanımı
// user nil olduğu için profile bakmaz, direkt nil döner.
let email = user?.profile?.email
if let safeEmail = email {
print(safeEmail)
} else {
print("Email bulunamadi") // Buraya gelir, ekrana bunu yazdırır.
}
}
}Temel fark: Verinin hafızada nasıl taşındığıdır.
- Value Type → Verinin kendisini kopyalar.
- Reference Type → Verinin adresini paylaşır.
- Struct
- Enum
- Tuple
- Temel Tipler (Int, String, Double, Float, Bool)
- Koleksiyonlar (Array, Dictionary, Set)
- Optional, Result, Date
Davranış: Bir değişkeni başka bir değişkene atadığında, sistem o verinin birebir kopyasını oluşturur. İkisi birbirinden tamamen bağımsızdır.
- Class
- Closure
- Function
- Actor
- NSObject, UIView / UIViewController
- URLSession, NSManagedObject
- DispatchQueue, Timer
Davranış: Bir değişkeni başkasına atadığında, verinin kendisi kopyalanmaz. Sadece o verinin hafızadaki adresi (pointer) kopyalanır. İkisi de aynı kaynağa bakar.
Burası işlemcinin (CPU) doğrudan ve en hızlı eriştiği, geçici çalışma alanıdır.
- Barındırdığı: Value Types (Struct, Enum, Int, Bool...).
- Özellikler:
- Çok hızlıdır.
- LIFO (Last In First Out) mantığıyla çalışır.
- Fonksiyon bitince otomatik temizlenir.
- Thread-safe'tir.
Burası uygulamanın uzun süreli ve büyük verilerinin saklandığı, dinamik alandır.
- Barındırdığı: Reference type’lar (Class Instance'ları).
- Özellikler:
- Stack’e göre daha yavaştır.
- Dinamik bellek alanıdır.
- ARC (Automatic Reference Counting) ile yönetilir.
- Retain cycle riski vardır.
Soru: "Struct’lar her zaman stack’te mi tutulur?"
Cevap: Hayır. Özellikle Copy-on-Write kullanan yapılar istisnadır:
- Array
- Dictionary
- String
- Set
Bu yapılarda durum şöyledir:
- Yapının kendisi: Stack
- İç veri (buffer): Heap
| Özellik | Struct (Yapı) | Class (Sınıf) |
|---|---|---|
| Tip Türü | Value Type (Değer Tipi) | Reference Type (Referans Tipi) |
| Hafıza Bölgesi | Stack (Genellikle). Çok hızlıdır. | Heap. Maliyetlidir, yer arama ve temizleme gerekir. |
| Veri Aktarımı | Kopyalanır. var a = b dersen b kopyalanıp a olur. |
Paylaşılır. var a = b dersen ikisi de aynı adrese bakar. |
| Miras (Inheritance) | Yoktur. Başkasından türetilemez. | Vardır. Başka bir Class'tan özellik alabilir. |
| Initializer (Init) | Otomatik gelir (Memberwise Init). Yazmana gerek yoktur. | Zorunludur. Başlangıç değerlerini elle atamalısın. |
| Değişkenlik | Fonksiyon içinde özellik değiştirmek için mutating yazmak şarttır. |
mutating gerekmez. Doğrudan değişebilir. |
| Hafıza Yönetimi | İşletim sistemi otomatik halleder (Scope bitince silinir). | ARC (Automatic Reference Counting) yönetir. |
| Kimlik (Identity) | Yoktur. === operatörü kullanılamaz. |
Vardır. === ile "aynı nesne mi?" diye bakılır. |
| Deinit | Yoktur. | Vardır (deinit ile bellekten silindiğini yakalarsın). |
Swift’te Miras Alma (Inheritance) özelliği SADECE CLASS'lara (Referans Tiplerine) özgüdür.
- ❌ Bir Class, bir Struct'ı miras alamaz.
- ❌ Bir Struct, bir Class'ı miras alamaz.
- ❌ Bir Struct, başka bir Struct'ı da miras alamaz.
Mülakatçı: "Tamam miras alamıyor ama ben hem struct hem class kullanıp ortak özellikler kazandırmak istiyorum, ne yapacağım?" derse, cevabın Protocol olmalı.
- Swift, "Protocol Oriented Programming" (Protokol Odaklı Programlama) dilidir.
- Miras almak yerine, bir "sözleşme" (protocol) imzalatırız.
Verileri belirli bir sırada (index) tutar.
- Özellik: Sıralıdır (Ordered). Eklediğin sırayla durur. Duplicate (tekrar eden) eleman serbesttir.
- Erişim: Index biliniyorsa direkt gider. Ama arama yapmak için baştan sona bakar.
Verileri Key-Value (Anahtar-Değer) eşleşmesi olarak tutar.
- Özellik: Sırasızdır (Unordered).
- Kural: Key (Anahtar) benzersiz (unique) olmak zorundadır. Value (Değer) aynı olabilir.
- Çalışma: Hash table kullanır. Doğrudan doğru bucket’a (kutucuğa) gider.
- Özellik: Sırasızdır (Unordered). Karışık bir torba gibidir.
- Kural: Tekrarı asla kabul etmez. Her eleman sadece 1 kere bulunabilir.
- Erişim: Index yoktur. "İçinde var mı?" (
contains) diye sorarsınız. Arama, ekleme ve silme işlemleri hash değerine bakarak doğrudan yapılır.
Not: Dictionary ve Set sıralı değildir. Hızlı olmalarının sebebi Hashing'dir. Bu yüzden Hashable protokolü zorunludur.
Senaryo: "Elimde 1 milyon eleman var. Bir elemanın listede olup olmadığını kontrol edeceğim (contains). Array mi kullanayım Set mi?"
- Cevap: Kesinlikle Set (veya Dictionary).
- Neden?
- Array: Tek tek en baştan sona kadar bakar (Linear Search -
O(n)). Çok yavaştır. - Set: Verinin "Hash" değerine bakar ve nokta atışı bulur (Constant Time -
O(1)). Eleman sayısı artsa da hız değişmez.
- Array: Tek tek en baştan sona kadar bakar (Linear Search -
Senaryo: "Bir Array'e sürekli append ile eleman ekliyoruz. Bu işlem her zaman çok hızlı (O(1)) mıdır?"
- ❌ Yanlış Cevap: "Evet, sona eklemek her zaman hızlıdır."
- ✅ Doğru Cevap (Senior): "Genellikle hızlıdır (
O(1)). ANCAK, Array'in kapasitesi dolduğunda, Swift arka planda daha büyük yeni bir Array oluşturur ve eski elemanların hepsini oraya kopyalar. O anlık işlemO(n)olur ve maliyetlidir." - Çözüm: Eğer elinde kaç eleman olacağı yaklaşık belliyse (örneğin 10.000 eleman), Swift'e baştan yer ayırtırsın (
reserveCapacity). Böylece Array dolup taşmaz, kopyalama yapmaz.
Senaryo: "1 milyon elemanlı bir listem var. İçindeki ilk 'negatif' sayıyı bulmak istiyorum. filter mı kullanayım, first(where:) mi?"
- Tuzak: İkisi de aynı sonucu verir ama çalışma mantıkları farklıdır.
- ❌ Yanlış Cevap:
numbers.filter { $0 < 0 }.firstyazarım. - ✅ Doğru Cevap: Kesinlikle
first(where:)kullanırım. - Neden?
- filter: Listenin sonuna kadar gider (
O(n)). Hepsini tarar, negatif olanlardan hafızada yeni bir Array oluşturur. Sonra onun ilkini alır. (Çok işlem, ekstra hafıza). - first(where:): Baştan başlar, ilk bulduğu anda DURUR ve döner (Early Exit). Listede 1 milyon eleman olsa bile, negatif sayı 2. sıradaysa sadece 2 işlem yapar.
- filter: Listenin sonuna kadar gider (
Senaryo: "Kendi oluşturduğum bir Ogrenci class'ını Dictionary'de 'Key' (Anahtar) olarak kullanabilir miyim?"
var sinif: [Ogrenci: Int] = [:]- Cevap: "Hayır, doğrudan kullanamam. O class'ın Hashable protokolüne uyması gerekir."
- Neden? Set ve Dictionary'nin
O(1)hızında çalışabilmesi (nokta atışı bulması) için, o anahtarın benzersiz bir Hash ID (Parmak izi) üretmesi gerekir. Swift bu parmak izine bakarak verinin hangi rafta olduğunu bulur. EğerHashableyapmazsan, Swift o veriyi nereye koyacağını bilemez ve derleyici hata verir.
İki nesnenin birbirine eşit olup olmadığını (== operatörü ile) kontrol etmeni sağlayan protokoldür.
- Ne İşe Yarar: Swift, senin oluşturduğun özel bir
Userstruct'ının eşitliğini nasıl kontrol edeceğini bilmez. (İsmi mi aynı olmalı? ID'si mi?).Equatableile ona bu kuralı öğretirsin. - Kullanım Alanı:
if user1 == user2,array.contains(user1),array.firstIndex(of: user1)gibi işlemlerde zorunludur.
Bir nesneyi benzersiz bir Tam Sayıya (Integer Hash Value) dönüştüren protokoldür.
- Ne İşe Yarar: Nesnenin hafızadaki "adresini" hesaplamak için kullanılır. Bu sayede arama yaparken tek tek gezmek yerine, doğrudan o adrese gidilir.
- Kullanım Alanı:
Setiçine eleman eklerken veyaDictionary'de Key olarak kullanırken ZORUNLUDUR.
- Çoğu struct otomatik olarak
EquatableveHashableolur. - Yani çoğu zaman elle implement etmeye gerek yoktur.
- Equatable: Eğer içerik bazlı karşılaştırma istiyorsan
Equatableimplement etmelisin. - Hashable: Kullanmak istiyorsan
hash(into:)ve==fonksiyonlarını elle yazmalısın.
class User: Hashable {
var id: Int
init(id: Int) { self.id = id }
// Equatable Gerekliliği
static func == (lhs: User, rhs: User) -> Bool {
return lhs.id == rhs.id
}
// Hashable Gerekliliği
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}==(Eşitlik): "İçindeki veriler aynı mı?" (Örn: İkisinin de adı "Ali" mi?)===(Kimlik): "Bunlar fiziksel olarak aynı nesne mi?" (Örn: Hafıza adresleri0x123ve0x123mü?)
=== operatörü SADECE CLASS (Referans tipleri) için kullanılır. Struct'larda (Değer tipleri) kullanılamaz, çünkü Struct'ların hafızada sabit bir kimliği yoktur, sürekli kopyalanırlar.
Swift'te erişim belirleyiciler, kodunuzun diğer dosyalar veya modüller tarafından ne kadar görünebilir olduğunu yönetir.
- Kapsam: Sadece tanımlandığı süslü parantezler
{ }içinde (ve aynı dosyadaki extension'larında) geçerlidir. - Özellik: Dışarıdan kimse göremez.
- Kullanım: Sadece o sınıfın iç işleyişini ilgilendiren yardımcı fonksiyonlar veya değişkenler için kullanılır.
- Kapsam: Sadece bulunduğu
.swiftdosyasının içinde geçerlidir. - Özellik: Aynı dosya içinde farklı class veya struct'lar varsa birbirini görebilir.
- Kullanım: Bir dosyada birden fazla class/extension yazıp, birbirlerinin özel verilerine erişmesini istediğinizde kullanılır.
- Kapsam: Hiçbir şey yazmazsanız otomatik olarak budur.
- Özellik: Sadece kendi uygulaman (modülün) içindeki her yerden erişilebilir.
- Sınır: Uygulamanın dışından (başka bir framework'ten) görülemez.
- Kapsam: Başka modüllerden (frameworklerden) erişilebilir.
- Kritik Kısıtlama: Başka modülde miras alınamaz (subclass) veya override edilemez. Sadece "kullanılabilir".
- Kapsam: En geniş erişim seviyesidir. Sadece Class'lar için geçerlidir.
- Özellik: Başka modüllerden erişilebilir, miras alınabilir ve override edilebilir.
- Kullanım: UIKit içindeki
UIViewControllerveyaUITableViewgibi, sizin geliştirip üzerine kod yazdığınız kütüphane elemanlarıopendır.
Soru: "Bir kütüphane yazdım, public class ile open class arasındaki fark nedir?"
Cevap:
- public: Sınıfı başka modülde sadece kullanabilirim (instance oluşturabilirim) ama ondan türetip (inheritance) özelliklerini değiştiremem (override).
- open: Buna izin verir; yani o sınıfı miras alıp genişletebilirim.
Hiçbir şey yazmadan standart bir tanımlama yaparsan, Swift bunu otomatik olarak internal kabul eder.
- Ne Anlama Geliyor? Bu kodu, yazdığın uygulamanın (App Target / Modül) her yerinde kullanabilirsin. Ancak bu kodu bir kütüphane (Framework) haline getirip başka bir projeye eklersen, oradan görünmez.
Mülakat Notu: Eğer mülakatçı "Neden default olarak public değil de internal?" derse:
"Çünkü Swift güvenliği ön planda tutar. Yazdığım kodun varsayılan olarak dış dünyaya (API'lara) kapalı olmasını, sadece kendi projem içinde rahatça çalışmasını ister."
iOS'te bir ekranın (ViewController) oluşturulmasından yok edilmesine kadar geçen süreçtir.
- Ne Zaman Çalışır: Ekran hafızaya yüklendiğinde sadece 1 kez çalışır.
- Ne Yapılır:
- Ekranın ilk kurulumu (Initial Setup).
- Değişkenlere ilk değer atama.
- Network isteği atma (Genellikle).
delegatevedataSourceatamaları (TableView vs).
- Mülakat Notu: Ekranı kapatıp geri gelmediğin sürece (Navigation Stack'te kaldığı sürece) bir daha çalışmaz.
- Ne Zaman Çalışır: Ekran kullanıcıya görünmeden hemen önce çalışır. Her sayfaya giriş-çıkışta tekrar tetiklenir.
- Ne Yapılır:
- Ekrandaki veriyi güncelleme (Örn: Profil sayfasına her girdiğinde Coin miktarını yenilemek).
- Navigation Bar veya Tab Bar gizleme/gösterme işlemleri.
- Klavye olaylarını dinlemeye başlama (
NotificationCenter addObserver).
- Ne Zaman Çalışır: Ekran tamamen ekrana geldikten hemen sonra çalışır.
- Ne Yapılır:
- Animasyonları başlatmak.
- Analytics event göndermek ("Kullanıcı bu sayfayı gördü" verisi).
- GPS/Konum servisini başlatmak.
- Ne Zaman Çalışır: Kullanıcı ekrandan ayrılmaya başladığı an (Back tuşuna bastığı an) çalışır.
- Ne Yapılır:
- Animasyonları durdurmak.
- Klavye'yi kapatmak (
view.endEditing(true)). - Yapılan değişiklikleri kaydetmek (Autosave).
- Ne Zaman Çalışır: Ekran tamamen kaybolduktan sonra çalışır.
- Ne Yapılır:
- NotificationCenter dinlemeyi durdurmak (
removeObserver). - Ağır işlemleri (Timer, Sensor) tamamen durdurmak.
- NotificationCenter dinlemeyi durdurmak (
Soru: "Kullanıcı profil sayfasına gitti, adını değiştirdi ve geri tuşuna basıp Ana Sayfa'ya döndü. Ana Sayfa'daki 'Hoşgeldin İsim' yazısını güncellemek için kodu viewDidLoad'a mı yazarsın viewWillAppear'a mı?"
Cevap: Kesinlikle viewWillAppear içine yazarım.
Neden?
- viewDidLoad: Ana Sayfa hafızada hala durduğu için (Stack'ten silinmediği için), geri dönüldüğünde tekrar çalışmaz. İsim eski kalır.
- viewWillAppear: Ekran her göründüğünde çalıştığı için, geri dönüldüğünde güncel ismi yakalar ve ekrana basar.
| Metod | Çalışma Sıklığı | Temel Görev |
|---|---|---|
| viewDidLoad | 1 Kez (O ekran RAM'de canlı kaldığı sürece). | UI Kurulumu, API İsteği |
| viewWillAppear | Her Girişte | Veri güncelleme, UI tazelemeleri |
| viewDidAppear | Her Girişte | Animasyon başlatma, Analytics |
| viewWillDisappear | Her Çıkışta | Klavye kapatma, Kayıt |
| viewDidDisappear | Her Çıkışta | Temizlik, Dinlemeyi durdurma |
| Senaryo | viewDidLoad Çalışır mı? | Neden? |
|---|---|---|
| Uygulama İlk Açılışı | ✅ EVET | Ekran ilk kez hafızaya giriyor. |
| Başka Ekrana Gidip "GERİ" Dönmek | ❌ HAYIR | Ekran zaten hafızada (arkada) bekliyordu. |
| Tab Bar'da Başka Taba Gidip Dönmek | ❌ HAYIR | Tab'lar genelde hafızada tutulur, silinmez. |
| Ekranı Kapatıp (Back) Sonra Tekrar Girmek | ✅ EVET | Ekran hafızadan silinmişti, (Back tuşu ekranı öldürür) yeniden yaratıldı. |
loadView()→ View oluşturulur (Custom view yoksa elleme).viewDidLoad()→ View yüklendi, initial setup.viewWillAppear()→ Ekran görünmeden hemen önce.viewDidAppear()→ Ekran göründükten sonra.viewWillDisappear()→ Ekran kaybolmadan önce.viewDidDisappear()→ Ekran kaybolduktan sonra.deinit→ Controller hafızadan silinir.
Extension (Genişletme): Class, Struct, Enum veya Protocol gibi mevcut türlere, orijinal kaynak koduna erişimin olmasa bile yeni özellikler (fonksiyon, hesaplanan değişken) ekleme yeteneğidir.
Soru: "Extension ile stored property (hafızada yer kaplayan normal değişken) ekleyebilir miyim?"
- ❌ Cevap: HAYIR!
- Neden: Extension, o yapının hafızadaki boyutunu (memory footprint) değiştiremez. Sadece var olan veriyi kullanarak hesaplama yapabilir (computed property) veya fonksiyon ekleyebilir.
// ❌ YANLIŞ (Stored Property eklenemez)
extension Int {
var name: String = "Ali"
}
// ✅ DOĞRU (Computed Property eklenebilir)
extension Int {
var isEven: Bool { return self % 2 == 0 }
}| Özellik | Frame | Bounds |
|---|---|---|
| Referans Noktası | Parent (Superview) | Self (Kendisi) |
| Origin (x, y) | Superview'un sol üst köşesine uzaklığı. | Genellikle (0,0). (ScrollView kaydırılınca değişir). |
| Size (En, Boy) | Görünümü kaplayan dikdörtgen kutu. | Görünümün gerçek boyutları. |
| Rotasyon Etkisi | Dönünce boyutları değişir/büyür (kapsayıcı kutu büyür). | Dönse bile boyutları aynı kalır. |
| Ne Zaman Kullanılır? | Yerleştirme (Positioning), Animasyon. | İç Çizim (Drawing), Scroll işlemleri. |
"Hesaplama Başlamak Üzere"
Bu metod çağrıldığında, View Controller'ın ana view'ının sınırları (bounds) değişmiştir (örneğin telefon yatay moda geçmiştir), ancak alt görünümler (subviews) henüz yeni yerlerine ve boyutlarına oturtulmamıştır.
- Durum: Frame'ler henüz güncel değildir (eski değerlerde olabilir).
- Ne Zaman Çalışır? Auto Layout hesaplaması başlamadan hemen önce.
- Burada Ne Yapılır? Genellikle çok aktif kullanılmaz.
- Layout hesaplaması başlamadan önce eklenmesi veya kaldırılması gereken görünümler varsa burada müdahale edilebilir.
- Constraint'lerde son dakika değişikliği (update) yapılacaksa burası kullanılabilir.
"Hesaplama Bitti, Her Şey Yerli Yerinde"
Bu metod çağrıldığında, Auto Layout motoru işini bitirmiştir. Tüm butonlar, textler, resimler ekrandaki nihai yerlerine (frame) ve boyutlarına kavuşmuştur.
- Durum: Frame ve Bounds değerleri artık kesindir ve doğrudur.
- Ne Zaman Çalışır? Auto Layout hesaplaması bittikten hemen sonra.
- Burada Ne Yapılır? (Çok Önemli)
- Layer İşlemleri (Gradient, Shadow, Border):
viewDidLoadiçinde bir view'a gradient verirsen veyacornerRadiusile tam yuvarlak yapmak istersen bazen hatalı görünür. ÇünküviewDidLoadanında ekranın genişliği henüz tam hesaplanmamış olabilir. Burası, frame'e bağlı UI makyajı için en doğru yerdir. - Yuvarlak Buton Yapma:
button.layer.cornerRadius = button.frame.height / 2kodunu buraya yazarsan, buton boyutu ne olursa olsun her zaman tam yuvarlak olur. - ScrollView ContentSize: Manuel ScrollView yapıyorsan
contentSizeburada güncellenir.
- Layer İşlemleri (Gradient, Shadow, Border):
viewDidLayoutSubviews içinde, ekranın yerleşimini tekrar tetikleyecek bir kod yazarsan (örneğin view.layoutIfNeeded() çağırırsan veya bir constraint'i değiştirirsen), sistem tekrar layout hesaplar, tekrar bu metoda girer, tekrar hesaplar... Uygulama donar (crash).
Özet: "Frame'e (boyutlara) bağlı görsel işlemler (layer, gradient, radius) için viewDidLayoutSubviews kullanırım çünkü viewDidLoad aşamasında frame değerleri henüz kesinleşmemiş olabilir."
Swift’in RAM (Hafıza) yönetim sistemidir.
- Çalışma Mantığı: Basittir. Bir obje oluşturulduğunda ve bir değişkene atandığında, ARC o objenin "Referans Sayısını" 1 artırır. O değişkenle işin bittiğinde veya değişken
nilolduğunda sayıyı 1 azaltır. - Kural: Referans sayısı 0 olduğu an, obje hafızadan (RAM) anında silinir (Deallocation).
- Amaç: Kullanılmayan objeleri silerek RAM'i boşaltmak.
ARC'nin sayıyı artırıp artırmayacağını belirleyen anahtar kelimelerdir.
- Durum: Varsayılandır. (
var user = User()dediğinde strong olur). - Etki: Referans sayısını +1 artırır.
- Anlamı: "Ben bu objeyi tutuyorum, ben bırakana kadar silinmesin."
- Risk: Güvenlidir ancak Retain Cycle yaratma riski vardır.
- Etki: Referans sayısını artırmaz (+0).
- Davranış: Obje silinirse (referans sayısı 0 olursa), bu değişken otomatik olarak
nilolur. - Kural: Bu yüzden weak değişkenler her zaman Optional (
var?) olmak zorundadır. - Kullanım: Retain Cycle'ı kırmak için en çok kullanılan yöntemdir.
- Etki: Referans sayısını artırmaz (+0).
- Fark:
weakgibidir AMA Optional değildir. - Garanti: "Ben bu objeye eriştiğimde, obje hafızada kesinlikle var olacak."
- Risk: Eğer obje silinmişse ve sen
unownedreferansa erişmeye çalışırsan uygulama çöker (crash).
İki objenin birbirini Strong (Güçlü) olarak tutması durumudur.
- A objesi B'yi tutuyor (+1).
- B objesi A'yı tutuyor (+1).
- Sen bu iki objeyi kullanmayı bıraksan bile, birbirlerini tuttukları için referans sayıları asla 0'a düşmez.
- Sonuç: ARC bu objeleri asla silemez -> Memory Leak.
Zincirin bir halkasını zayıflatman gerekir. İki taraf da birbirini Strong tutamaz.
1. Delegate Pattern'de:
Delegate değişkeni her zaman weak tanımlanmalıdır.
weak var delegate: SomeDelegate?2. Closure Kullanımında:
Closure içinde self kullanılacaksa, [weak self] yapısı kullanılmalıdır.
// ❌ Yanlış (Retain Cycle yapar):
profilGetir { sonuc in
self.ismiGuncelle() // self strong tutuluyor
}
// ✅ Doğru:
profilGetir { [weak self] sonuc in
self?.ismiGuncelle() // self weak tutuluyor, referans artmaz
}Retain Cycle sonucu oluşan durumdur. Uygulama içinde artık erişilemeyen, kullanılmayan ama RAM'de yer kaplamaya devam eden veri yığınıdır.
Bir closure (blok), bir fonksiyona parametre olarak geçildiğinde; eğer bu closure, fonksiyonun gövdesi (scope) çalışmasını bitirdikten sonra çağrılıyorsa, bu closure "escaping" (kaçan) olarak adlandırılır.
- Varsayılan Durum: Swift'te tüm closure'lar varsayılan olarak
non-escaping'dir (fonksiyon bitmeden çalışmak zorundadırlar). - Derleyici Bildirimi: Bu durumu belirtmek için
@escapinganahtar kelimesi kullanılır. - Bellek Yönetimi: Bu closure'lar fonksiyon bittikten sonra da çalışacakları için bellekte (heap) tutulmaya devam ederler.
Bir dil özelliği değil, bir dizayn desenidir (design pattern). Bir işlemin (genellikle uzun süren bir işin) bittiğini, sonucun başarılı mı yoksa hatalı mı olduğunu çağıran tarafa haber vermek için kullanılan yöntemdir.
Evet, genellikle vardır. * Her escaping closure asenkron olmak zorunda değildir (örneğin bir değişken dışarıya atanıp sonra çalıştırılabilir).
- Ancak Completion Handler'ların neredeyse tamamı asenkron işlemler (network, veritabanı, animasyon) için kullanılır. İşlem arka planda devam ederken ana fonksiyon sonlanır; iş bittiğinde ise closure "kaçar" ve tetiklenir.
| Özellik | Normal Fonksiyon (return) | Completion Handler (Escaping) |
|---|---|---|
| Zamanlama | Hemen değer döner (Synchronous). | İşlem bittiğinde (belirsiz süre sonra) döner. |
| Bloklama | İşlem bitene kadar thread bekler. | Thread serbest kalır, iş bitince tetiklenir. |
| Kontrol | Tek bir değer dönebilir. | Birden fazla kez çağrılabilir veya hiç çağrılmayabilir. |
| Kapsam | Fonksiyon bitince değişkenler ölür. | Değişkenler "capture" edilir ve yaşatılır. |
- Network İstekleri: Sunucudan veri beklerken UI'ı dondurmamak için.
- Animasyonlar: Bir animasyon bittiğinde başka bir işlemi başlatmak için.
- Dosya İşlemleri: Büyük bir dosyayı okuma/yazma işlemi tamamlandığında haber almak için.
Swift 5.5 ile gelen async/await yapısı artık sektör standardı haline gelmektedir.
Neden Async/Await?
- Callback Hell: İç içe geçen closure'ların (Pyramid of Doom) önüne geçer.
- Hata Yönetimi:
try-catchblokları ile daha temiz kod sağlar. - Okunabilirlik: Asenkron kodu, sanki senkronmuş gibi yukarıdan aşağıya okumayı sağlar.
- Güvenlik: Closure'da "unutup çağırmama" riski varken,
asyncfonksiyonlar mutlaka bir değer veya hata dönmek zorundadır.
Klasik Yaklaşım (Completion Handler):
func fetchData(url: String, completion: @escaping (Result<Data, Error>) -> Void) {
URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in
if let error = error {
completion(.failure(error)) // Fonksiyon bittikten sonra çağrılıyor
return
}
if let data = data {
completion(.success(data)) // Kaçış (Escape) gerçekleşiyor
}
}.resume()
}Modern Yaklaşım (Async/Await):
func fetchData(url: String) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: URL(string: url)!)
return data
}Özet: > * @escaping: Fonksiyonun kapsamından sağ kurtulan kod bloğudur.
- Completion Handler: "İşim bitti, al bu da sonuç" deme yöntemidir.
- Async/Await: Bu işlemlerin en modern ve güvenli halidir.
"Closure'lar isimsiz kod bloklarıdır. Eğer bu bloklar fonksiyonun ömründen daha uzun süre yaşayacaksa (örneğin bir network isteği bittiğinde çalışacaksa), onları @escaping olarak işaretleriz. Bu yapıya genellikle Completion Handler deriz; ancak güncel projelerde bu yapının yerini daha temiz ve güvenli olan Async/Await almaktadır."
Profesyonel projelerde asenkron veri çekme işlemlerinin nasıl yönetildiğine dair somut bir örnek.
import Alamofire
struct MovieService {
// 1. Fonksiyon Tanımı: 'completion' parametresi @escaping'dir.
// İnternet hızı belirsizdir; fonksiyon hemen biter ama veri sonra (asenkron) gelir.
func fetchMovies(completion: @escaping (Result<[String], AFError>) -> Void) {
let url = "[https://api.ornek.com/movies](https://api.ornek.com/movies)"
// 2. Alamofire İsteği: AF.request arka planda (background) çalışır.
AF.request(url).responseDecodable(of: [String].self) { response in
// 4. Burası bir closure'dır ve fonksiyon bittikten sonra çalışır (Escaping).
// Alamofire sonuçları otomatik olarak ANA THREAD (Main) üzerinden döner.
switch response.result {
case .success(let movies):
completion(.success(movies)) // Başarı durumu
case .failure(let error):
completion(.failure(error)) // Hata durumu
}
}
// 3. Fonksiyonun gövdesi burada biter ve Stack'ten silinir.
print("LOG: fetchMovies fonksiyonu return etti.")
}
}- Tetikleme:
fetchMoviesçağrıldığında, içine bir kod bloğu (closure) teslim edilir. - Request: Alamofire isteği başlatır. Bu sırada uygulama donmaz çünkü işlem arka planda yürütülür.
- Fonksiyonun Sonu: Fonksiyon son süslü parantezine ulaşır ve biter. Normalde her şeyin silinmesi gerekirken,
completionbloğu@escapingolduğu için bellekte (Heap) tutulmaya devam eder. - Yanıt: Sunucudan cevap geldiğinde Alamofire veriyi modeller.
- Çalıştırma: Alamofire, elinde tuttuğu o "paketlenmiş kod bloğunu" (
completion) bulur, verileri içine koyar ve tetikler.
Yeni nesil projelerde kodun okunabilirliğini artırmak için closure yerine bu yöntem tercih edilir:
// ❌ ESKİ: Completion Handler ile
func getMovies(completion: @escaping (Result<[String], AFError>) -> Void) { ... }
// ✅ YENİ: Async/Await ile
func getMovies() async throws -> [String] {
let task = AF.request("[https://api.com/movies](https://api.com/movies)").serializingDecodable([String].self)
// Burada cevap gelene kadar bekler ama main thread'i dondurmaz.
let movies = try await task.value
return movies
}Özet Not: Alamofire kullanırken de mantık aynıdır: "Sen git veriyi çek, gelince benim sana verdiğim bu closure paketini aç ve çalıştır." Alamofire, bu süreci otomatik thread yönetimi ile daha güvenli hale getirir.
Bir enum'ın her bir case'ine (durumuna), derleme zamanında (compile-time) önceden belirlenmiş, sabit bir değer atanmasıdır.
- Özellikleri: * Tüm case'ler aynı veri tipinde (
String,Int,Charactervb.) olmalıdır.- Kod çalışırken bu değerler değişemez, sabittir.
- Her bir ham değer (raw value) benzersiz olmalıdır.
enum HTTPError: Int {
case ok = 200
case unauthorized = 401
case notFound = 404
case internalServerError = 500
}
// Kullanımı:
let status = HTTPError.notFound
print(status.rawValue) // Çıktı: 404Yazılımda kodun içine rastgele serpiştirilmiş sayılara (404, 200 gibi) "Magic Numbers" denir ve bu kötü bir pratiktir.
- Kötü Senaryo: Kodun 10 farklı yerinde
if statusCode == 404yazarsan, bir gün bu kodu güncellemek işkenceye döner. - İyi Senaryo (Enum):
if error == .notFoundyazarsın. Kodun ne yaptığı bir bakışta anlaşılır. "404 neydi?" diye düşünmezsin.
Backend'den her zaman beklediğin sayılar gelmeyebilir. Enum kullanmak seni olmayan durumlara karşı korur.
Diyelim ki backend sana yanlışlıkla 999 diye bir kod gönderdi:
- Enum Olmazsa: Kodun 999 sayısını işlemeye çalışabilir ve beklenmedik hatalar verebilir.
- Enum Olursa:
HTTPError(rawValue: 999)dediğinde sonuçnildöner. Swift sana "Böyle bir durum tanımlı değil" der ve sen de bunuif letveyaguard letile güvenli bir şekilde yönetirsin.
❌ Enum KULLANMADAN (Zor ve Hataya Açık):
let incomingCode = 404
if incomingCode == 404 {
print("Sayfa bulunamadı")
} else if incomingCode == 500 {
print("Sunucu hatası")
}
// Sayıların ne anlama geldiğini ezbere bilmek zorundasın.✅ Enum KULLANARAK (Güvenli ve Okunaklı):
enum HTTPError: Int {
case notFound = 404
case serverError = 500
case unauthorized = 401
}
// Backend'den gelen veriyi tipe dönüştür
if let error = HTTPError(rawValue: 404) {
// Artık sayılarla değil, anlamlı isimlerle konuşuyoruz:
switch error {
case .notFound:
print("Sayfa bulunamadı")
case .serverError:
print("Sunucu hatası")
case .unauthorized:
print("Yetkisiz giriş")
}
}Özet: Raw Value (Ham Değer), dış dünyadaki "ilkel" veriyi (sayı, string), Swift dünyasındaki "anlamlı" etiketlere dönüştüren bir güvenlik köprüsüdür.