@EnvironmentObject — ما هو، مبدأ العمل والاستخدام

المؤلف: IT Sectr نُشر: 2026-06-19 وقت القراءة: 8 دق

@EnvironmentObject هو property wrapper في SwiftUI يقوم بتمرير ObservableObject تلقائياً عبر تسلسل العرض بالكامل دون تمرير صريح في المُهيئ. تحصل العرض الابن على الوصول إلى كائن البيئة بمجرد تعريف خاصية، بينما يوفره الأصل من خلال طريقة .environmentObject(). وفقاً لوثائق مطوري Apple (2025)، يستخدم SwiftUI آلية حقن التبعية على مستوى البيئة، مما يلغي الحاجة لتمرير البيانات عبر مُهيئات العروض الوسيطة. @EnvironmentObject مفيد بشكل خاص للكائنات التي تحتاجها العديد من شاشات التطبيق — نماذج المصادقة، عربات التسوق أو الإعدادات العامة.

النقاط الرئيسية

  • @EnvironmentObject — property wrapper يحصل على ObservableObject من بيئة SwiftUI دون تمريره عبر مُهيئ
  • الحقن يتم بطريقة .environmentObject() على العرض الأصل — يصبح الكائن متاحاً لجميع العناصر الابن
  • الفرق عن @ObservedObject: العروض الابن لا تتطلب معلمة في المُهيئ؛ يتم التقاط الكائن تلقائياً حسب النوع
  • خطأ فقدان الكائن في البيئة — تعطل التطبيق مع خطأ fatal error، لذلك يجب ضمان توفير الكائن قبل أول عرض ابن
  • iOS 17+ ماكرو @Observable يستبدل جزئياً ObservableObject، لكن @EnvironmentObject يستمر في العمل مع الماكرو الجديد عبر @Environment

ما هو @EnvironmentObject؟

@EnvironmentObject هو property wrapper مُعلن في إطار عمل SwiftUI يسمح للعرض بالوصول إلى كائن مخزن في البيئة. على عكس @State أو @StateObject، @EnvironmentObject لا ينشئ كائن — بل يقرأ فقط نسخة موجودة مسبقاً مقدمة من أحد الأسلاف في تسلسل العرض.

تعتمد الآلية على بيئة SwiftUI — قاموس ضمني يُمرر من العرض الجذر إلى جميع العروض الابن. عندما يستدعي الأصل طريقة .environmentObject(someObject)، يضع SwiftUI مرجعاً إلى someObject في البيئة. يمكن لأي عرض في الشجرة الفرعية تعريف @EnvironmentObject var model: ViewModel والحصول على نفس النسخة.

وفقاً لجلسة Apple WWDC 2021 “Demystify SwiftUI،” البيئة مُحسّنة لتمرير البيانات عبر تسلسل عميق دون فقدان أداء — الوصول إلى الكائن يحدث في O(1) عبر البحث حسب النوع. هذا يتناقض مع التمرير اليدوي عبر المُهيئات، حيث ينمو التعقيد خطياً مع عمق التسلسل.

استخدم @EnvironmentObject للحالة العامة المطلوبة في مستويات مختلفة من التطبيق. المرشحون النموذجيون هم نماذج المصادقة، مدراء التنقل، عربات التسوق وموفري بيانات الشبكة.

كيف يعمل @EnvironmentObject

@EnvironmentObject يستخدم آلية SwiftUI المسماة حقن التبعية القائم على البيئة. عندما يعرض SwiftUI التسلسل، يحتفظ بقاموس داخلي EnvironmentValues، يمكن الوصول إليه للقراءة والكتابة في كل مستوى. يقرأ property wrapper @EnvironmentObject من هذا القاموس حسب النوع، باستخدام objectWillChange من بروتوكول ObservableObject للاشتراك في التغييرات.

تتكون العملية من ثلاث خطوات. أولاً، إنشاء ObservableObject في مكان ما في التسلسل، عادة عبر @StateObject أو @ObservedObject في عرض الأصل. ثانياً، استدعاء .environmentObject(object) في ذلك العرض، مما يضع الكائن في البيئة. ثالثاً، تعريف @EnvironmentObject في العروض الابن، التي تستقبل وتشترك تلقائياً في نفس النسخة.

يضمن SwiftUI أنه كلما تغيرت أي خاصية @Published داخل الكائن، سيتم إعادة عرض جميع العروض التي عرّفت @EnvironmentObject بهذا النوع. وفقاً لمقال دوني والز (Donny Wals, 2024)، آلية الاشتراك مماثلة لـ @ObservedObject — الفرق فقط في طريقة الحصول على النسخة، وليس في آلية التحديث.

صمم التسلسل بحيث يتم توفير الكائن في أعلى مستوى ممكن — يضمن ذلك الوصول لجميع العروض التي تحتاجه دون تكرار الكود.

@EnvironmentObject ضد @ObservedObject

