Suspended — шта је то, замрзавање апликације у позадини iOS-а

Аутор: IT Sectr Објављено: 2026-03-03 Време читања: 11 мин

Suspended — суспендовано стање животног циклуса iOS апликације, у којем је она замрзнута у меморији, али не извршава код. Показујемо како функционише Suspended, које ризике носи замрзавање апликације у позадини, како iOS управља истоваром Suspended апликација и како имплементирати state restoration за беспрекоран опоравак након повратка из Suspended-а.

Главно

  • Suspended — апликација је замрзнута у меморији, код се не извршава, UI стек је сачуван
  • iOS Suspended — јединствено стање, којег нема у Android-у; процес постоји, али није активан
  • Истовар — при недостатку меморије, Suspended апликације се прве уклањају, подаци у меморији се губе
  • State Restoration — iOS механизам за чување и обнављање UI стека након истовара
  • didEnterBackground — последња метода која се гарантовано позива пре Suspended-а

Suspended — шта је то за стање

Suspended — стање животног циклуса iOS апликације у којем се она налази у оперативној меморији уређаја, али не извршава никакав код. Ово је коначно стање пре потпуног завршетка: апликација прелази у Suspended из Background-а након завршетка свих позадинских задатака или након истека временског ограничења. У Suspended-у апликација је потпуно замрзнута — све нити су обустављене, тајмери не раде, мрежна активност не постоји.

Suspended — јединствена карактеристика iOS-а, која не постоји у стандардном животном циклусу Android-а. Разлог је различита архитектура управљања процесима. iOS чува слику апликације у меморији (аналогно хибернацији на десктопу) како би при повратку корисника тренутно обновио интерфејс без хладног старта. Android нема Suspended — процес или постоји и може да извршава код (Background), или је завршен (Not Running), мада Android може да обустави извршавање нити путем LMK-а.

За корисника Suspended изгледа као тренутни опоравак: он се пребацује између апликација преко App Switcher-а и свака апликација се отвара са истог места где ју је оставио. То ствара илузију да све апликације раде истовремено. У стварности већина њих је замрзнута у Suspended-у. Врући старт из Suspended-а је вишеструко бржи од хладног старта из Not Running-а, јер је код већ учитан у меморију.

Како систем управља Suspended-ом

iOS прати стање свих апликација и доноси одлуку о истовару Suspended апликација на основу доступне меморије. Када недостаје меморије, систем почиње да истоварује Suspended апликације, почевши од оних које су најдуже у овом стању. Ако меморије и даље недостаје, систем пребацује апликације из Background-а и Inactive-а у Suspended, а затим их истоварује. Овај процес је потпуно транспарентан за корисника — он само види икону апликације у App Switcher-у која при клику покреће хладни старт.

КарактеристикаSuspended (iOS)Background (iOS)Background (Android)
Код се извршаваНеДа (ограничено)Да (ограничено)
У меморијиДаДаДа
Потрошња CPU-а0%НискаНиска
Врући стартДа — тренутни опоравакДа — преко Inactive-аНе — процес је могао бити убијен
Временско ограничењеНе — може бити у меморији сатима~30 секунди (након beginBackgroundTask)Зависи од верзије API-ја
Истовар од стране системаПри недостатку меморијеПри критичном недостатку меморијеLMK (Low Memory Killer)
Повратак на радИз App Switcher-а — тренутноИз App Switcher-а — преко Inactive-аХладни старт
State RestorationПожељанНије потребанSavedStateHandle

Suspended у iOS-у: механизам замрзавања

У iOS-у Suspended се достиже аутоматски након завршетка свих позадинских задатака. Систем позива applicationDidEnterBackground, даје време за извршавање beginBackgroundTask (око 30 секунди), након чега принудно обуставља све нити и пребацује апликацију у Suspended стање. Објекти у меморији су сачувани, али се ниједан код не извршава — апликација је замрзнута у тренутном стању.

Критично важан тренутак: applicationDidEnterBackground — последња метода која се гарантовано позива пре Suspended-а. Након тога апликација не прима никаква обавештења о истовару из меморије. Ако корисник или систем убије апликацију која се налази у Suspended-у, не позива се ни applicationWillTerminate, ни поново applicationDidEnterBackground. Зато се све чување података мора обавити у applicationDidEnterBackground, а не у applicationWillTerminate.

swift
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    // Последњи гарантовани позив пре Suspended-а
    func applicationDidEnterBackground(_ application: UIApplication) {
        // Чувамо све што треба да преживи истовар из меморије
        savePersistentState()
        saveNavigationStack()

        // Тражимо додатно време ако је потребно
        let task = application.beginBackgroundTask {
            application.endBackgroundTask(task)
        }
    }

    // Повратак из Suspended-а — врући старт
    func applicationWillEnterForeground(_ application: UIApplication) {
        // Апликација је била у Suspended-у, враћамо се на рад
        print("Повратак из Suspended-а или Background-а")
    }

    // Потпуни опоравак након истовара из меморије
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // Ако је ово хладни старт након истовара из Suspended-а —
        // обнављамо state restoration
        return true
    }

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

    private func saveNavigationStack() {
        guard let rootVC = window?.rootViewController else { return }
        // Чувамо текући навигациони стек
        if let navController = rootVC as? UINavigationController {
            let vcClasses = navController.viewControllers.map { type(of: $0) }
            UserDefaults.standard.set(vcClasses.map { NSStringFromClass($0) }, forKey: "navStack")
        }
    }
}

