Active — الحالة النشطة لدورة حياة تطبيق iOS، حيث يكون في المقدمة، يستلم أحداث اللمس ويتفاعل مع المستخدم. دعنا نفهم كيف يعمل حالة Active، وما هي طرق UIApplicationDelegate المسؤولة عنها، وكيف يتم معالجة الانتقالات بين Active و Inactive في Swift بشكل صحيح.
النقاط الرئيسية
Active — حالة من دورة حياة التطبيق المحمول حيث يكون في المقدمة، يظهر على شاشة الجهاز ويتفاعل بنشاط مع المستخدم. في هذه الحالة، يتلقى التطبيق جميع أحداث اللمس، وضغطات المفاتيح، وبيانات مقياس التسارع والجيروسكوب، ولديه وصول كامل إلى معالج الرسومات لتصدير الواجهة.
في iOS، حالة Active هي جزء من نموذج دورة الحياة خماسي الحالات: Not Running → Inactive → Active → Inactive → Background → Suspended → Not Running. في Android، المكافئ هو حالة Activity بعد استدعاء onResume، عندما تكون Activity في قمة الكومة وتستقبل إدخال المستخدم. Active هي الحالة الوحيدة حيث تكون واجهة المستخدم تفاعلية تماماً وتستجيب للإيماءات والتمرير والنقر والرسوم المتحركة.
يمنح النظام التطبيق في حالة Active أولوية قصوى لمعالج المركزي والذاكرة عائلة التذبذب. هذا يعني أن النظام لن ينهي مثل هذا التطبيق عندما تكون الموارد منخفضة — سيتم تفريغ العمليات الخلفية والمعلقة أولاً. ولكن يجب على التطبيق استخدام الموارد بكفاءة لتجنب استنزاف البطارية وتسبب تقليل سرعة معالج المركزي.
بالنسبة للمستخدم، Active هي الحالة العادية للعمل مع التطبيق. يرى المستخدم الواجهة، ويمكنه الضغط على الأزرار، وملء النماذج، والتمرير في الخيط الزمني. أي مقاطعة لهذه الحالة (مكالمة، إشعار، تمرير للأعلى لفتح Control Center) تنقل التطبيق إلى Inactive، بعد ذلك يمكنه العودة إلى Active أو الذهاب إلى Background.
يستخدم iOS UIApplicationMain لإدارة الحالة. عند الانتقال إلى Active، يستدعي النظام applicationDidBecomeActive. بالنسبة لـ SwiftUI، الآلية المكافئة هي مراقبة scenePhase عبر Environment. يستخدم Android onResume كمؤشر لكون Activity في المقدمة. كلا النهجين يضمنان أن التطبيق يتلقى إشعاراً بتغير الحالة ويمكنه تكييف سلوكه.
| المنصة | الطريقة/الحدث | Swift (UIKit) | SwiftUI | Android (Kotlin) |
|---|---|---|---|---|
| iOS | الانتقال إلى Active | applicationDidBecomeActive | scenePhase == .active | — |
| iOS | الخروج من Active | applicationWillResignActive | scenePhase == .inactive | — |
| Android | الانتقال إلى Active | — | — | onResume() |
| Android | الخروج من Active | — | — | onPause() |
في iOS، تتم معالجة حالة Active عبر UIApplicationDelegate. الطريقة الرئيسية هي applicationDidBecomeActive(_:). تتم استدعاؤها عند أول تشغيل للتطبيق وعند العودة من Inactive. هذه الطريقة هي المكان المثالي لاستئناف المهام التي تم إيقافها عند الدخول إلى Inactive: بدء الرسوم المتحركة، استئناف المؤقتات، إعادة تشغيل الحساسات، التحقق من تحديثات البيانات على الخادم.
مع iOS 13، قدمت Apple UISceneDelegate لدعم النوافذ المتعددة على iPad. في هذه الحالة، يتم استبدال applicationDidBecomeActive بـ sceneDidBecomeActive لكل مشهد فردي. يمكن للتطبيقات التي تدعم شاشة واحدة فقط مواصلة استخدام UIApplicationDelegate. كلا النهجين يتم استدعاؤهما عندما يصبح التطبيق أو المشهد نشطاً.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// أصبح التطبيق نشطاً — استئناف المهام
func applicationDidBecomeActive(_ application: UIApplication) {
resumeAnimations()
restartTimers()
refreshDataIfNeeded()
startObservingSensors()
}
// التطبيق يفقد النشاط — إيقاف مؤقت
func applicationWillResignActive(_ application: UIApplication) {
pauseAnimations()
stopTimers()
saveDraftData()
}
private func resumeAnimations() {
UIView.animate(withDuration: 0.3) {
// استئناف رسوم الواجهة
}
}
private func refreshDataIfNeeded() {
let lastRefresh = UserDefaults.standard.object(forKey: "lastRefresh") as? Date ?? .distantPast
if Date().timeIntervalSince(lastRefresh) > 300 {
fetchDataFromServer()
}
}
}يظهر الكود معالجة Active صحيحة في UIKit. applicationDidBecomeActive تستئنف الرسوم المتحركة والمؤقتات وتتحقق من احتياج تحديثات البيانات. applicationWillResignActive توقف كل شيء يمكن أن يستهلك الموارد وتحفظ المسودات. يضمن هذا الزوج من الطرق أن التطبيق يستجيب بشكل صحيح لتغيرات الحالة.
SwiftUI ليس لديه AppDelegate — إدارة الحالة تتم عبر Environment<ScenePhase>. يتم تعيين القيمة .active عندما يكون المشهد في المقدمة وتفاعلياً. SwiftUI تعيد تشغيل الرسوم المتحركة والتحديثات تلقائياً عند العودة إلى Active. يحتاج المطور فقط إلى الاشتراك في onChange لتنفيذ الآثار الجانبية.
import SwiftUI
@main
struct ActiveDemoApp: App {
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { oldPhase, newPhase in
switch newPhase {
case .active:
print("أصبح المشهد نشطاً")
resumeWork()
case .inactive:
print("أصبح المشهد غير نشط")
pauseWork()
case .background:
print("ذهب المشهد إلى الخلفية")
saveState()
@unknown default:
break
}
}
}
private func resumeWork() {
// استئناف طلبات الشبكة، الرسوم المتحركة
}
private func pauseWork() {
// إيقاف المهام الحساسة للوقت
}
private func saveState() {
// حفظ حالة التطبيق
}
}في SwiftUI، scenePhase هي المصدر الوحيد للحقيقة حول حالة التطبيق. onChange تسمح بتنفيذ إجراءات عند كل انتقال. من المهم تذكر أن scenePhase متاحة فقط على iOS 14+ وفي SwiftUI Lifecycle. بالنسبة لتطبيقات UIKit بشاشات SwiftUI، استخدم نهج UIApplicationDelegate.
Active يمكن الوصول إليه عبر عدة مسارات. الأول والأكثر وضوحاً هو البدء البارد: ينقر المستخدم على الأيقونة، ينتقل التطبيق من Not Running عبر Inactive إلى Active. الثاني هو العودة من الخلفية: يعود المستخدم إلى التطبيق عبر App Switcher، يمر التطبيق عبر Inactive ويصبح Active. الثالث هو العودة من مقاطعة مؤقتة: ينهي المستخدم مكالمة، أو يغلق Control Center، أو يستجيب لإشعار — يعود التطبيق من Inactive إلى Active.
Not Running → Inactive → Active — بدء بارد. Background → Inactive → Active — عودة من الخلفية. Inactive → Active — عودة من مقاطعة مؤقتة. في كل حالة، يتم استدعاء applicationDidBecomeActive، ولكن السياق قد يختلف. في البدء البارد، يتم استدعاء didFinishLaunchingWithOptions قبل Active؛ عند العودة من الخلفية، يتم استدعاء willEnterForeground. يمكن للمطور استخدام هذه الاختلافات لاختيار استراتيجية استعادة الحالة.
| السيناريو | مسار الانتقال | Callbacks iOS | Callbacks Android |
|---|---|---|---|
| بدء بارد | Not Running → Active | didFinishLaunching → didBecomeActive | onCreate → onStart → onResume |
| عودة من الخلفية | Background → Active | willEnterForeground → didBecomeActive | onRestart → onStart → onResume |
| عودة من Suspended | Suspended → Active | willEnterForeground → didBecomeActive | onRestart → onStart → onResume |
| بعد المقاطعة | Inactive → Active | didBecomeActive | onResume |
ملاحظة مهمة: عند العودة من Suspended، لا يستدعي iOS didFinishLaunchingWithOptions لأن التطبيق كان محمولاً فعلى في الذاكرة. هذا يعني أن كود التهيئة الموضوع في هذه الطريقة لا يتم تنفيذه مرة أخرى. كثيراً ما ينسى المطورون هذا وينقلون المنطق الحرج إلى applicationWillEnterForeground أو applicationDidBecomeActive لكلا السيناريوين.
في Android، مكافئ Active هو حالة Activity بعد استدعاء onResume(). تعتبر Activity نشطة عندما تكون في المقدمة وتستقبل إدخال المستخدم. تتوافق هذه الحالة مع قمة كومة Activity. إذا ظهرت Activity أخرى فوقها (حتى جزئياً)، تنتقل Activity الحالية إلى حالة onPause — مكافئ Inactive في iOS.
اختلاف رئيسي في Android هو أن عدة Activities يمكن أن تكون نشطة في نفس الوقت في وضع متعدد النوافذ (split screen، freeform). في هذه الحالة، تعتبر Activity التي يتفاعل معها المستخدم نشطة، بينما المجاورة موقوفة (onPause). iOS لا يدعم النوافذ المتعددة على iPhone، فقط على iPad عبر UIScene.
class MainActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
// أصبح التطبيق نشطاً — استئناف المهام
resumeCameraPreview()
startLocationUpdates()
activateSensors()
}
override fun onPause() {
super.onPause()
// التطبيق يفقد النشاط — تحرير الموارد
releaseCamera()
stopLocationUpdates()
deactivateSensors()
}
private fun resumeCameraPreview() {
// بدء معاينة الكاميرا (يتطلب إذناً)
cameraProvider?.unbindAll()
cameraProvider?.bindToLifecycle(
this,
cameraSelector,
preview,
imageAnalyzer
)
}
private fun startLocationUpdates() {
val locationRequest = LocationRequest.Builder(
Priority.PRIORITY_HIGH_ACCURACY, 5000
).build()
locationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
}
}يظهر الكود معالجة Active في Android عبر onResume/onPause. onResume تستئنف الكاميرا، والجيولوكيشن والحساسات — الموارد التي يجب أن تكون نشطة فقط عندما يكون التطبيق مرئياً للمستخدم. onPause تحرر هذه الموارد لتجنب استنزاف البطارية. API CameraX lifecycle-aware توقف المعاينة تلقائياً عند onPause.
القاعدة الأولى — لا تقم بعمليات ثقيلة في applicationDidBecomeActive أو onResume. تحميل البيانات، تحليل JSON، العمل مع قاعدة البيانات — كل هذا يجب أن يكون غير متزامن ولا يعطل الخيط الرئيسي. استخدم GCD (DispatchQueue) في iOS و Coroutines في Kotlin للمهام الخلفية. يجب على الخيط الرئيسي فقط تحديث الواجهة وبدء العمليات غير المتزامنة.
القاعدة الثانية — قم بمزامنة الحالة عند كل عودة إلى Active. قد يكون المستخدم قد غير الإعدادات في تطبيق النظام، أو تلقى إشعاراً دفعياً، أو حدث البيانات في تطبيق آخر. تحقق من حداثة الذاكرة المؤقتة عند الانتقال إلى Active — قد تكون البيانات قد قدمت خلال غياب المستخدم.
القاعدة الثالثة — لا تعتمد على Active كالحالة الوحيدة. يمكن للتطبيق تجاوز Active والانتقال مباشرة من Not Running إلى Background (إذا تم تشغيله في الوضع الخلفي). في iOS، يحدث هذا عند التشغيل عبر إشعار دفع مع خيار content-available. في Android، عند التشغيل عبر BroadcastReceiver. تحقق دائماً من الحالة الحالية قبل تنفيذ عمليات واجهة المستخدم.
القاعدة الرابعة — استخدم Activity Result API في Android بدلاً من onActivityResult. يتيح هذا معالجة نتيجة استدعاء الكاميرا أو المعرض أو الأذونات مباشرة في حالة Active دون فقدان البيانات عند إعادة إنشاء Activity. لـ iOS، استخدم async/await مع UIApplication.shared.open لحوارات النظام.
import UIKit
final class ActiveStateManager {
static let shared = ActiveStateManager()
private var isActive = false
func setActive(_ active: Bool) {
isActive = active
if active {
NotificationCenter.default.post(name: .appDidBecomeActive, object: nil)
}
}
func performWhenActive(_ block: @escaping () -> Void) {
if isActive {
block()
} else {
// تأجيل التنفيذ حتى العودة إلى Active
NotificationCenter.default.addObserver(
forName: .appDidBecomeActive,
object: nil,
queue: .main
) { _ in
block()
}
}
}
}
extension Notification.Name {
static let appDidBecomeActive = Notification.Name("appDidBecomeActive")
}يظهر الكود مدير حالة Active يسمح لمكونات التطبيق الأخرى بالتحقق من الحالة النشطة الحالية. performWhenActive إما أن تنفذ الكتلة فوراً إذا كان التطبيق نشطاً، أو تؤجل التنفيذ حتى العودة إلى Active. هذا مفيد للخدمات التي تحتاج إلى تنفيذ إجراء بعد عودة المستخدم إلى التطبيق.
الأسئلة الشائعة
يتم استدعاء الطريقة في كل مرة ينتقل فيها التطبيق إلى الحالة النشطة: عند أول تشغيل، عند العودة من الخلفية، بعد إغلاق Control Center أو Notification Center، بعد انتهاء مكالمة. في جلسة عادية يمكن استدعاؤها 5–10 مرات حسب إجراءات المستخدم. لا تضع التهيئة لمرة واحدة في هذه الطريقة.
Visible هو مصطلح غير رسمي يعني أن التطبيق مرئي على الشاشة ولكنه قد لا يستلم الأحداث (على سبيل المثال، مغطى جزئياً بنافذة أخرى على iPad). Active هي الحالة الرسمية حيث يكون التطبيق مرئياً وتفاعلياً في نفس الوقت. على iPhone، التطبيق Visible هو دائماً Active؛ على iPad، من الممكن حالة Visible + Inactive.
willEnterForeground يتم استدعاؤه عند العودة من الخلفية، ولكن التطبيق ليس نشطاً بعد — إنه في Inactive. didBecomeActive يتم استدعاؤه بعد أن يصبح التطبيق تفاعلياً تماماً. إذا كنت تحتاج إلى تنفيذ إجراء قبل أن يرى المستخدم الواجهة — استخدم willEnterForeground. إذا بعد العرض — استخدم didBecomeActive.
لا. Active تعني أن التطبيق في المقدمة ويظهر على الشاشة. بدون واجهة مرئية، يمكن أن يكون التطبيق في Background أو Suspended. الاستثناء هو وضع متعدد النوافذ في iPad، حيث يمكن أن تكون نافذة واحدة نشطة والأخرى لا، ولكن كلتاهما مرئيتان. VoiceOver ومسجل الصوت لا يغيران هذه القاعدة.
على محاكي iOS، اضغط على Cmd+Shift+H للذهاب إلى الشاشة الرئيسية (يذهب التطبيق إلى Background)، ثم اضغط على أيقون التطبيق مرة أخرى. استخدم Cmd+L لقفل الشاشة (willResignActive) وفتحها (didBecomeActive). لاختبار Inactive، افتح Control Center (Cmd+Shift+; للوحة مفاتيح macOS) أو Notification Center.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.