Uygulamanı her şey yolundayken App Store'a gönderdin. İncelemeler harika, kullanıcılar mutlu... Ta ki bir hafta sonra crash raporları yağmaya başlayana kadar. Sebep? Memory leak. Kullanıcılar uygulamayı uzun süre açık bıraktığında, bellek tükeniyor ve iOS uygulamayı öldürüyor. Bu rehberde, iOS memory management'ın derinliklerine dalacağız ve bir daha asla bu kabusu yaşamayacaksın.
💡 Hızlı Not: Bu rehber Apple'ın Memory Management Programming Guide ve WWDC session'larından derlendi. Swift 5.9+ ve iOS 17 dahil güncel bilgiler içerir.
İçindekiler
- ARC Nasıl Çalışır? Perde Arkası
- Strong, Weak ve Unowned: Karşılaştırma
- Closure Capture Lists
- Yaygın Memory Leak Kalıpları
- Autorelease Pool ve Bellek Yönetimi
- Value Types vs Reference Types
- Memory Debugging Araçları
- Instruments ile Profiling
- SwiftUI'da Memory Management
- Production Best Practices
ARC Nasıl Çalışır? Perde Arkası {#arc-nasil-calisir}
ARC (Automatic Reference Counting), Swift'in bellek yönetim mekanizmasıdır. Garbage collection'dan farklı olarak, compile-time'da retain/release çağrıları ekler. Bu, runtime overhead olmaması anlamına gelir — performans açısından büyük avantaj.
ARC'ın temel kuralı basit: Bir nesneye en az bir strong reference varsa, nesne bellekte kalır. Tüm strong reference'lar sıfıra düştüğünde, nesne deallocate edilir.
Reference Counting Mekanizması
Her Swift class instance'ının bir reference count değeri vardır. Bu değer şu durumlarda değişir:
İşlem | Reference Count | Sonuç |
|---|---|---|
Yeni strong reference atama | +1 (retain) | Nesne bellekte kalır |
Strong reference nil yapma | -1 (release) | Count 0 ise deallocate |
Weak reference atama | Değişmez | Nesne etkilenmez |
Unowned reference atama | Değişmez | Nesne etkilenmez |
Scope'dan çıkma | -1 (release) | Otomatik release |
Strong, Weak ve Unowned: Karşılaştırma {#reference-types}
swift
1// Strong reference (varsayılan)2class Person {3 let name: String4 var apartment: Apartment?5 6 init(name: String) {7 self.name = name8 print("\(name) initialized")9 }10 11 deinit {12 print("\(name) deinitialized")13 }14}15 16class Apartment {17 let unit: String18 var tenant: Person? // Strong reference19 20 init(unit: String) {21 self.unit = unit22 }23 24 deinit {25 print("Apartment \(unit) deinitialized")26 }27}28 29// ❌ RETAIN CYCLE30var john: Person? = Person(name: "John")31var unit4A: Apartment? = Apartment(unit: "4A")32 33john?.apartment = unit4A34unit4A?.tenant = john35 36john = nil // Person not deallocated!37unit4A = nil // Apartment not deallocated!38// Memory leak!Weak ve Unowned References
swift
1// WEAK - Nesne nil olabilir2class Apartment {3 let unit: String4 weak var tenant: Person? // Weak reference5 6 init(unit: String) {7 self.unit = unit8 }9}10 11// UNOWNED - Nesne her zaman var olacak12class Customer {13 let name: String14 var card: CreditCard?15 16 init(name: String) {17 self.name = name18 }19}20 21class CreditCard {22 let number: String23 unowned let customer: Customer // Unowned - customer her zaman var24 25 init(number: String, customer: Customer) {26 self.number = number27 self.customer = customer28 }29}30 31// Kullanım32let customer = Customer(name: "John")33customer.card = CreditCard(number: "1234", customer: customer)34// Retain cycle yok!Weak vs Unowned Karşılaştırma Tablosu
Özellik | strong | weak | unowned |
|---|---|---|---|
Reference count | Artırır | Artırmaz | Artırmaz |
Optional mı? | Hayır | Evet (her zaman Optional) | Hayır |
nil olabilir mi? | Hayır | Evet (otomatik nil olur) | Hayır (crash riski!) |
Ne zaman kullanılır? | Varsayılan | Diğer nesne daha kısa ömürlüyse | Aynı veya daha uzun ömürlüyse |
Parent-child | Parent → Child | Child → Parent | Child → Parent (emin ol!) |
Performans | Normal | Küçük overhead (zeroing) | Daha hızlı (zeroing yok) |
Crash riski | Yok | Yok | Var (dangling pointer) |
⚠️ Kritik Kural: Eğer reference'ın nil olma ihtimali %1 bile varsa,unownedyerineweakkullan.unownedile deallocate edilmiş nesneye erişim = instant crash. Production'da bunu istemezsin.
Closure Capture Lists {#closure-captures}
swift
1class NetworkManager {2 var onComplete: (() -> Void)?3 4 func fetchData() {5 // API call...6 }7}8 9class ViewController: UIViewController {10 let networkManager = NetworkManager()11 var data: [String] = []12 13 func loadData() {14 // ❌ RETAIN CYCLE15 networkManager.onComplete = {16 self.data = ["item1", "item2"] // Strong capture17 self.tableView.reloadData()18 }19 20 // ✅ WEAK CAPTURE21 networkManager.onComplete = { [weak self] in22 guard let self = self else { return }23 self.data = ["item1", "item2"]24 self.tableView.reloadData()25 }26 27 // ✅ UNOWNED CAPTURE (ViewController her zaman var olacaksa)28 networkManager.onComplete = { [unowned self] in29 self.data = ["item1", "item2"]30 self.tableView.reloadData()31 }32 }33}34 35// Multiple captures36someAsyncFunction { [weak self, weak delegate, networkManager] in37 guard let self = self else { return }38 // networkManager is captured strongly (value type)39 // self and delegate are weak40}Common Memory Leak Patterns
swift
1// 1. Timer retain cycle2class TimerViewController: UIViewController {3 var timer: Timer?4 5 override func viewDidLoad() {6 super.viewDidLoad()7 8 // ❌ RETAIN CYCLE9 timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in10 self.updateUI()11 }12 13 // ✅ WEAK CAPTURE14 timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in15 self?.updateUI()16 }17 }18 19 override func viewWillDisappear(_ animated: Bool) {20 super.viewWillDisappear(animated)21 timer?.invalidate()22 }23}24 25// 2. NotificationCenter26class NotificationViewController: UIViewController {27 var observer: NSObjectProtocol?28 29 override func viewDidLoad() {30 super.viewDidLoad()31 32 observer = NotificationCenter.default.addObserver(33 forName: .someNotification,34 object: nil,35 queue: .main36 ) { [weak self] notification in37 self?.handleNotification(notification)38 }39 }40 41 deinit {42 if let observer = observer {43 NotificationCenter.default.removeObserver(observer)44 }45 }46}47 48// 3. Delegate pattern49protocol DataManagerDelegate: AnyObject {50 func dataDidUpdate(_ data: [String])51}52 53class DataManager {54 weak var delegate: DataManagerDelegate? // Always weak!55}Autorelease Pool ve Bellek Yönetimi {#autorelease-pool}
Autorelease pool, büyük miktarda geçici nesne oluşturduğunda bellek spike'larını önlemek için kullanılır. Swift'te autoreleasepool bloğu ile tanımlanır:
swift
1// ❌ Bellek spike - tüm image'lar loop bitene kadar bellekte2func processImages(paths: [String]) {3 for path in paths {4 let image = UIImage(contentsOfFile: path)5 let processed = applyFilter(to: image)6 saveToGallery(processed)7 }8}9 10// ✅ Autorelease pool ile her iterasyonda temizle11func processImages(paths: [String]) {12 for path in paths {13 autoreleasepool {14 let image = UIImage(contentsOfFile: path)15 let processed = applyFilter(to: image)16 saveToGallery(processed)17 } // Her iterasyonda geçici nesneler serbest bırakılır18 }19}💡 Ne zaman kullanmalı? Loop içinde çok sayıda NSObject alt sınıfı (UIImage, NSData, NSString) oluşturuyorsan, autoreleasepool bellek tüketimini dramatik şekilde azaltır.Value Types vs Reference Types {#value-vs-reference}
Swift'te struct ve enum value type, class ise reference type'dır. Bu ayrım memory management açısından kritiktir:
Özellik | Value Type (struct) | Reference Type (class) |
|---|---|---|
Bellek | Stack (genellikle) | Heap |
Kopyalama | Derin kopya (CoW) | Reference kopyalanır |
ARC | Gerekli değil | Gerekli |
Thread safety | Doğal olarak güvenli | Senkronizasyon gerekli |
Memory leak riski | Yok | Var (retain cycles) |
swift
1// Struct kullanarak retain cycle'dan tamamen kaçınma2struct UserProfile {3 let name: String4 let email: String5 var settings: AppSettings // Nested struct, reference cycle imkansız6}7 8// Class gerektiğinde - identity semantics veya inheritance için9class UserSession {10 let profile: UserProfile // Struct property - leak riski yok11 weak var delegate: SessionDelegate? // Weak delegate12 13 init(profile: UserProfile) {14 self.profile = profile15 }16}Kural: Mümkün olduğunca struct kullan. Class sadece identity semantics, inheritance veya Objective-C interop gerektiğinde tercih et.
Memory Debugging Araçları {#memory-debugging}
1. Xcode Memory Graph Debugger
Xcode'da debug sırasında Debug Navigator > Memory Graph butonuna tıkla. Bu araç:
- Tüm canlı nesneleri gösterir
- Retain cycle'ları mor uyarı ile işaretler
- Nesne grafiğini görsel olarak çizer
- "Show only leaked blocks" filtresiyle direkt leak'lere odaklan
2. Debug Retain Count
swift
1#if DEBUG2func debugRetainCount(_ object: AnyObject) {3 print("Retain count: \(CFGetRetainCount(object))")4}5 6// Allocation tracking ile leak detection7final class AllocationTracker {8 static let shared = AllocationTracker()9 private var allocations: [String: Int] = [:]10 private let queue = DispatchQueue(label: "allocation.tracker")11 12 func track(_ type: String) {13 queue.sync {14 allocations[type, default: 0] += 115 print("📈 \(type) allocated. Total: \(allocations[type]!)")16 }17 }18 19 func release(_ type: String) {20 queue.sync {21 allocations[type, default: 0] -= 122 print("📉 \(type) released. Total: \(allocations[type]!)")23 }24 }25 26 func reportLeaks() {27 queue.sync {28 let leaks = allocations.filter { $0.value > 0 }29 if leaks.isEmpty {30 print("✅ No memory leaks detected!")31 } else {32 print("⚠️ Potential leaks:")33 leaks.forEach { print(" - \($0.key): \($0.value) instances") }34 }35 }36 }37}38#endif3. MallocStackLogging
Environment variable olarak MallocStackLogging=1 ekleyerek her allocation'ın stack trace'ini kaydedebilirsin. Xcode'da Scheme > Run > Diagnostics > Malloc Stack Logging seçeneğini aktifleştir.
Instruments ile Profiling {#instruments-profiling}
Instruments, iOS'ta memory profiling için en güçlü araç. Doğru kullanmayı öğrenmek saatlerce debug süresini dakikalara indirir.
Leaks Instrument
- Product > Profile (⌘I) ile Instruments'ı aç
- Leaks template'ini seç
- Record butonuna bas ve uygulamayı kullan
- Kırmızı çubuklar leak'leri gösterir
- Leak'e tıklayıp Cycles & Roots sekmesinde retain cycle'ı gör
Allocations Instrument
- All Heap Allocations: Tüm heap nesnelerini listeler
- Statistics: Hangi class'tan kaç instance var gösterir
- Mark Generation: Belirli bir noktadan itibaren oluşan nesneleri filtrele (navigation leak tespiti için mükemmel)
VM Tracker
- Dirty Memory: Uygulamanın aktif kullandığı bellek
- Clean Memory: Tekrar yüklenebilir bellek (framework'ler, mapped files)
- iOS, bellek baskısında önce clean memory'i serbest bırakır
📊 Hedef Metrikler: Dirty memory < 100MB (iPhone), footprint < 200MB. Bu limitleri aşarsan iOS uygulamayı jetsam ile öldürebilir.SwiftUI'da Memory Management {#swiftui-memory}
SwiftUI, declarative yapısı sayesinde birçok memory sorununu otomatik çözer, ama dikkat etmen gereken noktalar var:
swift
1// ❌ ObservableObject ile retain cycle riski2class ParentViewModel: ObservableObject {3 @Published var child: ChildViewModel?4 5 init() {6 child = ChildViewModel(parent: self) // Retain cycle!7 }8}9 10class ChildViewModel: ObservableObject {11 let parent: ParentViewModel // Strong reference to parent12 init(parent: ParentViewModel) {13 self.parent = parent14 }15}16 17// ✅ Weak reference ile düzelt18class ChildViewModel: ObservableObject {19 weak var parent: ParentViewModel?20 init(parent: ParentViewModel) {21 self.parent = parent22 }23}24 25// ✅ iOS 17+ @Observable ile daha temiz26@Observable27class ParentModel {28 var items: [String] = []29 // @Observable struct reference cycle riski azaltır30 // çünkü observation tracking otomatik yönetilir31}SwiftUI View'larda Dikkat Edilecekler
- onAppear/onDisappear ile subscription yönetimi
- task modifier ile otomatik cancellation
- StateObject sadece view'ın sahibi olduğu nesneler için
- EnvironmentObject ile gereksiz strong reference'lardan kaçın
Production Best Practices {#best-practices}
Memory Management Checklist
- ✅ Delegate'ler her zaman `weak` olsun
- ✅ Closure capture list'leri kullanın —
[weak self]veya[unowned self] - ✅ Timer'ları `invalidate()` edin ve deinit'te temizleyin
- ✅ NotificationCenter observer'larını kaldırın
- ✅ Struct tercih edin — retain cycle riski sıfır
- ✅ Instruments ile düzenli profiling yapın (her sprint sonunda)
- ✅ autoreleasepool kullanın büyük loop'larda
- ✅ Memory warning'lere yanıt verin (
didReceiveMemoryWarning) - ✅ Image cache'leri
NSCacheile yönetin (otomatik eviction) - ✅ CI/CD'ye memory test ekleyin
Memory Warning Handling
swift
1// UIKit2override func didReceiveMemoryWarning() {3 super.didReceiveMemoryWarning()4 imageCache.removeAllObjects()5 dataCache.removeAll()6}7 8// SwiftUI + Combine9NotificationCenter.default10 .publisher(for: UIApplication.didReceiveMemoryWarningNotification)11 .sink { [weak self] _ in12 self?.clearCaches()13 }14 .store(in: &cancellables)Sonuç
Memory management, iOS uygulamalarının stabilitesi ve kullanıcı deneyimi için kritik bir beceridir. ARC'ın nasıl çalıştığını anlamak, retain cycle'ları tespit edip önlemek ve Instruments ile düzenli profiling yapmak — bu üç beceri seni "junior memory leak fixer"dan "senior memory architect"e taşır. Struct'ları tercih et, weak/unowned'ı doğru kullan, closure capture list'lerini unutma.
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var:
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.