كلا property wrappers — @EnvironmentObject و @ObservedObject — يشتركان في ObservableObject ويعيدان عرض العرض عند التغييرات. الفرق الرئيسي في طريقة الحصول على الكائن. @ObservedObject يتطلب تمريراً صريحاً للنسخة عبر مُهيئ العرض، بينما @EnvironmentObject يحصل عليها تلقائياً من البيئة.

خذ بعين الاعتبار تسلسلاً من ثلاثة مستويات: ParentView → MiddleView → ChildView. إذا كان ChildView يحتاج كائن UserSettings، باستخدام @ObservedObject سيتطلب تمريره عبر MiddleView، حتى لو كان MiddleView لا يستخدم هذا الكائن:

swift
struct MiddleView: View {
    @ObservedObject var settings: UserSettings  // only needed to pass down

    var body: some View {
        ChildView(settings: settings)
    }
}

مع @EnvironmentObject، MiddleView لا يحتاج لمعرفة وجود الكائن:

swift
struct MiddleView: View {
    var body: some View {
        ChildView()
    }
}

struct ChildView: View {
    @EnvironmentObject var settings: UserSettings

    var body: some View {
        Text(settings.username)
    }
}

وفقاً لـ Swift by Sundell (2024)، @EnvironmentObject مفضل عندما يُحتاج الكائن في مستويات متعددة من التسلسل، بينما @ObservedObject أفضل عندما يُمرر الكائن مباشرة من أصل إلى ابن وحيد مباشر. اختر @ObservedObject للتمريرات المحلية الفردية و @EnvironmentObject للتبعيات العامة.

@EnvironmentObject ضد @Environment

@Environment و @EnvironmentObject كلاهما يقرأ البيانات من بيئة SwiftUI، لكنهما يعملان مع مصادر مختلفة. @Environment يقرأ القيم المدمجة أو المخصصة من EnvironmentValues — هذه بيانات بسيطة: ألوان، خطوط، أحجام، تقويم، اتجاه التخطيط. @EnvironmentObject يقرأ أنواع المراجع المتوافقة مع ObservableObject.

الفرق الرئيسي هو آلية التحديث. @Environment يستخدم publish-subscribe على مستوى القيم الفردية: عندما تتغير البيئة، يتم إعادة عرض فقط العروض التي تقرأ تلك القيمة. @EnvironmentObject يشترك في objectWillChange لـ ObservableObject، مما قد يسبب إعادة عرض جميع العروض المشتركة في هذا النوع، بغض النظر عن أي خاصية محددة تغيرت.

وفقاً لـ Hacking with Swift (Paul Hudson, 2025)، @Environment مناسب لمعلمات التهيئة: نظام الألوان، حجم الخط الديناميكي، اتجاه الجهاز. @EnvironmentObject لمنطق الأعمال والحالة: نماذج البيانات، الخدمات، المدراء. استخدم @Environment للمعلمات الثابتة أو نادرة التغيير و @EnvironmentObject للبيانات الديناميكية التي تتطلب التفاعلية.

في الممارسة العملية، غالباً ما يتم دمج هاتين الآليتين: @EnvironmentObject يوفر البيانات، بينما @Environment يوفر سياق العرض.

الأخطاء الشائعة عند استخدام @EnvironmentObject

الخطأ الأكثر شيوعاً هو فقدان الكائن في البيئة عند الوصول إليه. إذا عرّف عرض @EnvironmentObject var model: ViewModel، لكن لم يستدعي أي سلف .environmentObject(model)، سيرمي SwiftUI fatal error مع رسالة: “لم يتم العثور على ObservableObject من النوع ViewModel.” يحدث هذا في وقت العرض، وليس وقت الترجمة، لذلك قد يظهر الخطأ فقط في وقت التشغيل.

المشكلة الشائعة الثانية هي نسخ متعددة من نفس النوع. يستخدم SwiftUI نوع الكائن كمفتاح للبحث في البيئة. إذا قدم سلفان مختلفان نسختين مختلفتين من ViewModel عبر .environmentObject، سيستقبل العرض الابن الأقرب في التسلسل، مما قد يؤدي إلى سلوك غير متوقع. الحل هو التصميم بحيث يظهر كل نوع في البيئة مرة واحدة بالضبط.

الخطأ الثالث هو الإفراط في استخدام @EnvironmentObject لبيانات تحتاجها فقط عرض أو اثنين. في هذه الحالة، @ObservedObject مع تمرير صريح عبر المُهيئ يوفر تدفق بيانات أكثر شفافية ويبسط الاختبار. وفقاً لـ Point-Free (2025)، العدد المفرط من الكائنات في البيئة يصعب فهم تبعيات العروض ويجعل الكود أقل قابلية للتنبؤ.

تحقق من أن كل @EnvironmentObject مُقدّم في المستوى الصحيح من التسلسل، وأضف فحوصات احتياطية في onAppear للكائنات الحرجة لاكتشاف الفقدان مبكراً.

أمثلة كود مع @EnvironmentObject

