Suspended — pozastavený stav životního cyklu iOS aplikace, ve kterém je zmrazena v paměti, ale nevykonává kód. Ukazujeme, jak Suspended funguje, jaká rizika přináší zmrazení aplikace na pozadí, jak iOS spravuje vykládku aplikací v Suspended a jak implementovat state restoration pro bezproblémové obnovení po návratu z Suspended.
Hlavní body
Suspended — stav životního cyklu iOS aplikace, ve kterém se nachází v operační paměti zařízení, ale nevykonává žádný kód. Jedná se o konečný stav před úplným ukončením: aplikace přechází do Suspended z Background po dokončení všech úkolů na pozadí nebo po uplynutí časového limitu. V Suspended je aplikace zcela zmrazena — všechna vlákna jsou pozastavena, časovače nefungují, síťová aktivita není přítomna.
Suspended — jedinečná vlastnost iOS, která neexistuje ve standardním životním cyklu Androidu. Důvodem je odlišná architektura řízení procesů. iOS uchovává obraz aplikace v paměti (analogie hibernace na desktopu), aby při návratu uživatele okamžitě obnovil rozhraní bez cold startu. Android nemá Suspended — proces buď existuje a může vykonávat kód (Background), nebo je ukončen (Not Running), i když Android může pozastavit provádění vláken prostřednictvím LMK.
Pro uživatele vypadá Suspended jako okamžité obnovení: přepíná mezi aplikacemi pomocí App Switcher a každá aplikace se otevírá na stejném místě, kde ji nechal. To vytváří iluzi, že všechny aplikace běží současně. Ve skutečnosti je většina z nich zmrazena v Suspended. Hot start z Suspended je nđ2ikrát rychlejší než cold start z Not Running, protože kód je již načten v paměti.
iOS sleduje stav všech aplikací a rozhoduje o vykládce aplikací v Suspended na základě dostupné paměti. Když paměť nestačí, systém začíná vykládět aplikace v Suspended, počínaje těmi, které jsou v tomto stavu nejdéle. Pokud paměť stále nestačí, systém přesouá aplikace z Background a Inactive do Suspended a následně je vykládá. Tento proces je pro uživatele zcela transparentní — vidí pouze ikonu aplikace v App Switcher, která při kliknutí spouští cold start.
| Vlastnost | Suspended (iOS) | Background (iOS) | Background (Android) |
|---|---|---|---|
| Kód se vykonává | Ne | Ano (omezeně) | Ano (omezeně) |
| V paměti | Ano | Ano | Ano |
| Spotřeba CPU | 0% | Nízká | Nízká |
| Hot start | Ano — okamžité obnovení | Ano — přes Inactive | Ne — proces mohl být zabit |
| Časový limit | Ne — může být v paměti hodiny | ~30 sekund (po beginBackgroundTask) | Závisí na verzi API |
| Vykládka systémem | Při nedostatku paměti | Při kritickém nedostatku paměti | LMK (Low Memory Killer) |
| Návrat k práci | Z App Switcher — okamžitě | Z App Switcher — přes Inactive | Cold start |
| State Restoration | Doporučuje se | Není vyžadován | SavedStateHandle |
V iOS je Suspended dosaženo automaticky po dokončení všech úkolů na pozadí. Systém zavolá applicationDidEnterBackground, dá čas na provedení beginBackgroundTask (přibližně 30 sekund), poté núze pozastaví všechna vlákna a přesune aplikaci do stavu Suspended. Objekty v paměti jsou zachovány, ale žádný kód se nevykonává — aplikace je zmrazena v aktuálním stavu.
Kriticky důležitý okamžik: applicationDidEnterBackground — poslední metoda, která je zaručeně volána před Suspended. Poté aplikace nepřijímá žádná oznámení o vykládce z paměti. Pokud uživatel nebo systém zabije aplikaci nacházející se v Suspended, není voláno ani applicationWillTerminate, ani znovu applicationDidEnterBackground. Proto veškeré ukládání dat musí probíhat v applicationDidEnterBackground, nikoli v applicationWillTerminate.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// Poslední zaručený hovor před Suspended
func applicationDidEnterBackground(_ application: UIApplication) {
// Ukládáme vše, co má přežít vykládku z paměti
savePersistentState()
saveNavigationStack()
// Žádáme o další čas, pokud je to nutné
let task = application.beginBackgroundTask {
application.endBackgroundTask(task)
}
}
// Návrat z Suspended — hot start
func applicationWillEnterForeground(_ application: UIApplication) {
// Aplikace byla v Suspended, vracíme se k práci
print("Návrat z Suspended nebo Background")
}
// Úplné obnovení po vykládce z paměti
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Pokud se jedná o cold start po vykládce z Suspended —
// obnovujeme state restoration
return true
}
private func savePersistentState() {
UserDefaults.standard.set(Date(), forKey: "lastActiveDate")
}
private func saveNavigationStack() {
guard let rootVC = window?.rootViewController else { return }
// Ukládáme aktuální navigační zásobník
if let navController = rootVC as? UINavigationController {
let vcClasses = navController.viewControllers.map { type(of: $0) }
UserDefaults.standard.set(vcClasses.map { NSStringFromClass($0) }, forKey: "navStack")
}
}
}Kód ukazuje kritické zpracování Suspended v iOS. applicationDidEnterBackground — poslední zaručený hovor. Všechna uložení dat by měla probíhat zde: stav uživatele, navigační zásobník, koncepty, časovače. applicationWillEnterForeground je volána při návratu z Suspended nebo Background. didFinishLaunchingWithOptions — pouze při cold startu, když byla aplikace vykládena z paměti po Suspended.
V Androidu neexistuje přímá analogie iOS Suspended. Android nezmrazuje aplikace v paměti s uchováním kontextu provádění. Místo toho Android buď drží proces na pozadí (Background), nebo jej ukončuje (Not Running). Nicméně na Android 11+ (API 30) se objevil mechanismus App Freezer, který pozastavuje provádění procesů na pozadí pomocí signálu SIGSTOP. Jedná se o funkční analogii Suspended, ale s důležitými rozdíly.
App Freezer — část systému řízení paměti Androidu. Když je aplikace dlouho na pozadí a nemá žádná aktivní oznámení, systém jí pošle SIGSTOP, čímž pozastaví všechna vlákna. Při návratu aplikace na popředí je odeslán SIGCONT a provádění je obnoveno. Klíčový rozdíl oproti iOS: App Freezer nezaručuje zachování stavu — data v paměti mohou být ztracena, pokud je proces během zmrazení zabit.
Na Androidu se doporučuje používání SavedStateHandle ve ViewModel pro automatické ukládání stavu při jakémkoli ukončení procesu. SavedStateHandle ukládá data do Bundle prostřednictvím onSaveInstanceState, který přežije jak App Freezer, tak Process Death. Na rozdíl od iOS, kde je vykládka z Suspended výjimečná situace, v Androidu je Process Death normální chování, které by mělo být vždy očekáváno.
// SavedStateHandle — záchrana před Process Death na Androidu
class CheckoutViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// Stav, který přežije proces i 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
}
}
// Ukládání v onStop pro případ App Freezer
class MainActivity : AppCompatActivity() {
override fun onStop() {
super.onStop()
// Ukládáme data, která mají přežít zmrazení
saveDraftData()
// Uvolňujeme zdroje, které nejsou potřeba ve zmrazeném stavu
releaseHeavyResources()
// Varujeme, že aplikace bude zmrazena
// (Logování pro ladění)
Log.d("Lifecycle", "Activity zastavena — možné App Freeze")
}
}Kód ukazuje přístup ke zpracování analogie Suspended na Androidu. SavedStateHandle ve ViewModel automaticky ukládá a obnovuje data při Process Death. onStop — poslední zaručená událost před možným App Freezerem nebo ukončením procesu. Stav objednávkového formuláře, seznam produktů v košíku — všechna tato data přežijí zmrazení díky SavedStateHandle. Pro těžké zdroje (bitmapy, kurzory databáze) je onStop místem pro uvolnění paměti.
State Restoration — vestavěný mechanismus iOS pro ukládání a obnovení stavu UI po vykládce aplikace z paměti. Pokud byla aplikace v Suspended a systém ji vykládl, při následujícím cold startu state restoration obnoví navigační zásobník, pozici posouvání, stav formulářů a další prvky UI. Uživatel se vrací na stejnou obrazovku, kde se zastavil.
State Restoration funguje prostřednictvím protokolů UIViewControllerRestoration a UIStateRestoring. Vývojář přiřazuje restorationIdentifier každému ViewController a View, které chce obnovit. Při přechodu do Background iOS kóduje stav těchto objektů. Po návratu z vykládky iOS vytváří nové objekty a dekóduje uložený stav. Bez state restoration uživatel uvidí prázdnou obrazovku po cold startu místo místa, kde se zastavil.
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
// Obnovujeme pozici po načtení dat
}
}
}
// AppDelegate — aktivace State Restoration
func application(
_ application: UIApplication,
shouldSaveSecureApplicationState coder: NSCoder
) -> Bool {
return true
}
func application(
_ application: UIApplication,
shouldRestoreSecureApplicationState coder: NSCoder
) -> Bool {
return true
}Kód ukazuje implementaci State Restoration v iOS. restorationIdentifier a restorationClass jsou nezbytné pro každý obnovovaný ViewController. encodeRestorableState/decodeRestorableState ukládají a načítají data prostřednictvím NSCoder. V AppDelegate, shouldSaveSecureApplicationState a shouldRestoreSecureApplicationState zapínají šifrované ukládání stavu. Od iOS 12+ se doporučuje používání secure encoding (NSSecureCoding) pro ochranu dat.
První pravidlo — nikdy nepředpokládejte, že se aplikace vrátí z Suspended. Systém může aplikaci kdykoli vykládt. Všechna kriticky důležitá data musí být uložena v trvalém úložišti před přechodem do Suspended — tj. v applicationDidEnterBackground nebo onStop. UserDefaults, Core Data, File Manager — vhodná úložiště. Paměť (proměnné, vlastnosti) — nespolehlivé úložiště pro data, která mají přežít Suspended.
Druhé pravidlo — uvolňujte zdroje před Suspended. Zavírejte deskriptory souborů, uvolňujte paměť GPU (Metal, Core Graphics), zavírejte síťová připojení. I když aplikace nespotřebovává CPU v Suspended, obsazené zdroje jsou blokovány pro jiné aplikace. V iOS v Suspended nelze držet otevřené sockety — při návratu z Suspended mohou být nefunkční, což způsobí chyby.
Třetí pravidlo — neumisťujte logiku závislou na čase v očekávání návratu z Suspended. Časovače, callbacky a síťová aktivita se v Suspended zastavují. Pokud byla aplikace v Suspended několik hodin, při návratu může časovač fungovat nesprávně. Kontrolujte aktuálnost dat při návratu — možná je cache zastaralá a autorizační token vypršel.
Čtvrté pravidlo — používejte State Restoration pro všechny obrazovky, zejména pro vstupní formuláře, seznamy s posouváním a detailní obrazovky. Bez state restoration uživatel po návratu z vykládeného Suspended uvidí počáteční obrazovku aplikace místo místa, kde se zastavil. To zhoršuje uživatelský zážitek a nutí uživatele opakovat akce.
import UIKit
// Kontrola: byla aplikace vykládena z paměti?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Kontrolujeme, zda existuje uložený stav
if UserDefaults.standard.object(forKey: "navStack") != nil {
// Aplikace byla vykládena z Suspended
// Je třeba obnovit stav
restoreNavigationStack()
} else {
// Čistý cold 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)
}
}
}Kód ukazuje praxi určení, zda byla aplikace vykládena z Suspended. Kontrola UserDefaults na přítomnost uloženého navigačního zásobníku umožňuje odlišit cold start po vykládce od čistého cold startu. V prvním případě se navigační zásobník obnoví, ve druhém — zobrazí se onboarding nebo hlavní obrazovka. Tento přístup doplňuje vestavěný State Restoration pro případy, kdy NSCoder nestačí.
Často kladené otázky
Neomezeně — od několika sekund do několika dní. iOS nemá časový limit pro Suspended. Aplikace zůstane v paměti, dokud se systém nerozhodne ji vykládt kvůli nedostatku zdrojů. V praxi zůstávají aplikace v Suspended od 15 minut do několika hodin, v závislosti na množství RAM zařízení a počtu aktivních aplikací.
Ne. applicationWillTerminate není volána při vykládce aplikace z Suspended. Systém jednoduše uvolní paměť bez upozornění aplikace. To je další důvod, proč by veškeré ukládání dat mělo probíhat v applicationDidEnterBackground. applicationWillTerminate je volána pouze tehdy, když uživatel ručně ukončí aplikaci tažením z App Switcher.
Přímá analogie neexistuje. Na Android 11+ se objevil App Freezer, který pozastavuje procesy na pozadí prostřednictvím SIGSTOP — je funkčně podobný Suspended. Nicméně aplikace pro Android by měly být navrhovány s očekáváním Process Death kdykoli. Používejte SavedStateHandle ve ViewModel a onSaveInstanceState pro ukládání stavu, který přežije jak App Freezer, tak Process Death.
V iOS neexistuje přímé API pro kontrolu. Nepřímá metoda: zkontrolujte UserDefaults na přítomnost uloženého stavu v didFinishLaunchingWithOptions. Pokud stav existuje — aplikace byla vykládena z Suspended a startuje studeně. Pokud stav neexistuje — čistý cold start. V SwiftUI můžete uložit flag do scenePhase.background a zkontrolovat jej při následujícím spuštění.
Při přechodu do Suspended iOS pořídí snapshot — snímek obrazovky aktuálního UI aplikace. Tento snímek se zobrazuje v App Switcher a při návratu do aplikace (jako animace rozmrazení). Pokud aplikace obsahuje důvěrné údaje, snapshot je může odhalit. Pro ochranu použijte UIApplication.shouldSnapshotSecureApp (iOS 16+) nebo aplikujte blur-overlay v applicationDidEnterBackground.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také