Suspended — co to jest, zamrożenie aplikacji w tle iOS

Autor: IT Sectr Opublikowano: 2026-03-03 Czas czytania: 11 min

Suspended — stan zawieszenia cyklu życia aplikacji iOS, w którym jest ona zamrożona w pamięci, ale nie wykonuje kodu. Pokazujemy, jak działa Suspended, jakie ryzyko niesie zamrożenie aplikacji w tle, jak iOS zarządza wyładowywaniem aplikacji w stanie Suspended i jak zaimplementować state restoration dla bezszwowego przywracania po powrocie z Suspended.

Najważniejsze

  • Suspended — aplikacja zamrożona w pamięci, kod nie jest wykonywany, UI-stack zachowany
  • iOS Suspended — unikalny stan, którego nie ma w Androidzie; proces istnieje, ale nie jest aktywny
  • Wyładowanie — przy braku pamięci aplikacje w stanie Suspended są usuwane jako pierwsze, dane w pamięci są tracone
  • State Restoration — mechanizm iOS do zapisywania i przywracania UI-stacka po wyładowaniu
  • didEnterBackground — ostatnia metoda, która jest gwarantowanie wywoływana przed Suspended

Suspended — co to za stan

Suspended — stan cyklu życia aplikacji iOS, w którym znajduje się ona w pamięci operacyjnej urządzenia, ale nie wykonuje żadnego kodu. To końcowy stan przed całkowitym zakończeniem: aplikacja przechodzi w Suspended z Background po zakończeniu wszystkich zadań w tle lub po upływie limitu czasu. W Suspended aplikacja jest całkowicie zamrożona — wszystkie wątki są wstrzymane, timery nie działają, aktywność sieciowa nie występuje.

Suspended — unikalna cecha iOS, niewystępująca w standardowym cyklu życia Androida. Powodem jest różna architektura zarządzania procesami. iOS zachowuje obraz aplikacji w pamięci (analog hibernacji na desktopie), aby po powrocie użytkownika natychmiast przywrócić interfejs bez zimnego startu. Android nie ma Suspended — proces albo istnieje i może wykonywać kod (Background), albo jest zakończony (Not Running), choć Android może wstrzymać wykonanie wątków przez LMK.

Dla użytkownika Suspended wygląda jak natychmiastowe przywrócenie: przełącza się między aplikacjami przez App Switcher, a każda aplikacja otwiera się w tym samym miejscu, w którym ją zostawił. To stwarza złudzenie, że wszystkie aplikacje działają jednocześnie. W rzeczywistości większość z nich jest zamrożona w Suspended. Gorący start z Suspended jest znacznie szybszy niż zimny start z Not Running, ponieważ kod jest już załadowany do pamięci.

Jak system zarządza Suspended

iOS śledzi stan wszystkich aplikacji i podejmuje decyzję o wyładowaniu aplikacji w stanie Suspended na podstawie dostępnej pamięci. Gdy brakuje pamięci, system zaczyna wyładowywać aplikacje w stanie Suspended, zaczynając od tych, które najdłużej znajdują się w tym stanie. Jeśli pamięci wciąż brakuje, system przenosi aplikacje z Background i Inactive do Suspended, a następnie je wyładowuje. Ten proces jest całkowicie przezroczysty dla użytkownika — widzi on jedynie ikonę aplikacji w App Switcher, która po kliknięciu uruchamia zimny start.

CechaSuspended (iOS)Background (iOS)Background (Android)
Kod jest wykonywanyNieTak (ograniczenie)Tak (ograniczenie)
W pamięciTakTakTak
Zużycie CPU0%NiskieNiskie
Gorący startTak — natychmiastowe przywrócenieTak — przez InactiveNie — proces mógł zostać zabity
Limit czasuNie — może być w pamięci godzinami~30 sekund (po beginBackgroundTask)Zależy od wersji API
Wyładowanie przez systemPrzy braku pamięciPrzy krytycznym braku pamięciLMK (Low Memory Killer)
Powrót do działaniaZ App Switcher — natychmiastZ App Switcher — przez InactiveZimny start
State RestorationZalecanyNie wymaganySavedStateHandle