خذ بعين الاعتبار مثالاً كاملاً لتطبيق مع حالة مصادقة عامة. سننشئ ObservableObject AuthManager يخزن حالة تسجيل دخول المستخدم، ونوفره عبر @EnvironmentObject لجميع الشاشات:

swift
import SwiftUI
import Combine

class AuthManager: ObservableObject {
    @Published var isLoggedIn = false
    @Published var username: String = ""

    func login(user: String) {
        username = user
        isLoggedIn = true
    }

    func logout() {
        username = ""
        isLoggedIn = false
    }
}

العرض الجذر يوفر AuthManager عبر البيئة:

swift
@main
struct MyApp: App {
    @StateObject private var authManager = AuthManager()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(authManager)
        }
    }
}

عرض ابن يستقبل AuthManager دون تمرير صريح:

swift
struct ProfileView: View {
    @EnvironmentObject var authManager: AuthManager

    var body: some View {
        VStack {
            if authManager.isLoggedIn {
                Text("Hello, \(authManager.username)")
                Button("Log Out") {
                    authManager.logout()
                }
            } else {
                Button("Log In") {
                    authManager.login(user: "user")
                }
            }
        }
    }
}

المثال الثالث يتضمن ObservableObjects متعددة والجمع بين @EnvironmentObject و @Environment. لنفترض أن التطبيق يستخدم CartManager لعربة التسوق و ThemeManager لنظام الألوان. كلاهما مُقدّم في المستوى الأعلى ومتوفران على أي شاشة دون تمرير عبر المُهيئات. هذا مناسب بشكل خاص مع الشاشات المتداخلة بعمق أو العروض المشروطة، حيث يكون تمرير البيانات عبر المُنشئات صعباً تقنياً.

الأسئلة الشائعة

كيف يختلف @EnvironmentObject عن @ObservedObject؟

@ObservedObject يتطلب تمريراً صريحاً للنسخة عبر مُهيئ العرض، بينما @EnvironmentObject يحصل على الكائن تلقائياً من بيئة SwiftUI. @EnvironmentObject مناسب للبيانات المطلوبة في مستويات متعددة من التسلسل، بينما @ObservedObject مفضل للتمرير المباشر بين الأصل والابن.

ماذا يحدث إذا لم يتم توفير @EnvironmentObject؟

سيرمي SwiftUI fatal error في وقت التشغيل: “لم يتم العثور على ObservableObject من النوع X.” يحدث الخطأ في لحظة عرض العرض الذي عرّف @EnvironmentObject، إذا لم يستدعي أي سلف .environmentObject() مع كائن من هذا النوع. لن يحذر المترجم من هذه الحالة.

هل يمكن استخدام @EnvironmentObject مع iOS 13؟

نعم، @EnvironmentObject متاح منذ iOS 13.0، macOS 10.15، tvOS 13.0 و watchOS 6.0. إنه واحد من أول property wrappers التي قدمتها Apple مع SwiftUI في 2019، وهو يعمل في جميع الإصدارات اللاحقة، بما في ذلك iOS 17 و 18 مع ماكرو @Observable.

كم عدد الكائنات التي يمكن تمريرها عبر @EnvironmentObject؟

عدد الكائنات غير محدود — كل نوع يعمل كمفتاح فريد. يمكن تمرير AuthManager، CartManager، NavigationManager وخدمات أخرى باستدعاء .environmentObject() لكل واحد على حدة. من المهم ألا يكون هناك كائنان من نفس النوع في البيئة — سيؤدي ذلك إلى سلوك غير محدد.

كيفية اختبار عرض مع @EnvironmentObject؟

في الاختبارات، أنشئ نسخة من ObservableObject ومررها عبر .environmentObject(obj) في Preview Provider أو XCTest. لاختبارات الوحدة لحقن العرض، من المناسب استخدام بروتوكول بدلاً من فئة محددة — يسمح ذلك باستبدال التبعيات بكائنات وهمية دون تغيير التسلسل الحقيقي.

الخلاصة

  • @EnvironmentObject — property wrapper للحصول تلقائياً على ObservableObject من بيئة SwiftUI دون تمرير عبر مُهيئ
  • الآلية تعتمد على حقن التبعية القائم على البيئة: يوضع الكائن في البيئة عبر .environmentObject() ويُستخرج حسب النوع
  • الفرق عن @ObservedObject: @EnvironmentObject يلغي حاجة العروض الوسيطة لمعرفة تبعيات الأحفاد العميقة
  • الفرق عن @Environment: @EnvironmentObject يعمل مع ObservableObject، @Environment يعمل مع قيم من EnvironmentValues
  • المخاطر: fatal error عند فقدان الكائن من البيئة، مشكلة نسخ متعددة من نفس النوع، إساءة استخدام الحالة العامة
  • iOS 17+ ماكرو @Observable لا يلغي @EnvironmentObject — كلتا الآليتين تتعايشان لسيناريوهات مختلفة
  • أفضل ممارسة: قدم الكائنات في أعلى مستوى ممكن من التسلسل، استخدم @EnvironmentObject للخدمات العامة و @ObservedObject للتمريرات المحلية

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا