Suspended — a paused state of the iOS application lifecycle in which the app is frozen in memory but does not execute code. We show how Suspended works, what risks background app freezing carries, how iOS manages the eviction of Suspended apps, and how to implement state restoration for seamless recovery after returning from Suspended.
Key Takeaways
Suspended is a state of the iOS application lifecycle in which the app resides in the device’s RAM but does not execute any code. This is the final state before full termination: the app transitions to Suspended from Background after completing all background tasks or after a timeout expires. In Suspended, the application is completely frozen — all threads are paused, timers do not work, and there is no network activity.
Suspended is a unique feature of iOS, absent from the standard Android lifecycle. The reason lies in the different process management architectures. iOS preserves the app’s image in memory (similar to desktop hibernation) so that when the user returns, the interface can be instantly restored without a cold start. Android does not have Suspended — a process either exists and can execute code (Background) or is terminated (Not Running), although Android can pause thread execution via LMK.
To the user, Suspended looks like instant restoration: they switch between apps using the App Switcher, and each app opens exactly where they left off. This creates the illusion that all apps are running simultaneously. In reality, most of them are frozen in Suspended. A hot start from Suspended is many times faster than a cold start from Not Running, because the code is already loaded into memory.
iOS monitors the state of all applications and decides to evict Suspended apps based on available memory. When memory is low, the system begins evicting Suspended apps, starting with those that have been in this state the longest. If memory is still insufficient, the system transitions apps from Background and Inactive into Suspended with subsequent eviction. This process is completely transparent to the user — they simply see the app icon in the App Switcher, which triggers a cold start when tapped.
| Characteristic | Suspended (iOS) | Background (iOS) | Background (Android) |
|---|---|---|---|
| Code executes | No | Yes (limited) | Yes (limited) |
| In memory | Yes | Yes | Yes |
| CPU consumption | 0% | Low | Low |
| Hot start | Yes — instant restoration | Yes — via Inactive | No — process may have been killed |
| Timeout | No — can be in memory for hours | ~30 seconds (after beginBackgroundTask) | Depends on API version |
| System eviction | When memory is low | When critically low on memory | LMK (Low Memory Killer) |
| Return to work | From App Switcher — instantly | From App Switcher — via Inactive | Cold start |
| State Restoration | Recommended | Not required | SavedStateHandle |
In iOS, Suspended is reached automatically after all background tasks are completed. The system calls applicationDidEnterBackground, gives time to execute beginBackgroundTask (about 30 seconds), then forcibly pauses all threads and transitions the app to Suspended. Objects in memory are preserved, but no code is executed — the application is frozen in its current state.
A critically important point: applicationDidEnterBackground is the last method guaranteed to be called before Suspended. After this, the app receives no notifications about memory eviction. If the user or system kills the app while it is in Suspended, neither applicationWillTerminate nor applicationDidEnterBackground is called again. Therefore, all data saving must happen in applicationDidEnterBackground, not in applicationWillTerminate.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// Last guaranteed call before Suspended
func applicationDidEnterBackground(_ application: UIApplication) {
// Save everything that needs to survive memory eviction
savePersistentState()
saveNavigationStack()
// Request extra time if needed
let task = application.beginBackgroundTask {
application.endBackgroundTask(task)
}
}
// Return from Suspended — hot start
func applicationWillEnterForeground(_ application: UIApplication) {
// App was in Suspended, resuming work
print("Return from Suspended or Background")
}
// Full restoration after memory eviction
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// If this is a cold start after eviction from Suspended —
// restore state restoration
return true
}
private func savePersistentState() {
UserDefaults.standard.set(Date(), forKey: "lastActiveDate")
}
private func saveNavigationStack() {
guard let rootVC = window?.rootViewController else { return }
// Save the current navigation stack
if let navController = rootVC as? UINavigationController {
let vcClasses = navController.viewControllers.map { type(of: $0) }
UserDefaults.standard.set(vcClasses.map { NSStringFromClass($0) }, forKey: "navStack")
}
}
}The code shows the critically important handling of Suspended on iOS. applicationDidEnterBackground is the last guaranteed call. All data saving should happen here: user state, navigation stack, drafts, timers. applicationWillEnterForeground is called when returning from Suspended or Background. didFinishLaunchingWithOptions — only on cold start, when the app was evicted from memory after Suspended.
In Android, there is no direct analogue of iOS Suspended. Android does not freeze apps in memory while preserving execution context. Instead, Android either keeps the process in the background or terminates it. However, on Android 11+ (API 30), a mechanism called App Freezer was introduced, which suspends background process execution using the SIGSTOP signal. This is functionally similar to Suspended, but with important differences.
App Freezer is part of the Android memory management system. When an app has been in the background for a long time without active notifications, the system sends it SIGSTOP, pausing all threads. When the app returns to the foreground, SIGCONT is sent and execution resumes. The key difference from iOS: App Freezer does not guarantee state preservation — data in memory can be lost if the process is killed during freezing.
On Android, it is recommended to use SavedStateHandle in ViewModel for automatic state preservation during any process termination. SavedStateHandle saves data in a Bundle via onSaveInstanceState, which survives both App Freezer and Process Death. Unlike iOS, where eviction from Suspended is an exceptional situation, on Android, Process Death is normal behavior that should always be expected.
// SavedStateHandle — salvation from Process Death on Android
class CheckoutViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// State that survives the process even after 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
}
}
// Save in onStop in case of App Freezer
class MainActivity : AppCompatActivity() {
override fun onStop() {
super.onStop()
// Save data that must survive freezing
saveDraftData()
// Free resources not needed in a frozen state
releaseHeavyResources()
// Warn that the app will be frozen
// (Logging for debugging)
Log.d("Lifecycle", "Activity stopped — possible App Freeze")
}
}The code shows the approach to handling the Suspended analogue on Android. SavedStateHandle in ViewModel automatically saves and restores data during Process Death. onStop is the last guaranteed event before a possible App Freezer or process termination. The checkout form state, cart item list — all this data survives freezing thanks to SavedStateHandle. For heavy resources (bitmaps, DB cursors), onStop is the place to free memory.
State Restoration is a built-in iOS mechanism for saving and restoring UI state after the app is evicted from memory. If the app was in Suspended and the system evicted it, on the next cold start state restoration restores the navigation stack, scroll position, form state, and other UI elements. The user returns to the same screen where they left off.
State Restoration works through the UIViewControllerRestoration and UIStateRestoring protocols. The developer assigns a restorationIdentifier to each ViewController and View they want to restore. When going to the background, iOS encodes the state of these objects. On return after eviction, iOS creates new objects and decodes the saved state. Without state restoration, the user will see a blank screen after a cold start instead of where they left off.
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
// Restore position after data loading
}
}
}
// AppDelegate — activating State Restoration
func application(
_ application: UIApplication,
shouldSaveSecureApplicationState coder: NSCoder
) -> Bool {
return true
}
func application(
_ application: UIApplication,
shouldRestoreSecureApplicationState coder: NSCoder
) -> Bool {
return true
}The code shows the implementation of State Restoration on iOS. restorationIdentifier and restorationClass are required for each restorable ViewController. encodeRestorableState/decodeRestorableState save and load data via NSCoder. In AppDelegate, shouldSaveSecureApplicationState and shouldRestoreSecureApplicationState enable encrypted state preservation. On iOS 12+, it is recommended to use secure encoding (NSSecureCoding) for data protection.
The first rule — never assume that the app will return from Suspended. The system can evict the app at any moment. All critically important data must be saved to persistent storage before transitioning to Suspended — that is, in applicationDidEnterBackground or onStop. UserDefaults, Core Data, File Manager — suitable storage options. Memory (variables, properties) is an unreliable storage for data that must survive Suspended.
The second rule — free resources before Suspended. Close file descriptors, release GPU memory (Metal, Core Graphics), close network connections. Although the app does not consume CPU in Suspended, held resources are blocked from other applications. On iOS, you cannot keep open sockets in Suspended — when returning from Suspended, they may be non-functional, causing errors.
The third rule — do not place time-dependent logic expecting to return from Suspended. Timers, callbacks, and network activity cease in Suspended. If the app was in Suspended for several hours, a timer may fire incorrectly upon return. Check data validity when returning — the cache may be stale, and the authorization token may have expired.
The fourth rule — use State Restoration for all screens, especially input forms, scrollable lists, and detail screens. Without state restoration, after returning from an evicted Suspended state, the user will see the app’s initial screen instead of where they left off. This degrades the user experience and forces the user to repeat actions.
import UIKit
// Check: was the app evicted from memory?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Check if saved state exists
if UserDefaults.standard.object(forKey: "navStack") != nil {
// The app was evicted from Suspended
// Need to restore state
restoreNavigationStack()
} else {
// Clean cold start from 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)
}
}
}The code shows the practice of determining whether the app was evicted from Suspended. Checking UserDefaults for the presence of a saved navigation stack allows distinguishing a cold start after eviction from a clean cold start. In the first case, the navigation stack is restored; in the second, onboarding or the main screen is shown. This approach complements the built-in State Restoration for cases where NSCoder is insufficient.
Frequently Asked Questions
Unlimited — from a few seconds to several days. iOS has no timeout for Suspended. The app will remain in memory until the system decides to evict it due to insufficient resources. In practice, apps stay in Suspended from 15 minutes to several hours, depending on the device’s RAM and the number of active applications.
No. applicationWillTerminate is not called when the app is evicted from Suspended. The system simply frees memory without notifying the application. This is another reason why all data saving must occur in applicationDidEnterBackground. applicationWillTerminate is only called when the user manually terminates the app by swiping it out of the App Switcher.
There is no direct analogue. On Android 11+, App Freezer was introduced, which pauses background processes via SIGSTOP — this is functionally similar to Suspended. However, Android apps should be designed assuming Process Death can happen at any time. Use SavedStateHandle in ViewModel and onSaveInstanceState to save state that will survive both App Freezer and Process Death.
In iOS, there is no direct API for checking. An indirect method: check UserDefaults for the presence of saved state in didFinishLaunchingWithOptions. If state exists — the app was evicted from Suspended and is starting cold. If no state exists — it is a clean cold start. In SwiftUI, you can save a flag in scenePhase.background and check it on the next launch.
When transitioning to Suspended, iOS takes a snapshot — a screenshot of the current app UI. This screenshot is shown in the App Switcher and when returning to the app (as a “thaw” animation). If the app contains confidential data, the snapshot may expose it. To protect against this, use UIApplication.shouldSnapshotSecureApp (iOS 16+) or apply a blur overlay in applicationDidEnterBackground.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also