Suspended w iOS: mechanizm zamrożenia

W iOS Suspended jest osiągany automatycznie po zakończeniu wszystkich zadań w tle. System wywołuje applicationDidEnterBackground, daje czas na wykonanie beginBackgroundTask (około 30 sekund), po czym przymusowo wstrzymuje wszystkie wątki i przenosi aplikację w stan Suspended. Obiekty w pamięci są zachowane, ale żaden kod nie jest wykonywany — aplikacja jest zamrożona w bieżącym stanie.

Krytycznie ważny moment: applicationDidEnterBackground — ostatnia metoda, która jest gwarantowanie wywoływana przed Suspended. Po tym aplikacja nie otrzymuje żadnych powiadomień o wyładowaniu z pamięci. Jeśli użytkownik lub system zabija aplikację znajdującą się w Suspended, nie jest wywoływane ani applicationWillTerminate, ani ponownie applicationDidEnterBackground. Dlatego całe zapisywanie danych musi odbywać się w applicationDidEnterBackground, a nie w applicationWillTerminate.

swift
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    // Ostatnie gwarantowane wywołanie przed Suspended
    func applicationDidEnterBackground(_ application: UIApplication) {
        // Zapisujemy wszystko, co ma przetrwać wyładowanie z pamięci
        savePersistentState()
        saveNavigationStack()

        // Prosimy o dodatkowy czas, jeśli potrzebny
        let task = application.beginBackgroundTask {
            application.endBackgroundTask(task)
        }
    }

    // Powrót z Suspended — gorący start
    func applicationWillEnterForeground(_ application: UIApplication) {
        // Aplikacja była w Suspended, wracamy do pracy
        print("Powrót z Suspended lub Background")
    }

    // Całkowite przywrócenie po wyładowaniu z pamięci
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // Jeśli to zimny start po wyładowaniu z Suspended —
        // przywracamy state restoration
        return true
    }

    private func savePersistentState() {
        UserDefaults.standard.set(Date(), forKey: "lastActiveDate")
    }

    private func saveNavigationStack() {
        guard let rootVC = window?.rootViewController else { return }
        // Zapisujemy bieżący stos nawigacji
        if let navController = rootVC as? UINavigationController {
            let vcClasses = navController.viewControllers.map { type(of: $0) }
            UserDefaults.standard.set(vcClasses.map { NSStringFromClass($0) }, forKey: "navStack")
        }
    }
}

Kod pokazuje krytycznie ważne obsługę Suspended na iOS. applicationDidEnterBackground — ostatnie gwarantowane wywołanie. Wszystkie zapisy danych powinny odbywać się tutaj: stan użytkownika, stos nawigacji, szkice, timery. applicationWillEnterForeground jest wywoływane przy powrocie z Suspended lub Background. didFinishLaunchingWithOptions — tylko przy zimnym starcie, gdy aplikacja została wyładowana z pamięci po Suspended.

Suspended w Androidzie — czy istnieje odpowiednik

W Androidzie nie ma bezpośredniego odpowiednika iOS Suspended. Android nie zamraża aplikacji w pamięci z zachowaniem kontekstu wykonania. Zamiast tego Android albo utrzymuje proces w tle (Background), albo go kończy (Not Running). Jednak na Android 11+ (API 30) pojawił się mechanizm App Freezer, który wstrzymuje wykonywanie procesów w tle za pomocą sygnału SIGSTOP. Jest to funkcjonalny odpowiednik Suspended, ale z istotnymi różnicami.

App Freezer — część systemu zarządzania pamięcią Androida. Gdy aplikacja długo znajduje się w tle i nie ma aktywnych powiadomień, system wysyła jej SIGSTOP, wstrzymując wszystkie wątki. Po przywróceniu aplikacji na pierwszy plan wysyłany jest SIGCONT i wykonywanie jest wznawiane. Kluczowa różnica w stosunku do iOS: App Freezer nie gwarantuje zachowania stanu — dane w pamięci mogą zostać utracone, jeśli proces zostanie zabity podczas zamrożenia.

Na Androidzie zaleca się używanie SavedStateHandle w ViewModel do automatycznego zapisywania stanu przy każdym zakończeniu procesu. SavedStateHandle zapisuje dane w Bundle przez onSaveInstanceState, który przetrwa zarówno App Freezer, jak i Process Death. W przeciwieństwie do iOS, gdzie wyładowanie z Suspended jest wyjątkową sytuacją, na Androidzie Process Death to normalne zachowanie, którego należy zawsze oczekiwać.

kotlin
// SavedStateHandle — ratunek przed Process Death na Androidzie
class CheckoutViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // Stan, który przetrwa proces nawet po App Freezer
    var currentStep: MutableLiveData<Int> =
        savedStateHandle.getLiveData("checkout_step", 1)

    var cartItems: MutableLiveData<List<CartItem>> =
        savedStateHandle.getLiveData("cart_items", emptyList())

    fun proceedToNextStep() {
        currentStep.value = (currentStep.value ?: 0) + 1
    }

    fun addToCart(item: CartItem) {
        val updatedList = (cartItems.value ?: emptyList()) + item
        cartItems.value = updatedList
        savedStateHandle["cart_items"] = updatedList
    }
}

// Zapis w onStop na wypadek App Freezer
class MainActivity : AppCompatActivity() {

    override fun onStop() {
        super.onStop()
        // Zapisujemy dane, które mają przetrwać zamrożenie
        saveDraftData()
        // Zwalniamy zasoby, które nie są potrzebne w stanie zamrożonym
        releaseHeavyResources()
        // Ostrzegamy, że aplikacja zostanie zamrożona
        // (Logowanie do debugowania)
        Log.d("Lifecycle", "Activity zatrzymana — możliwe App Freeze")
    }
}

Kod pokazuje podejście do obsługi odpowiednika Suspended na Androidzie. SavedStateHandle w ViewModel automatycznie zapisuje i przywraca dane przy Process Death. onStop — ostatnie gwarantowane zdarzenie przed możliwym App Freezer lub zakończeniem procesu. Stan formularza zamówienia, lista produktów w koszyku — wszystkie te dane przetrwają zamrożenie dzięki SavedStateHandle. Dla ciężkich zasobów (bitmapy, kursory bazy danych) onStop jest miejscem do zwalniania pamięci.

State Restoration: przywracanie po Suspended

State Restoration — wbudowany mechanizm iOS do zapisywania i przywracania stanu UI po wyładowaniu aplikacji z pamięci. Jeśli aplikacja była w Suspended i system ją wyładował, przy następnym zimnym starcie state restoration przywraca stos nawigacji, pozycję scrolla, stan formularzy i inne elementy UI. Użytkownik wraca do tego samego ekranu, na którym się zatrzymał.

State Restoration działa poprzez protokoły UIViewControllerRestoration i UIStateRestoring. Deweloper przypisuje restorationIdentifier każdemu ViewController i View, które chce przywrócić. Przy przejściu w Background iOS koduje stan tych obiektów. Po powrocie po wyładowaniu iOS tworzy nowe obiekty i dekoduje zapisany stan. Bez state restoration użytkownik zobaczy pusty ekran po zimnym starcie zamiast miejsca, w którym się zatrzymał.

swift
import UIKit

class DetailViewController: UIViewController {

    var itemID: String = ""
    var scrollPosition: CGPoint = .zero

    override func viewDidLoad() {
        super.viewDidLoad()
        restorationIdentifier = "DetailViewController"
        restorationClass = type(of: self)
    }

    override func encodeRestorableState(with coder: NSCoder) {
        super.encodeRestorableState(with: coder)
        coder.encode(itemID, forKey: "itemID")
        coder.encode(scrollPosition, forKey: "scrollPosition")
    }

    override func decodeRestorableState(with coder: NSCoder) {
        super.decodeRestorableState(with: coder)
        if let savedID = coder.decodeObject(forKey: "itemID") as? String {
            itemID = savedID
            loadItem()
        }
        if let savedPosition = coder.decodeCGPoint(forKey: "scrollPosition") {
            scrollPosition = savedPosition
            // Przywracamy pozycję po załadowaniu danych
        }
    }
}

// AppDelegate — aktywacja State Restoration
func application(
    _ application: UIApplication,
    shouldSaveSecureApplicationState coder: NSCoder
) -> Bool {
    return true
}

func application(
    _ application: UIApplication,
    shouldRestoreSecureApplicationState coder: NSCoder
) -> Bool {
    return true
}

Kod pokazuje implementację State Restoration na iOS. restorationIdentifier i restorationClass są niezbędne dla każdego przywracanego ViewController. encodeRestorableState/decodeRestorableState zapisują i ładują dane przez NSCoder. W AppDelegate shouldSaveSecureApplicationState i shouldRestoreSecureApplicationState włączają szyfrowane zapisywanie stanu. Od iOS 12+ zaleca się używanie secure encoding (NSSecureCoding) do ochrony danych.

Najlepsze praktyki pracy z Suspended

Pierwsza zasada — nigdy nie zakładaj, że aplikacja wróci z Suspended. System może wyładować aplikację w dowolnym momencie. Wszystkie krytycznie ważne dane muszą być zapisane w trwałym magazynie przed przejściem w Suspended — to znaczy w applicationDidEnterBackground lub onStop. UserDefaults, Core Data, File Manager — odpowiednie magazyny. Pamięć (zmienne, właściwości) — zawodny magazyn dla danych, które mają przetrwać Suspended.

Druga zasada — zwalniaj zasoby przed Suspended. Zamykaj deskryptory plików, zwalniaj pamięć GPU (Metal, Core Graphics), zamykaj połączenia sieciowe. Mimo że aplikacja nie zużywa CPU w Suspended, zajęte zasoby są blokowane dla innych aplikacji. Na iOS w Suspended nie można utrzymywać otwartych socketów — po powrocie z Suspended mogą one być niedziałające, co spowoduje błędy.

Trzecia zasada — nie umieszczaj logiki zależnej od czasu w oczekiwaniu na powrót z Suspended. Timery, callbacki i aktywność sieciowa są zatrzymywane w Suspended. Jeśli aplikacja była w Suspended przez kilka godzin, po powrocie timer może zadziałać nieprawidłowo. Sprawdzaj aktualność danych przy powrocie — być może cache jest nieaktualny, a token autoryzacyjny wygasł.

Czwarta zasada — używaj State Restoration dla wszystkich ekranów, szczególnie dla formularzy, list z przewijaniem i ekranów szczegółowych. Bez state restoration użytkownik po powrocie z wyładowanego Suspended zobaczy początkowy ekran aplikacji zamiast miejsca, w którym się zatrzymał. Pogarsza to doświadczenie użytkownika i zmusza go do powtarzania czynności.

swift
import UIKit

// Sprawdzenie: czy aplikacja została wyładowana z pamięci?
func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    // Sprawdzamy, czy istnieje zapisany stan
    if UserDefaults.standard.object(forKey: "navStack") != nil {
        // Aplikacja została wyładowana z Suspended
        // Należy przywrócić stan
        restoreNavigationStack()
    } else {
        // Czysty zimny start z Not Running
        showOnboardingIfNeeded()
    }
    return true
}

private func restoreNavigationStack() {
    guard let savedStack = UserDefaults.standard.array(forKey: "navStack") as? [String],
          let navController = window?.rootViewController as? UINavigationController
    else { return }

    for vcClassName in savedStack {
        if let vcClass = NSClassFromString(vcClassName) as? UIViewController.Type {
            let vc = vcClass.init()
            navController.pushViewController(vc, animated: false)
        }
    }
}

Kod pokazuje praktykę określania, czy aplikacja została wyładowana z Suspended. Sprawdzenie UserDefaults na obecność zapisanego stosu nawigacji pozwala odróżnić zimny start po wyładowaniu od czystego zimnego startu. W pierwszym przypadku przywracany jest stos nawigacji, w drugim — wyświetlany jest onboarding lub ekran główny. Takie podejście uzupełnia wbudowany State Restoration w przypadkach, gdy NSCoder nie wystarcza.

Często zadawane pytania

Jak długo aplikacja może pozostawać w Suspended?

Bez ograniczeń — od kilku sekund do kilku dni. iOS nie ma limitu czasu dla Suspended. Aplikacja będzie pozostawać w pamięci, dopóki system nie zdecyduje się jej wyładować z powodu braku zasobów. W praktyce aplikacje pozostają w Suspended od 15 minut do kilku godzin, w zależności od ilości pamięci RAM urządzenia i liczby aktywnych aplikacji.

Czy applicationWillTerminate jest wywoływane przy wyładowaniu z Suspended?

Nie. applicationWillTerminate nie jest wywoływane przy wyładowaniu aplikacji z Suspended. System po prostu zwalnia pamięć bez powiadamiania aplikacji. To kolejny powód, dlaczego wszystkie zapisy danych powinny odbywać się w applicationDidEnterBackground. applicationWillTerminate jest wywoływane tylko wtedy, gdy użytkownik ręcznie zakończy aplikację przeciągnięciem z App Switcher.

Czy Android ma odpowiednik Suspended?

Bezpośredniego odpowiednika nie ma. Na Android 11+ pojawił się App Freezer, który wstrzymuje procesy w tle przez SIGSTOP — jest to funkcjonalnie podobne do Suspended. Jednak aplikacje Androida powinny być projektowane z myślą o Process Death w dowolnym momencie. Używaj SavedStateHandle w ViewModel i onSaveInstanceState do zapisywania stanu, który przetrwa zarówno App Freezer, jak i Process Death.

Jak sprawdzić, czy aplikacja była w Suspended?

W iOS nie ma bezpośredniego API do sprawdzenia. Metoda pośrednia: sprawdź UserDefaults na obecność zapisanego stanu w didFinishLaunchingWithOptions. Jeśli stan istnieje — aplikacja została wyładowana z Suspended i uruchamia się na zimno. Jeśli nie ma stanu — czysty zimny start. W SwiftUI można zapisać flagę w scenePhase.background i sprawdzić ją przy następnym uruchomieniu.

Czym jest snapshot w kontekście Suspended?

Przy przejściu w Suspended iOS robi snapshot — zrzut ekranu bieżącego UI aplikacji. Ten zrzut jest pokazywany w App Switcher oraz przy powrocie do aplikacji (jako animacja „odmrożenia”). Jeśli aplikacja zawiera poufne dane, snapshot może je ujawnić. Do ochrony używaj UIApplication.shouldSnapshotSecureApp (iOS 16+) lub nakładaj blur-overlay w applicationDidEnterBackground.

Podsumowanie

  • Suspended — aplikacja zamrożona w pamięci iOS, kod nie jest wykonywany, ale UI-stack zachowany do natychmiastowego przywrócenia
  • Unikalność iOS — Suspended nie występuje w Androidzie; Android używa App Freezer (SIGSTOP) jako częściowego odpowiednika
  • Wyładowanie — system wyładowuje aplikacje w Suspended w pierwszej kolejności przy braku pamięci bez powiadomienia
  • Zapisywanie — applicationDidEnterBackground — ostatnia gwarantowana metoda, wszystkie dane muszą być zapisywane tutaj
  • State Restoration — mechanizm NSCoder do automatycznego przywracania UI po wyładowaniu z Suspended
  • Alternatywa Androida — SavedStateHandle + onSaveInstanceState do przetrwania Process Death
  • Snapshot — iOS robi zrzut ekranu przy Suspended; poufne dane należy ukrywać przez blur-overlay lub secure snapshot

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również