Tüm Yazılar
KategoriPerformance
Okuma Süresi
21 dk
Yayın Tarihi
...
Kelime Sayısı
2.138kelime

Kahveni hazırla - bu içerikli bir makale!

ARC derinlemesine, retain cycles, weak/unowned references, memory debugging ve Instruments ile profiling.

iOS Memory Management: ARC ve Memory Leaks

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

  1. ARC Nasıl Çalışır? Perde Arkası
  2. Strong, Weak ve Unowned: Karşılaştırma
  3. Closure Capture Lists
  4. Yaygın Memory Leak Kalıpları
  5. Autorelease Pool ve Bellek Yönetimi
  6. Value Types vs Reference Types
  7. Memory Debugging Araçları
  8. Instruments ile Profiling
  9. SwiftUI'da Memory Management
  10. 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: String
4 var apartment: Apartment?
5
6 init(name: String) {
7 self.name = name
8 print("\(name) initialized")
9 }
10
11 deinit {
12 print("\(name) deinitialized")
13 }
14}
15 
16class Apartment {
17 let unit: String
18 var tenant: Person? // Strong reference
19
20 init(unit: String) {
21 self.unit = unit
22 }
23
24 deinit {
25 print("Apartment \(unit) deinitialized")
26 }
27}
28 
29// ❌ RETAIN CYCLE
30var john: Person? = Person(name: "John")
31var unit4A: Apartment? = Apartment(unit: "4A")
32 
33john?.apartment = unit4A
34unit4A?.tenant = john
35 
36john = nil // Person not deallocated!
37unit4A = nil // Apartment not deallocated!
38// Memory leak!

Weak ve Unowned References

swift
1// WEAK - Nesne nil olabilir
2class Apartment {
3 let unit: String
4 weak var tenant: Person? // Weak reference
5
6 init(unit: String) {
7 self.unit = unit
8 }
9}
10 
11// UNOWNED - Nesne her zaman var olacak
12class Customer {
13 let name: String
14 var card: CreditCard?
15
16 init(name: String) {
17 self.name = name
18 }
19}
20 
21class CreditCard {
22 let number: String
23 unowned let customer: Customer // Unowned - customer her zaman var
24
25 init(number: String, customer: Customer) {
26 self.number = number
27 self.customer = customer
28 }
29}
30 
31// Kullanım
32let 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, unowned yerine weak kullan. unowned ile 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 CYCLE
15 networkManager.onComplete = {
16 self.data = ["item1", "item2"] // Strong capture
17 self.tableView.reloadData()
18 }
19
20 // ✅ WEAK CAPTURE
21 networkManager.onComplete = { [weak self] in
22 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] in
29 self.data = ["item1", "item2"]
30 self.tableView.reloadData()
31 }
32 }
33}
34 
35// Multiple captures
36someAsyncFunction { [weak self, weak delegate, networkManager] in
37 guard let self = self else { return }
38 // networkManager is captured strongly (value type)
39 // self and delegate are weak
40}

Common Memory Leak Patterns

swift
1// 1. Timer retain cycle
2class TimerViewController: UIViewController {
3 var timer: Timer?
4
5 override func viewDidLoad() {
6 super.viewDidLoad()
7
8 // ❌ RETAIN CYCLE
9 timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
10 self.updateUI()
11 }
12
13 // ✅ WEAK CAPTURE
14 timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
15 self?.updateUI()
16 }
17 }
18
19 override func viewWillDisappear(_ animated: Bool) {
20 super.viewWillDisappear(animated)
21 timer?.invalidate()
22 }
23}
24 
25// 2. NotificationCenter
26class 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: .main
36 ) { [weak self] notification in
37 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 pattern
49protocol 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 bellekte
2func 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 temizle
11func 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ır
18 }
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çınma
2struct UserProfile {
3 let name: String
4 let email: String
5 var settings: AppSettings // Nested struct, reference cycle imkansız
6}
7 
8// Class gerektiğinde - identity semantics veya inheritance için
9class UserSession {
10 let profile: UserProfile // Struct property - leak riski yok
11 weak var delegate: SessionDelegate? // Weak delegate
12 
13 init(profile: UserProfile) {
14 self.profile = profile
15 }
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 DEBUG
2func debugRetainCount(_ object: AnyObject) {
3 print("Retain count: \(CFGetRetainCount(object))")
4}
5 
6// Allocation tracking ile leak detection
7final 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] += 1
15 print("📈 \(type) allocated. Total: \(allocations[type]!)")
16 }
17 }
18 
19 func release(_ type: String) {
20 queue.sync {
21 allocations[type, default: 0] -= 1
22 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#endif

3. 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

  1. Product > Profile (⌘I) ile Instruments'ı aç
  2. Leaks template'ini seç
  3. Record butonuna bas ve uygulamayı kullan
  4. Kırmızı çubuklar leak'leri gösterir
  5. 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 riski
2class 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 parent
12 init(parent: ParentViewModel) {
13 self.parent = parent
14 }
15}
16 
17// ✅ Weak reference ile düzelt
18class ChildViewModel: ObservableObject {
19 weak var parent: ParentViewModel?
20 init(parent: ParentViewModel) {
21 self.parent = parent
22 }
23}
24 
25// ✅ iOS 17+ @Observable ile daha temiz
26@Observable
27class ParentModel {
28 var items: [String] = []
29 // @Observable struct reference cycle riski azaltır
30 // çünkü observation tracking otomatik yönetilir
31}

SwiftUI View'larda Dikkat Edilecekler

  1. onAppear/onDisappear ile subscription yönetimi
  2. task modifier ile otomatik cancellation
  3. StateObject sadece view'ın sahibi olduğu nesneler için
  4. EnvironmentObject ile gereksiz strong reference'lardan kaçın

Production Best Practices {#best-practices}

Memory Management Checklist

  1. Delegate'ler her zaman `weak` olsun
  2. Closure capture list'leri kullanın — [weak self] veya [unowned self]
  3. Timer'ları `invalidate()` edin ve deinit'te temizleyin
  4. NotificationCenter observer'larını kaldırın
  5. Struct tercih edin — retain cycle riski sıfır
  6. Instruments ile düzenli profiling yapın (her sprint sonunda)
  7. autoreleasepool kullanın büyük loop'larda
  8. Memory warning'lere yanıt verin (didReceiveMemoryWarning)
  9. Image cache'leri NSCache ile yönetin (otomatik eviction)
  10. CI/CD'ye memory test ekleyin

Memory Warning Handling

swift
1// UIKit
2override func didReceiveMemoryWarning() {
3 super.didReceiveMemoryWarning()
4 imageCache.removeAllObjects()
5 dataCache.removeAll()
6}
7 
8// SwiftUI + Combine
9NotificationCenter.default
10 .publisher(for: UIApplication.didReceiveMemoryWarningNotification)
11 .sink { [weak self] _ in
12 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.

Etiketler

#Memory#ARC#iOS#Performance#Debugging
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz