• 24.09.2026 09:08:30
  • Admin Admin

Bu SwiftUI eğitimi, kapanmayan Task, Combine aboneliği ve navigasyon referanslarının oluşturduğu bellek sızıntılarını Allocations, Leaks ve Memory Graph ile tekrar üretip ölçmeyi gösterir.

SwiftUI Eğitiminde Bellek Sızıntılarını Instruments ile Avlamak

SwiftUI eğitiminde sızıntıyı tekrar üretilebilir hale getirmek

Bir SwiftUI eğitimi içinde bellek sızıntısını yalnızca Instruments ekranında görülen mor bir iz olarak ele almak yetersizdir. Önce test edilebilir bir kullanıcı senaryosu kurun: profil ekranını açın, bir ağ isteğinin başlamasını bekleyin, geri dönün ve bunu 20 kez tekrarlayın. Xcode Instruments içindeki Allocations aracında her geri dönüşten sonra Generation işaretleyin. Son generation sonrasında ProfileViewModel nesne sayısı 0 yerine 20 ise, sorun geçici bellek basıncı değil, canlı nesne grafiğinde tutulan bir referanstır. Leaks aracı yalnızca erişilemeyen döngüleri yakalayabildiği için, erişilebilir ama artık gereksiz view model nesnelerini bulmakta Allocations ve Debug Memory Graph birlikte kullanılmalıdır.

iOS eğitimi materyallerinde sık görülen Combine hatası, view modelin abonelik setini tutması ve abonelik closure'unun da view modeli güçlü tutmasıdır. Aşağıdaki ilk sürümde sink closure'u self'i güçlü yakalar; AnyCancellable self üzerinde tutulduğu için döngü oluşur. İkinci sürümde [weak self] kullanılmalı ve ekran kapanırken aboneliğin gerçekten iptal edilmesi gereken iş kuralları ayrıca modellenmelidir.

@MainActor
final class ProfileViewModel: ObservableObject {
    @Published private(set) var profile: Profile?
    private var cancellables = Set<AnyCancellable>()

    init(service: ProfileService) {
        // Hatali: self -> cancellables -> closure -> self
        // service.profilePublisher
        //     .sink { profile in self.profile = profile }
        //     .store(in: &cancellables)

        service.profilePublisher
            .sink { [weak self] profile in
                self?.profile = profile
            }
            .store(in: &cancellables)
    }

    deinit {
        assertionFailure("ProfileViewModel serbest birakildi")
    }
}

Buradaki edge case, deinit mesajının tek başına kanıt sayılmasıdır. SwiftUI, görünüm değerlerini sık üretir; önemli olan View struct'ının değil, @StateObject ile sahip olunan referans tipinin yaşam döngüsüdür. Memory Graph Debugger'da yaşayan ProfileViewModel'i seçip Incoming References görünümünde AnyCancellable, closure veya navigation coordinator zincirini inceleyin. Bir swift kursu laboratuvarında bu grafiğin ekran görüntüsünü almak yerine, 20 döngü sonunda instance count'unun 0 olmasını kabul kriteri yapın.

iOS MVVM mimarisi ve Swift Concurrency'de Task sahipliği

ios mvvm mimarisi kullanırken Task referansını view model üzerinde saklamak, Task closure'unun view modeli hangi noktada güçlü yakaladığı bilinmeden yapılırsa uzun ömürlü bir retain zinciri kurabilir. [weak self] ile başlayan bir closure bile await öncesinde guard let self yaparsa, self ağ isteği bitene kadar güçlü kalır. İptale tepki vermeyen bir istemci veya hiç dönmeyen AsyncSequence bu süreyi ekrandan ayrıldıktan sonra da uzatır. Bu nedenle Task'a girdi olarak gereken api ve id değerlerini önce kopyalayın, self'e yalnızca sonucu UI durumuna yazarken kısa süreli erişin.

@MainActor
final class ProfileViewModel: ObservableObject {
    @Published private(set) var profile: Profile?
    private let api: ProfileAPI
    private var refreshTask: Task<Void, Never>?

    init(api: ProfileAPI) { self.api = api }

    func refresh(userID: String) {
        refreshTask?.cancel()
        let api = api

        refreshTask = Task { [weak self, api, userID] in
            do {
                let value = try await api.fetchProfile(id: userID)
                guard !Task.isCancelled else { return }
                self?.profile = value
            } catch is CancellationError {
                // Ekran kapanirken beklenen yol.
            } catch {
                guard !Task.isCancelled else { return }
                self?.profile = nil
            }
        }
    }

    deinit { refreshTask?.cancel() }
}

View tarafında ekran yaşam döngüsüne ait yüklemeler için .task(id:) tercih edin; SwiftUI görünüm hiyerarşisinden çıkınca bu structured task'i iptal eder. Ancak iptal sihirli bir kaynak temizliği değildir: API katmanındaki URLSession çağrısı CancellationError üretmeli, AsyncStream ise onTermination içinde producer kaynaklarını kapatmalıdır. swift eğitimi kapsamında aşağıdaki kullanımda userID değiştiğinde eski görev de iptal edilir; bu, elle Task sözlüğü tutmaktan daha az sahiplik yüzeyi yaratır.

struct ProfileScreen: View {
    let userID: String
    @StateObject private var model: ProfileViewModel

    var body: some View {
        ProfileContent(profile: model.profile)
            .task(id: userID) {
                model.refresh(userID: userID)
            }
    }
}

Xcode eğitimi için Allocations, Leaks ve Memory Graph ölçüm akışı

Uygulanabilir bir xcode eğitimi akışı, Debug çalıştırmasındaki bir gözleme değil, aynı senaryonun önce-sonra ölçümüne dayanmalıdır. Önce sorunlu dalda cihazı USB ile bağlayın, Product > Profile üzerinden Allocations şablonunu açın ve Record Reference Counts seçeneğini etkinleştirin. Profil ekranını 20 kez push-pop yaptıktan sonra Mark Generation kullanın. Düzeltmeden önce generation sonrası örneğin 20 ProfileViewModel, 20 AnyCancellable ve artan canlı byte değeri kaydedin. [weak self], cancel veya .task(id:) değişikliğinden sonra aynı cihazda aynı 20 döngüyü çalıştırın; hedef, generation sonrasında bu sınıfların sayısının başlangıç düzeyine dönmesidir.

Leaks sonucu boş olsa bile Allocations içindeki Persistent filtresini açın ve sınıf adına göre filtreleyin. Leaks, erişilemeyen bellek bloklarını tarar; navigation path, singleton cache veya bekleyen Task tarafından erişilebilir kalan nesneler leak olarak raporlanmayabilir. Debug Memory Graph'ta bir ProfileViewModel seçip zinciri tersine izlemek daha doğru mekanizmayı gösterir: NavigationPath > closure > coordinator gibi bir zincir varsa sorun Combine değil, rota state'inde taşınan referans tipidir.

CI dışında tekrarlanabilir kayıt almak için Instruments izini komutla da başlatabilirsiniz. Uygulama çalıştırılabilir dosyasının yolunu Archive çıktısından veya DerivedData'dan verin; oluşan .trace dosyasını ekip içi karşılaştırmada saklayın. Bu komut, simulator ile cihazın allocator davranışını eşitlemez; ölçümleri aynı hedef cihaz ve aynı build ayarlarıyla karşılaştırmak gerekir.

xcrun xctrace record   --template 'Allocations'   --output /tmp/profile-memory.trace   --time-limit 60s   --launch -- /path/to/MyApp.app/MyApp

iPhone uygulama geliştirme sürecinde yayın öncesi bellek kapısı

iPhone uygulama geliştirme ekiplerinde bellek kontrolünü manuel bir son dakika aktivitesi olmaktan çıkarmak için UI testine tekrar senaryosu ekleyin. XCTest, Instruments kadar ayrıntılı allocation stack vermez; fakat ekran kapandıktan sonra zayıf referansın nil olmasını doğrulayarak retain regresyonunu pull request aşamasında yakalar. Test edilen coordinator veya view model, gerçek ağ yerine iptal edilebilir bir stub kullanmalıdır; aksi halde yavaş ağ testi rastgele başarısız yapar.

func testProfileModelIsReleasedAfterCancellation() async {
    weak var weakModel: ProfileViewModel?

    autoreleasepool {
        let api = DelayedProfileAPI(delayNanoseconds: 5_000_000_000)
        let model = ProfileViewModel(api: api)
        weakModel = model
        model.refresh(userID: "42")
    }

    try? await Task.sleep(nanoseconds: 50_000_000)
    XCTAssertNil(weakModel)
}

Bu testin önemli inceliği şudur: await eden Task view modeli güçlü yakalıyorsa autoreleasepool bitse bile XCTAssertNil başarısız olur. Test başarısızlığını yalnızca deinit'e print koyarak gizlemeyin; Memory Graph ile Task closure'unun capture listesini doğrulayın. Ayrıca testteki DelayedProfileAPI iptali göz ardı ediyorsa, üretimdeki davranışı değil test double hatasını ölçmüş olursunuz.

app store yayınlama öncesinde Archive ile oluşan Release derlemesinde aynı 20 push-pop senaryosunu fiziksel cihazda yeniden çalıştırın. Debug ve Release optimizasyon farkları, özellikle closure inlining ve zamanlamayı değiştirebilir; hedefiniz mutlak byte sayısını iki build arasında eşitlemek değil, her build içinde kapanan ekranın instance sayısının baseline'a dönmesidir. Bu kontrol, ios kursu veya swiftui eğitimi projelerinde test planına 'Profile ekranı, 20 aç-kapa, persistent instance = 0' biçiminde ölçülebilir bir madde olarak eklenebilir.

Sık Sorulan Sorular

SwiftUI eğitiminde Instruments Leaks temizken neden ProfileViewModel yaşıyor?

Leaks yalnızca erişilemeyen bellek döngülerini saptar. ProfileViewModel bir NavigationPath, AnyCancellable, singleton cache veya çalışan Task tarafından erişilebilir kalıyorsa leak raporu boş olabilir. Allocations'ta 20 aç-kapa sonrası Persistent instance sayısını filtreleyin, sonra Xcode Memory Graph'ta Incoming References zincirini inceleyin.

iOS MVVM mimarisi içinde Task ile view model retain cycle nasıl önlenir?

View modelin tuttuğu Task içinde await öncesinde guard let self kullanmayın. API ve kimlik gibi değerleri closure'a değer olarak taşıyın, sonucu yazarken self?.property kullanın. Ekrana bağlı işler için .task(id:) kullanın; elle saklanan Task'lar için yeni istekten önce cancel ve deinit içinde cancel çağırın.

Xcode eğitimi sırasında Allocations ile bellek düzeltmesini nasıl doğrularım?

Düzeltmeden önce ve sonra aynı fiziksel cihazda aynı build ayarıyla 20 push-pop çalıştırın. Her çalışmada son geri dönüşten sonra Mark Generation ekleyin, Persistent filtresinde ProfileViewModel ve AnyCancellable sayılarını kaydedin. Başarılı sonuç, ikinci ölçümde bu sayıların başlangıç seviyesine dönmesidir; yalnızca toplam Live Bytes grafiğine bakmak yeterli değildir.

Swift kursu projesinde deinit assertion kullanmak güvenilir mi?

deinit assertion bir alarmdır, tek başına ölçüm değildir. SwiftUI View struct'larının deinit'i yoktur ve framework geçici nesneleri gecikmeli bırakabilir. Assertion'ı, weak referanslı XCTest ve Allocations generation karşılaştırmasıyla birlikte kullanın; assertion üretim build'inde fatal davranış yaratmamalıdır.

AI / LLM Discovery

Bu makale Opendart Akademi iOS / Swift eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt