Suspended — un estado pausado del ciclo de vida de una aplicación iOS en el que la aplicación está congelada en memoria pero no ejecuta código. Mostramos cómo funciona Suspended, qué riesgos conlleva la congelación de una aplicación en segundo plano, cómo iOS gestiona la descarga de aplicaciones en Suspended y cómo implementar state restoration para una recuperación sin problemas después de regresar de Suspended.
Puntos clave
Suspended es un estado del ciclo de vida de una aplicación iOS en el que la aplicación reside en la RAM del dispositivo pero no ejecuta ningún código. Es el estado final antes de la terminación completa: la aplicación pasa a Suspended desde Background después de completar todas las tareas en segundo plano o después de que expire un tiempo de espera. En Suspended, la aplicación está completamente congelada — todos los hilos están pausados, los temporizadores no funcionan y no hay actividad de red.
Suspended es una característica única de iOS, ausente en el ciclo de vida estándar de Android. La razón radica en las diferentes arquitecturas de gestión de procesos. iOS conserva la imagen de la aplicación en memoria (similar a la hibernación de escritorio) para que cuando el usuario regrese, la interfaz se pueda restaurar instantáneamente sin un inicio en frío. Android no tiene Suspended — un proceso existe y puede ejecutar código (Background) o está terminado (Not Running), aunque Android puede pausar la ejecución de hilos mediante LMK.
Para el usuario, Suspended parece una restauración instantánea: cambian entre aplicaciones usando el App Switcher, y cada aplicación se abre exactamente donde la dejaron. Esto crea la ilusión de que todas las aplicaciones se ejecutan simultáneamente. En realidad, la mayoría están congeladas en Suspended. Un inicio en caliente desde Suspended es muchas veces más rápido que un inicio en frío desde Not Running, porque el código ya está cargado en memoria.
iOS monitorea el estado de todas las aplicaciones y decide descargar las aplicaciones en Suspended según la memoria disponible. Cuando la memoria es baja, el sistema comienza a descargar las aplicaciones en Suspended, comenzando por las que han estado más tiempo en este estado. Si la memoria sigue siendo insuficiente, el sistema transiciona aplicaciones desde Background e Inactive a Suspended con posterior descarga. Este proceso es completamente transparente para el usuario — simplemente ven el icono de la aplicación en el App Switcher, que al tocarlo inicia un inicio en frío.
| Característica | Suspended (iOS) | Background (iOS) | Background (Android) |
|---|---|---|---|
| Ejecuta código | No | Sí (limitado) | Sí (limitado) |
| En memoria | Sí | Sí | Sí |
| Consumo de CPU | 0% | Bajo | Bajo |
| Inicio en caliente | Sí — restauración instantánea | Sí — mediante Inactive | No — el proceso pudo haber sido eliminado |
| Tiempo de espera | No — puede estar en memoria durante horas | ~30 segundos (después de beginBackgroundTask) | Depende de la versión de API |
| Descarga por el sistema | Cuando falta memoria | Cuando falta memoria críticamente | LMK (Low Memory Killer) |
| Regreso al trabajo | Desde App Switcher — instantáneamente | Desde App Switcher — mediante Inactive | Inicio en frío |
| State Restoration | Recomendado | No requerido | SavedStateHandle |
En iOS, Suspended se alcanza automáticamente después de completar todas las tareas en segundo plano. El sistema llama a applicationDidEnterBackground, da tiempo para ejecutar beginBackgroundTask (aproximadamente 30 segundos), luego pausa forzosamente todos los hilos y transiciona la aplicación a Suspended. Los objetos en memoria se conservan, pero no se ejecuta ningún código — la aplicación está congelada en su estado actual.
Un punto críticamente importante: applicationDidEnterBackground es el último método garantizado que se llama antes de Suspended. Después de esto, la aplicación no recibe ninguna notificación sobre la descarga de memoria. Si el usuario o el sistema elimina la aplicación mientras está en Suspended, no se llama ni a applicationWillTerminate ni a applicationDidEnterBackground nuevamente. Por lo tanto, todo el guardado de datos debe ocurrir en applicationDidEnterBackground, no en applicationWillTerminate.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// Última llamada garantizada antes de Suspended
func applicationDidEnterBackground(_ application: UIApplication) {
// Guardar todo lo que necesita sobrevivir a la descarga de memoria
savePersistentState()
saveNavigationStack()
// Solicitar tiempo adicional si es necesario
let task = application.beginBackgroundTask {
application.endBackgroundTask(task)
}
}
// Regreso de Suspended — inicio en caliente
func applicationWillEnterForeground(_ application: UIApplication) {
// La aplicación estaba en Suspended, reanudando trabajo
print("Regreso de Suspended o Background")
}
// Restauración completa después de la descarga de memoria
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Si esto es un inicio en frío después de la descarga de Suspended —
// restaurar state restoration
return true
}
private func savePersistentState() {
UserDefaults.standard.set(Date(), forKey: "lastActiveDate")
}
private func saveNavigationStack() {
guard let rootVC = window?.rootViewController else { return }
// Guardar la pila de navegación actual
if let navController = rootVC as? UINavigationController {
let vcClasses = navController.viewControllers.map { type(of: $0) }
UserDefaults.standard.set(vcClasses.map { NSStringFromClass($0) }, forKey: "navStack")
}
}
}El código muestra el manejo críticamente importante de Suspended en iOS. applicationDidEnterBackground es la última llamada garantizada. Todo el guardado de datos debe ocurrir aquí: estado del usuario, pila de navegación, borradores, temporizadores. applicationWillEnterForeground se llama al regresar de Suspended o Background. didFinishLaunchingWithOptions — solo en inicio en frío, cuando la aplicación fue descargada de memoria después de Suspended.
En Android, no existe un análogo directo de iOS Suspended. Android no congela aplicaciones en memoria conservando el contexto de ejecución. En su lugar, Android mantiene el proceso en segundo plano o lo termina. Sin embargo, en Android 11+ (API 30) se introdujo un mecanismo llamado App Freezer, que suspende la ejecución de procesos en segundo plano mediante la señal SIGSTOP. Esto es funcionalmente similar a Suspended, pero con diferencias importantes.
App Freezer es parte del sistema de gestión de memoria de Android. Cuando una aplicación ha estado en segundo plano durante mucho tiempo sin notificaciones activas, el sistema le envía SIGSTOP, pausando todos los hilos. Cuando la aplicación regresa al primer plano, se envía SIGCONT y la ejecución se reanuda. La diferencia clave con iOS: App Freezer no garantiza la conservación del estado — los datos en memoria pueden perderse si el proceso es eliminado durante la congelación.
En Android, se recomienda usar SavedStateHandle en ViewModel para la conservación automática del estado durante cualquier terminación del proceso. SavedStateHandle guarda datos en un Bundle mediante onSaveInstanceState, que sobrevive tanto a App Freezer como a Process Death. A diferencia de iOS, donde la descarga de Suspended es una situación excepcional, en Android Process Death es un comportamiento normal que debe esperarse siempre.
// SavedStateHandle — salvación de Process Death en Android
class CheckoutViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// Estado que sobrevive al proceso incluso después de 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
}
}
// Guardar en onStop en caso de App Freezer
class MainActivity : AppCompatActivity() {
override fun onStop() {
super.onStop()
// Guardar datos que deben sobrevivir a la congelación
saveDraftData()
// Liberar recursos no necesarios en estado congelado
releaseHeavyResources()
// Advertir que la aplicación será congelada
// (Registro para depuración)
Log.d("Lifecycle", "Activity stopped — posible App Freeze")
}
}El código muestra el enfoque para manejar el análogo de Suspended en Android. SavedStateHandle en ViewModel guarda y restaura automáticamente los datos durante Process Death. onStop es el último evento garantizado antes de un posible App Freezer o terminación del proceso. El estado del formulario de pago, la lista de artículos en el carrito — todos estos datos sobreviven a la congelación gracias a SavedStateHandle. Para recursos pesados (bitmaps, cursores de BD), onStop es el lugar para liberar memoria.
State Restoration es un mecanismo integrado de iOS para guardar y restaurar el estado de la UI después de que la aplicación es descargada de memoria. Si la aplicación estaba en Suspended y el sistema la descargó, en el próximo inicio en frío state restoration restaura la pila de navegación, la posición de desplazamiento, el estado del formulario y otros elementos de la UI. El usuario regresa a la misma pantalla donde estaba.
State Restoration funciona a través de los protocolos UIViewControllerRestoration y UIStateRestoring. El desarrollador asigna un restorationIdentifier a cada ViewController y View que desea restaurar. Al ir al segundo plano, iOS codifica el estado de estos objetos. Al regresar después de una descarga, iOS crea nuevos objetos y decodifica el estado guardado. Sin state restoration, el usuario verá una pantalla en blanco después de un inicio en frío en lugar de donde se quedó.
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
// Restaurar posición después de cargar datos
}
}
}
// AppDelegate — activando State Restoration
func application(
_ application: UIApplication,
shouldSaveSecureApplicationState coder: NSCoder
) -> Bool {
return true
}
func application(
_ application: UIApplication,
shouldRestoreSecureApplicationState coder: NSCoder
) -> Bool {
return true
}El código muestra la implementación de State Restoration en iOS. restorationIdentifier y restorationClass son necesarios para cada ViewController restaurable. encodeRestorableState/decodeRestorableState guardan y cargan datos mediante NSCoder. En AppDelegate, shouldSaveSecureApplicationState y shouldRestoreSecureApplicationState habilitan la conservación cifrada del estado. En iOS 12+, se recomienda usar codificación segura (NSSecureCoding) para la protección de datos.
La primera regla — nunca asuma que la aplicación regresará de Suspended. El sistema puede descargar la aplicación en cualquier momento. Todos los datos críticamente importantes deben guardarse en almacenamiento persistente antes de la transición a Suspended — es decir, en applicationDidEnterBackground o onStop. UserDefaults, Core Data, File Manager — opciones de almacenamiento adecuadas. La memoria (variables, propiedades) es un almacenamiento no fiable para datos que deben sobrevivir a Suspended.
La segunda regla — libere recursos antes de Suspended. Cierre descriptores de archivos, libere memoria de GPU (Metal, Core Graphics), cierre conexiones de red. Aunque la aplicación no consume CPU en Suspended, los recursos ocupados quedan bloqueados para otras aplicaciones. En iOS, no se pueden mantener sockets abiertos en Suspended — al regresar de Suspended, pueden no ser funcionales, causando errores.
La tercera regla — no coloque lógica dependiente del tiempo esperando regresar de Suspended. Los temporizadores, callbacks y la actividad de red cesan en Suspended. Si la aplicación estuvo en Suspended durante varias horas, un temporizador puede activarse incorrectamente al regresar. Verifique la validez de los datos al regresar — la caché puede estar obsoleta y el token de autorización puede haber expirado.
La cuarta regla — use State Restoration para todas las pantallas, especialmente formularios de entrada, listas con desplazamiento y pantallas de detalle. Sin state restoration, después de regresar de un Suspended descargado, el usuario verá la pantalla inicial de la aplicación en lugar de donde se quedó. Esto degrada la experiencia del usuario y obliga al usuario a repetir acciones.
import UIKit
// Verificación: ¿la aplicación fue descargada de memoria?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Verificar si existe estado guardado
if UserDefaults.standard.object(forKey: "navStack") != nil {
// La aplicación fue descargada de Suspended
// Necesita restaurar el estado
restoreNavigationStack()
} else {
// Inicio en frío limpio desde 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)
}
}
}El código muestra la práctica de determinar si la aplicación fue descargada de Suspended. Verificar UserDefaults para la presencia de una pila de navegación guardada permite distinguir un inicio en frío después de una descarga de un inicio en frío limpio. En el primer caso, se restaura la pila de navegación; en el segundo, se muestra la pantalla de inicio o la principal. Este enfoque complementa el State Restoration integrado para casos donde NSCoder es insuficiente.
Preguntas frecuentes
Ilimitado — desde unos segundos hasta varios días. iOS no tiene tiempo de espera para Suspended. La aplicación permanecerá en memoria hasta que el sistema decida descargarla por falta de recursos. En la práctica, las aplicaciones permanecen en Suspended desde 15 minutos hasta varias horas, dependiendo de la RAM del dispositivo y la cantidad de aplicaciones activas.
No. applicationWillTerminate no se llama cuando la aplicación se descarga de Suspended. El sistema simplemente libera memoria sin notificar a la aplicación. Esta es otra razón por la que todo el guardado de datos debe ocurrir en applicationDidEnterBackground. applicationWillTerminate solo se llama cuando el usuario termina manualmente la aplicación deslizándola fuera del App Switcher.
No hay un análogo directo. En Android 11+, se introdujo App Freezer, que pausa los procesos en segundo plano mediante SIGSTOP — esto es funcionalmente similar a Suspended. Sin embargo, las aplicaciones de Android deben diseñarse asumiendo Process Death en cualquier momento. Use SavedStateHandle en ViewModel y onSaveInstanceState para guardar el estado que sobrevivirá tanto a App Freezer como a Process Death.
En iOS no hay una API directa para verificarlo. Un método indirecto: verificar UserDefaults para la presencia de estado guardado en didFinishLaunchingWithOptions. Si existe estado — la aplicación fue descargada de Suspended y se inicia en frío. Si no existe estado — es un inicio en frío limpio. En SwiftUI, se puede guardar un indicador en scenePhase.background y verificarlo en el próximo inicio.
Al hacer la transición a Suspended, iOS toma un snapshot — una captura de pantalla de la UI actual de la aplicación. Esta captura se muestra en el App Switcher y al regresar a la aplicación (como una animación de “descongelación”). Si la aplicación contiene datos confidenciales, el snapshot puede exponerlos. Para protegerlos, use UIApplication.shouldSnapshotSecureApp (iOS 16+) o aplique una superposición de desenfoque en applicationDidEnterBackground.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también