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 запазва образа на приложението в паметта (аналог на хибернация на десктоп), за да при връщане на потребителя незабавно възстанови интерфейса без cold start. Android няма Suspended — процесът или съществува и може да изпълнява код (Background), или е приключен (Not Running), въпреки че Android може да спре изпълнението на нишките чрез LMK.

За потребителя Suspended изглежда като незабавно възстановяване: той превключва между приложения чрез App Switcher и всяко приложение се отваря от същото място, където го е оставил. Това създава илюзията, че всички приложения работят едновременно. В действителност повечето от тях са замразени в Suspended. Горещ старт от Suspended е многократно по-бърз от cold start от Not Running, тъй като кодът вече е зареден в паметта.

Как системата управлява Suspended

iOS проследява състоянието на всички приложения и взема решение за разтоварване на Suspended приложения на базата на наличната памет. Когато паметта не достига, системата започва да разтоварва Suspended приложения, започвайки от тези, които са най-дълго в това състояние. Ако паметта все още е недостатъчна, системата премества приложенията от Background и Inactive в Suspended и след това ги разтоварва. Този процес е напълно прозрачен за потребителя — той просто вижда иконата на приложението в App Switcher, която при щракване стартира cold start.

ХарактеристикаSuspended (iOS)Background (iOS)Background (Android)
Код се изпълняваНеДа (ограничено)Да (ограничено)
В паметтаДаДаДа
Консумация на CPU0%НискаНиска
Горещ стартДа — незабавно възстановяванеДа — чрез InactiveНе — процесът може да е бил убит
Времеви лимитНе — може да е в паметта часове~30 секунди (след beginBackgroundTask)Зависи от версията на API
Разтоварване от систематаПри липса на паметПри критична липса на паметLMK (Low Memory Killer)
Връщане към работаОт App Switcher — незабавноОт App Switcher — чрез InactiveCold start
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 {
        // Ако това е cold start след разтоварване от 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 — само при cold start, когато приложението е било разтоварено от паметта след 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 и системата го е разтоварила, при следващия cold start state restoration възстановява навигационния стек, позицията на превъртане, състоянието на формите и други UI елементи. Потребителят се връща на същия екран, където е спрял.

State Restoration работи чрез протоколите UIViewControllerRestoration и UIStateRestoring. Разработчикът присвоява restorationIdentifier на всеки ViewController и View, които иска да възстанови. При преминаване в Background iOS кодира състоянието на тези обекти. След връщане от разтоварване iOS създава нови обекти и декодира запазеното състояние. Без state restoration потребителят ще види празен екран след cold start вместо мястото, където е спрял.

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 — подходящи хранилища. Паметта (променливи, свойства) — ненадeждно хранилище за данни, които трябва да оцелеят 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 {
        // Чист cold start от 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 за наличие на запазен навигационен стек позволява да се разграничи cold start след разтоварване от чист cold start. В първия случай навигационният стек се възстановява, във втория — се показва 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 и стартира студено. Ако няма състояние — чист cold start. В 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 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също