Код приказује критично важну обраду Suspended-а на iOS-у. applicationDidEnterBackground — последњи гарантовани позив. Сва чувања података треба да се обаве овде: стање корисника, навигациони стек, нацрти, тајмери. applicationWillEnterForeground се позива при повратку из Suspended-а или Background-а. didFinishLaunchingWithOptions — само при хладном старту, када је апликација истоварена из меморије након Suspended-а.

Suspended у Android-у — да ли постоји аналог

У Android-у нема директног аналога iOS Suspended-а. Android не замрзава апликације у меморији са чувањем контекста извршавања. Уместо тога, Android или држи процес у позадини (Background), или га завршава (Not Running). Међутим, на Android 11+ (API 30) се појавио механизам App Freezer, који обуставља извршавање позадинских процеса помоћу сигнала SIGSTOP. Ово је функционални аналог Suspended-а, али са важним разликама.

App Freezer — део система за управљање меморијом Android-а. Када апликација дуго остане у позадини и нема активних обавештења, систем јој шаље SIGSTOP, обустављајући све нити. При повратку апликације у први план шаље се SIGCONT и извршавање се наставља. Кључна разлика у односу на iOS: App Freezer не гарантује чување стања — подаци у меморији могу бити изгубљени ако процес буде убијен током замрзавања.

На Android-у препоручује се коришћење SavedStateHandle-а у ViewModel-у за аутоматско чување стања при било каквом завршетку процеса. SavedStateHandle чува податке у Bundle-у преко onSaveInstanceState-а, који преживљава и App Freezer и Process Death. За разлику од iOS-а, где је истовар из Suspended-а изузетна ситуација, на Android-у Process Death је нормално понашање које треба увек очекивати.

kotlin
// SavedStateHandle — спас од Process Death-а на Android-у
class CheckoutViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // Стање које ће преживети процес чак и након 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
    }
}

// Чување у onStop за случај App Freezer-а
class MainActivity : AppCompatActivity() {

    override fun onStop() {
        super.onStop()
        // Чувамо податке који треба да преживе замрзавање
        saveDraftData()
        // Ослобађамо ресурсе који нису потребни у замрзнутом стању
        releaseHeavyResources()
        // Упозоравамо да ће апликација бити замрзнута
        // (Логовање за отклањање грешака)
        Log.d("Lifecycle", "Activity заустављена — могуће App Freeze")
    }
}

Код приказује приступ обради аналога Suspended-а на Android-у. SavedStateHandle у ViewModel-у аутоматски чува и обнавља податке при Process Death-у. onStop — последњи гарантовани догађај пре могућег App Freezer-а или завршетка процеса. Стање форме за поруџбину, листа производа у корпи — сви ови подаци преживљавају замрзавање захваљујући SavedStateHandle-у. За тешке ресурсе (битмапе, курсоре базе) onStop је место за ослобађање меморије.

State Restoration: опоравак након Suspended-а

State Restoration — уграђени iOS механизам за чување и обнављање UI стања након истовара апликације из меморије. Ако је апликација била у Suspended-у и систем ју је истоварио, при следећем хладном старту state restoration обнавља навигациони стек, позицију скроловања, стање форми и друге UI елементе. Корисник се враћа на исти екран где се зауставио.

State Restoration ради кроз протоколе UIViewControllerRestoration и UIStateRestoring. Програмер додељује restorationIdentifier сваком ViewController-у и View-у које жели да обнови. При одласку у Background, iOS кодира стање ових објеката. Након повратка из истовара, iOS креира нове објекте и декодира сачувано стање. Без state restoration-а корисник ће видети празан екран након хладног старта уместо места где се зауставио.

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
            // Обнављамо позицију након учитавања података
        }
    }
}

// AppDelegate — активација State Restoration-а
func application(
    _ application: UIApplication,
    shouldSaveSecureApplicationState coder: NSCoder
) -> Bool {
    return true
}

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

Код приказује имплементацију State Restoration-а на iOS-у. restorationIdentifier и restorationClass су неопходни за сваки ViewController који се обнавља. encodeRestorableState/decodeRestorableState чувају и учитавају податке преко NSCoder-а. У AppDelegate-у shouldSaveSecureApplicationState и shouldRestoreSecureApplicationState укључују шифровано чување стања. Од iOS 12+ препоручује се коришћење secure encoding-а (NSSecureCoding) за заштиту података.

Најбоље праксе рада са Suspended-ом

Прво правило — никада не рачунајте да ће се апликација вратити из Suspended-а. Систем може да истовари апликацију у било ком тренутку. Сви критично важни подаци морају бити сачувани у трајном складишту пре преласка у Suspended — односно у applicationDidEnterBackground или onStop-у. UserDefaults, Core Data, File Manager — одговарајућа складишта. Меморија (променљиве, својства) — непоуздано складиште за податке који треба да преживе Suspended.

Друго правило — ослобађајте ресурсе пре Suspended-а. Затварајте дескрипторе датотека, ослобађајте GPU меморију (Metal, Core Graphics), затварајте мрежне конекције. Иако апликација не троши CPU у Suspended-у, заузети ресурси су блокирани за друге апликације. На iOS-у у Suspended-у не могу се држати отворени сокети — при повратку из Suspended-а они могу бити неисправни, што ће изазвати грешке.

Треће правило — не постављајте логику зависну од времена у очекивању повратка из Suspended-а. Тајмери, callback-и и мрежна активност престају у Suspended-у. Ако је апликација била у Suspended-у неколико сати, при повратку тајмер може да реагује некоректно. Проверавајте актуелност података при повратку — можда је кеш застарео, а токен за ауторизацију је истекао.

Четврто правило — користите State Restoration за све екране, посебно за форме за унос, листе са скроловањем и детаљне екране. Без state restoration-а, корисник након повратка из истовареног Suspended-а видеће почетни екран апликације уместо места где се зауставио. То погоршава корисничко искуство и приморава корисника да понавља радње.

swift
import UIKit

// Провера: да ли је апликација истоварена из меморије?
func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    // Проверавамо да ли постоји сачувано стање
    if UserDefaults.standard.object(forKey: "navStack") != nil {
        // Апликација је истоварена из Suspended-а
        // Потребно је обновити стање
        restoreNavigationStack()
    } else {
        // Чисти хладни старт из 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)
        }
    }
}

Код приказује праксу утврђивања да ли је апликација истоварена из Suspended-а. Провера UserDefaults-а на постојање сачуваног навигационог стека омогућава разликовање хладног старта након истовара од чистог хладног старта. У првом случају се обнавља навигациони стек, у другом — приказује се onboarding или главни екран. Овај приступ допуњује уграђени State Restoration за случајеве када NSCoder није довољан.

Често постављана питања

Колико дуго апликација може остати у Suspended-у?

Неограничено — од неколико секунди до неколико дана. iOS нема временско ограничење за Suspended. Апликација ће висити у меморији док систем не одлучи да је истовари због недостатка ресурса. У пракси апликације остају у Suspended-у од 15 минута до неколико сати, у зависности од количине RAM меморије уређаја и броја активних апликација.

Да ли се applicationWillTerminate позива при истовару из Suspended-а?

Не. applicationWillTerminate се не позива при истовару апликације из Suspended-а. Систем једноставно ослобађа меморију без обавештавања апликације. Ово је још један разлог зашто сва чувања података треба да се обаве у applicationDidEnterBackground. applicationWillTerminate се позива само када корисник ручно заврши апликацију превлачењем из App Switcher-а.

Да ли Android има аналог Suspended-а?

Директног аналога нема. На Android 11+ се појавио App Freezer, који обуставља позадинске процесе путем SIGSTOP-а — функционално је слично Suspended-у. Међутим, Android апликације треба пројектовати са очекивањем Process Death у било ком тренутку. Користите SavedStateHandle у ViewModel-у и onSaveInstanceState за чување стања које ће преживети и App Freezer и Process Death.

Како проверити да ли је апликација била у Suspended-у?

У iOS-у не постоји директан API за проверу. Индиректан метод: проверите UserDefaults на постојање сачуваног стања у didFinishLaunchingWithOptions. Ако стање постоји — апликација је истоварена из Suspended-а и стартује хладно. Ако стања нема — чист хладни старт. У SwiftUI-ју можете сачувати флаг у scenePhase.background и проверити га при следећем покретању.

Шта је snapshot у контексту Suspended-а?

При преласку у Suspended, iOS прави snapshot — снимак екрана тренутног UI апликације. Овај снимак се приказује у App Switcher-у и при повратку у апликацију (као анимација «одмрзавања»). Ако апликација садржи поверљиве податке, snapshot их може открити. За заштиту користите UIApplication.shouldSnapshotSecureApp (iOS 16+) или нанесите blur-overlay у applicationDidEnterBackground.

Резиме

  • Suspended — апликација је замрзнута у iOS меморији, код се не извршава, али UI стек је сачуван за тренутни опоравак
  • Јединственост iOS-а — Suspended не постоји у Android-у; Android користи App Freezer (SIGSTOP) као делимични аналог
  • Истовар — систем истоварује Suspended апликације првим редом при недостатку меморије без обавештења
  • Чување — applicationDidEnterBackground — последња гарантована метода, сви подаци се морају чувати овде
  • State Restoration — NSCoder механизам за аутоматско обнављање UI након истовара из Suspended-а
  • Android алтернатива — SavedStateHandle + onSaveInstanceState за преживљавање Process Death-а
  • Snapshot — iOS прави снимак екрана при Suspended-у; поверљиви подаци треба да се сакрију путем blur-overlay-а или secure snapshot-а

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође