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)
Код выполняетсяНетДа (ограниченно)Да (ограниченно)
В памятиДаДаДа
Потребление CPU0%НизкоеНизкое
Горячий стартДа — мгновенное восстановлениеДа — через 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 stopped — possible App Freeze")
    }
}

В коде показан подход к обработке аналога Suspended на Android. SavedStateHandle в ViewModel автоматически сохраняет и восстанавливает данные при Process Death. onStop — последнее гарантированное событие перед возможным App Freezer или завершением процесса. Состояние checkout-формы, список товаров в корзине — все эти данные переживают заморозку благодаря 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 на наличие сохранённого навигационного стека позволяет отличить холодный старт после выгрузки от чистого холодного старта. В первом случае восстанавливается навигационный стек, во втором — показывается онбординг или главный экран. Такой подход дополняет встроенный State Restoration для случаев, когда NSCoder недостаточно.

Часто задаваемые вопросы

Сколько времени приложение может находиться в Suspended?

Неограниченно — от нескольких секунд до нескольких дней. iOS не имеет таймаута для Suspended. Приложение будет висеть в памяти, пока система не решит его выгрузить из-за нехватки ресурсов. На практике приложения остаются в Suspended от 15 минут до нескольких часов, в зависимости от объёма оперативной памяти устройства и количества активных приложений.

Вызывается ли 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-оверлей в 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-оверлей или secure snapshot

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также