Suspended — حالت تعلیق چرخه حیات برنامه iOS، که در آن برنامه در حافظه منجمد شده اما کدی اجرا نمیکند. نشان میدهیم Suspended چگونه کار میکند، چه خطراتی freezing برنامه در پسزمینه دارد، iOS چگونه تخلیه برنامههای Suspended را مدیریت میکند و چگونه state restoration را برای بازیابی یکپارچه پس از بازگشت از Suspended پیادهسازی کنیم.
نکات اصلی
Suspended — حالتی از چرخه حیات برنامه iOS است که در آن برنامه در حافظه عملیاتی دستگاه قرار دارد، اما هیچ کدی اجرا نمیکند. این حالت نهایی قبل از پایان کامل است: برنامه پس از اتمام تمام وظایف پسزمینه یا پس از انقضای مهلت زمانی از Background به Suspended منتقل میشود. در Suspended برنامه کاملاً منجمد میشود — همه threadها متوقف، تایمرها غیرفعال و فعالیت شبکه وجود ندارد.
Suspended — ویژگی منحصربهفرد iOS است که در چرخه حیات استاندارد Android وجود ندارد. دلیل آن معماری متفاوت مدیریت فرآیندهاست. iOS تصویر برنامه را در حافظه نگه میدارد (مشابه hibernation در دسکتاپ) تا هنگام بازگشت کاربر، رابط کاربری را بدون cold start فوراً بازیابی کند. Android Suspended ندارد — فرآیند یا وجود دارد و میتواند کد اجرا کند (Background) یا پایان یافته است (Not Running)، اگرچه Android میتواند اجرای threadها را از طریق LMK متوقف کند.
برای کاربر Suspended مانند بازیابی فوری به نظر میرسد: او بین برنامهها از طریق App Switcher جابهجا میشود و هر برنامه از همان جایی که رها کرده باز میشود. این توهم را ایجاد میکند که همه برنامهها همزمان کار میکنند. در واقعیت بیشتر آنها در Suspended منجمد شدهاند. Hot start از Suspended چندین برابر سریعتر از cold start از Not Running است، زیرا کد قبلاً در حافظه بارگذاری شده است.
iOS وضعیت همه برنامهها را ردیابی میکند و بر اساس حافظه موجود درباره تخلیه برنامههای Suspended تصمیم میگیرد. در کمبود حافظه، سیستم شروع به تخلیه برنامههای Suspended میکند، از آنهایی که طولانیترین مدت در این حالت بودهاند. اگر باز هم حافظه کافی نباشد، سیستم برنامهها را از Background و Inactive به Suspended منتقل کرده و سپس تخلیه میکند. این فرآیند برای کاربر کاملاً شفاف است — او فقط آیکون برنامه را در App Switcher میبیند که با کلیک روی آن cold start آغاز میشود.
| ویژگی | Suspended (iOS) | Background (iOS) | Background (Android) |
|---|---|---|---|
| کد اجرا میشود | خیر | بله (محدود) | بله (محدود) |
| در حافظه | بله | بله | بله |
| مصرف CPU | 0% | کم | کم |
| Hot start | بله — بازیابی فوری | بله — از طریق Inactive | خیر — فرآیند ممکن است کشته شده باشد |
| مهلت زمانی | خیر — میتواند ساعتها در حافظه بماند | حدود ۳۰ ثانیه (پس از beginBackgroundTask) | بستگی به نسخه API دارد |
| تخلیه توسط سیستم | در کمبود حافظه | در کمبود بحرانی حافظه | LMK (Low Memory Killer) |
| بازگشت به کار | از App Switcher — فوری | از App Switcher — از طریق Inactive | Cold start |
| State Restoration | توصیه میشود | نیاز نیست | SavedStateHandle |
در iOS Suspended پس از اتمام تمام وظایف پسزمینه بهطور خودکار به دست میآید. سیستم applicationDidEnterBackground را فراخوانی میکند، زمان برای اجرای beginBackgroundTask (حدود ۳۰ ثانیه) میدهد، سپس تمام threadها را اجباری متوقف کرده و برنامه را به حالت Suspended منتقل میکند. اشیاء در حافظه حفظ میشوند، اما هیچ کدی اجرا نمیشود — برنامه در وضعیت فعلی منجمد میشود.
نکته بحرانی: applicationDidEnterBackground — آخرین متدی است که تضمیناً قبل از Suspended فراخوانی میشود. پس از آن برنامه هیچ اعلانی درباره تخلیه از حافظه دریافت نمیکند. اگر کاربر یا سیستم برنامهای را که در Suspended است بکشد، نه applicationWillTerminate و نه دوباره applicationDidEnterBackground فراخوانی نمیشود. بنابراین تمام ذخیرهسازی دادهها باید در applicationDidEnterBackground انجام شود، نه applicationWillTerminate.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// آخرین فراخوانی تضمینشده قبل از Suspended
func applicationDidEnterBackground(_ application: UIApplication) {
// هر چیزی را که باید از تخلیه حافظه جان سالم به در ببرد ذخیره میکنیم
savePersistentState()
saveNavigationStack()
// در صورت نیاز زمان اضافی درخواست میکنیم
let task = application.beginBackgroundTask {
application.endBackgroundTask(task)
}
}
// بازگشت از Suspended — hot start
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 از حافظه تخلیه شده است.
در Android مشابه مستقیمی وجود ندارد برای iOS Suspended. Android برنامهها را با حفظ زمینه اجرا در حافظه منجمد نمیکند. در عوض Android یا فرآیند را در پسزمینه نگه میدارد (Background) یا آن را پایان میدهد (Not Running). اما در Android 11+ (API 30) مکانیزم App Freezer ظاهر شد که اجرای فرآیندهای پسزمینه را با سیگنال SIGSTOP متوقف میکند. این یک مشابه عملکردی Suspended است، اما با تفاوتهای مهم.
App Freezer — بخشی از سیستم مدیریت حافظه Android. وقتی برنامه برای مدت طولانی در پسزمینه است و اعلان فعالی ندارد، سیستم SIGSTOP به آن ارسال میکند و تمام threadها را متوقف میکند. هنگام بازگشت برنامه به پیشزمینه، SIGCONT ارسال میشود و اجرا از سر گرفته میشود. تفاوت کلیدی با iOS: App Freezer حفظ وضعیت را تضمین نمیکند — دادههای حافظه ممکن است اگر فرآیند در طول freezing کشته شود، از بین بروند.
در Android استفاده از SavedStateHandle توصیه میشود در ViewModel برای ذخیره خودکار وضعیت در هر پایان فرآیند. SavedStateHandle دادهها را در Bundle از طریق onSaveInstanceState ذخیره میکند که هم از App Freezer و هم از Process Death جان سالم به در میبرد. برخلاف iOS که تخلیه از Suspended یک وضعیت استثنایی است، در Android Process Death یک رفتار عادی است که باید همیشه انتظار داشت.
// 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()
// دادههایی که باید از freezing جان سالم به در ببرند را ذخیره میکنیم
saveDraftData()
// منابعی که در حالت منجمد نیاز نیستند را آزاد میکنیم
releaseHeavyResources()
// هشدار میدهیم که برنامه منجمد خواهد شد
// (ورود به لاگ برای اشکالزدایی)
Log.d("Lifecycle", "Activity متوقف شد — App Freeze احتمالی")
}
}کد رویکرد پردازش مشابه Suspended در Android را نشان میدهد. SavedStateHandle در ViewModel بهطور خودکار دادهها را در Process Death ذخیره و بازیابی میکند. onStop — آخرین رویداد تضمینشده قبل از App Freezer یا پایان فرآیند. وضعیت فرم سفارش، لیست محصولات در سبد خرید — همه این دادهها به لطف SavedStateHandle از freezing جان سالم به در میبرند. برای منابع سنگین (bitmapها، کرسیورهای دیتابیس) onStop مکان مناسبی برای آزادسازی حافظه است.
State Restoration — مکانیزم داخلی iOS برای ذخیره و بازیابی وضعیت UI پس از تخلیه برنامه از حافظه. اگر برنامه در Suspended بود و سیستم آن را تخلیه کرد، در cold start بعدی state restoration پشته ناوبری، موقعیت اسکرول، وضعیت فرمها و سایر عناصر UI را بازیابی میکند. کاربر به همان صفحهای که در آن متوقف شده بود بازمیگردد.
State Restoration از طریق پروتکلهای UIViewControllerRestoration و UIStateRestoring کار میکند. توسعهدهنده به هر ViewController و View که میخواهد بازیابی شود restorationIdentifier اختصاص میدهد. هنگام رفتن به Background، iOS وضعیت این اشیاء را کدگذاری میکند. پس از بازگشت از تخلیه، iOS اشیاء جدید ایجاد کرده و وضعیت ذخیرهشده را رمزگشایی میکند. بدون state restoration کاربر یک صفحه خالی خواهد دید پس از cold start به جای جایی که متوقف شده بود.
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 — یعنی در applicationDidEnterBackground یا onStop — در ذخیرهسازی دائمی ذخیره شوند. UserDefaults, Core Data, File Manager — ذخیرهسازهای مناسب. حافظه (متغیرها، خصوصیات) — ذخیرهساز غیرقابل اعتماد برای دادههایی که باید از Suspended جان سالم به در ببرند.
قانون دوم — قبل از Suspended منابع را آزاد کنید. توصیفگرهای فایل را ببندید، حافظه GPU (Metal, Core Graphics) را آزاد کنید، اتصالات شبکه را ببندید. اگرچه برنامه در Suspended CPU مصرف نمیکند، منابع اشغالشده برای سایر برنامهها مسدود میشوند. در iOS در Suspended نمیتوان سوکتهای باز نگه داشت — پس از بازگشت از Suspended ممکن است غیرقابل استفاده باشند که باعث خطا میشود.
قانون سوم — منطق وابسته به زمان را در انتظار بازگشت از Suspended قرار ندهید. تایمرها، callbackها و فعالیت شبکه در Suspended متوقف میشوند. اگر برنامه برای چند ساعت در Suspended بوده باشد، پس از بازگشت تایمر ممکن است نادرست عمل کند. در بازگشت بهروزرسانی دادهها را بررسی کنید — احتمالاً cache قدیمی شده و توکن احراز هویت منقضی شده است.
قانون چهارم — برای همه صفحهها از State Restoration استفاده کنید، بهویژه برای فرمهای ورود، لیستهای قابل اسکرول و صفحههای جزئیات. بدون state restoration، کاربر پس از بازگشت از Suspended تخلیهشده، صفحه اولیه برنامه را به جای جایی که متوقف شده بود میبیند. این تجربه کاربری را بدتر میکند و کاربر را مجبور به تکرار اقدامات میکند.
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 خالص تشخیص دهید. در حالت اول پشته ناوبری بازیابی میشود، در حالت دوم — صفحه خوشآمدگویی یا صفحه اصلی نمایش داده میشود. این رویکرد State Restoration داخلی را برای مواردی که NSCoder کافی نیست تکمیل میکند.
سوالات متداول
نامحدود — از چند ثانیه تا چند روز. iOS مهلت زمانی برای Suspended ندارد. برنامه تا زمانی که سیستم تصمیم به تخلیه آن به دلیل کمبود منابع نگیرد در حافظه باقی میماند. در عمل برنامهها بسته به میزان RAM دستگاه و تعداد برنامههای فعال از ۱۵ دقیقه تا چند ساعت در Suspended باقی میمانند.
خیر. applicationWillTerminate فراخوانی نمیشود هنگام تخلیه برنامه از Suspended. سیستم به سادگی حافظه را بدون اطلاعرسانی به برنامه آزاد میکند. این دلیل دیگری است که تمام ذخیرهسازی دادهها باید در applicationDidEnterBackground انجام شود. applicationWillTerminate فقط زمانی فراخوانی میشود که کاربر بهطور دستی برنامه را با کشیدن از App Switcher ببندد.
مشابه مستقیم وجود ندارد. در Android 11+ App Freezer ظاهر شد که فرآیندهای پسزمینه را از طریق SIGSTOP متوقف میکند — این از نظر عملکردی شبیه Suspended است. با این حال، برنامههای Android باید با در نظر گرفتن Process Death در هر لحظه طراحی شوند. از SavedStateHandle در ViewModel و onSaveInstanceState برای ذخیره وضعیتی که هم App Freezer و هم Process Death را تحمل میکند استفاده کنید.
در iOS API مستقیم برای بررسی وجود ندارد. روش غیرمستقیم: UserDefaults را بررسی کنید برای وجود وضعیت ذخیرهشده در didFinishLaunchingWithOptions. اگر وضعیت وجود دارد — برنامه از Suspended تخلیه شده و cold start میکند. اگر وضعیت وجود ندارد — cold start خالص. در SwiftUI میتوانید یک flag در scenePhase.background ذخیره کرده و در راهاندازی بعدی بررسی کنید.
هنگام انتقال به Suspended، iOS یک snapshot — تصویری از UI فعلی برنامه میگیرد. این تصویر در App Switcher و هنگام بازگشت به برنامه (به عنوان انیمیشن «ذوب شدن») نمایش داده میشود. اگر برنامه حاوی دادههای محرمانه باشد، snapshot میتواند آنها را افشا کند. برای محافظت از UIApplication.shouldSnapshotSecureApp (iOS 16+) استفاده کنید یا در applicationDidEnterBackground یک blur-overlay اعمال کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید