@EnvironmentObject: ما هو، حقن التبعيات والوصول إلى البيانات

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

@EnvironmentObject هو property wrapper في SwiftUI يسمح لأي View في التسلسل بالوصول إلى ObservableObject دون تمريره صراحة عبر سلسلة من المُهيئات. يتم حقن الكائن في البيئة باستخدام المُعدِل .environmentObject() عند مستوى محدد من التسلسل، وبعد ذلك يمكن لجميع View التابعة الوصول إليه عبر @EnvironmentObject. هذا يلغي الحاجة إلى تمرير الكائن عبر View وسيطة لا تستخدمه — ما يُعرف بـ prop drilling. وفقاً لمقال لجون ساندل — Swift by Sundell (2025)، فإن @EnvironmentObject مفيد بشكل خاص للبيانات المشتركة بين الشاشات: جلسة المستخدم، إعدادات التطبيق، مدير سلة التسوق أو ذاكرة تخزين مؤقت محلية.

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

  • @EnvironmentObject — property wrapper للوصول إلى ObservableObject من بيئة SwiftUI.
  • الحقن عبر .environmentObject() — يتم تمرير الكائن إلى التسلسل مرة واحدة، متاح لجميع View التابعة.
  • بدون تمرير صريح — View الوسيطة لا تحتاج إلى معرفة الكائن، مما يبسط البنية.
  • عطل وقت التشغيل — إذا لم يتم العثور على الكائن في البيئة، يتعطل التطبيق مع خطأ fatal.
  • iOS 13+ — @EnvironmentObject متاح منذ الإصدار الأول من SwiftUI.

ما هو @EnvironmentObject في SwiftUI

@EnvironmentObject هو property wrapper يسمح لـ View في SwiftUI بالوصول إلى ObservableObject من بيئة التطبيق. البيئة هي حاوية يمكن وضع الكائنات فيها على أي مستوى من تسلسل View باستخدام المُعدِل .environmentObject(). بمجرد وضع كائن في البيئة، يمكن لأي View تابعة الوصول إليه بمجرد تعريف خاصية باستخدام @EnvironmentObject وتحديد نوع الكائن.

الغرض الرئيسي من @EnvironmentObject هو حل مشكلة تمرير البيانات عبر تسلسل View عميق دون الحاجة إلى تمرير الكائن عبر كل مستوى وسيط. في التطبيقات المعقدة ذات الهيكل المتفرع من NavigationStack وTabView والنوافذ المشروطة، يعمل @EnvironmentObject على تبسيط البنية بشكل كبير من خلال إلغاء الحاجة إلى الكود المتكرر.

وفقاً لـ Apple Developer Documentation — Environment (2025)، يستخدم @EnvironmentObject آلية داخلية في SwiftUI تعتمد على PreferenceKey وتعريف View. تخزن كل View مرجعاً لبيئتها الخاصة، الموروثة من View الأصل ويمكن توسيعها باستخدام .environmentObject(). يتم البحث عن الكائن بالصعود في التسلسل حتى View الجذر.

swift
class UserSession: ObservableObject {
    @Published var isLoggedIn = false
    @Published var userName: String = ""
    
    func login(name: String) {
        userName = name
        isLoggedIn = true
    }
}

@main
struct MyApp: App {
    @StateObject var session = UserSession()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(session)
        }
    }
}

كيف يعمل @EnvironmentObject

@EnvironmentObject يعمل بناءً على آلية حقن التبعيات (DI) المدمجة في SwiftUI. عندما تستدعي .environmentObject() على View، يخزن SwiftUI الكائن في مخزن خاص مرتبط بتلك View وجميع توابعها. عندما تعلن View تابعة عن @EnvironmentObject من نفس النوع، يبحث SwiftUI عن الكائن في البيئة، صاعداً عبر تسلسل الوالدين.

ميزة مهمة — يتم استخدام نوع الكائن كمفتاح للبحث في البيئة. إذا كان هناك كائنان من نفس النوع في البيئة، يجد SwiftUI الأقرب إلى View الحالية في التسلسل. عندما يتم حقن الكائن على مستوى WindowGroup، يصبح متاحاً عالمياً لجميع شاشات التطبيق، وهو مناسب للخدمات ذات الأغراض العامة.

وفقاً لـ objc.io — SwiftUI Architecture (2025)، يستخدم @EnvironmentObject داخلياً آلية مشابهة لـ @ObservedObject، ولكن مع طبقة إضافية من التجريد للعثور على الكائن في التسلسل. SwiftUI لا ينسخ الكائن ولا ينشئ واحداً جديداً — بل يمرر مرجعاً إلى المثيل الموجود، لذا فإن التغييرات في الكائن تكون مرئية تلقائياً لجميع View التي تستخدم @EnvironmentObject.

البحث عن الكائن في البيئة

  • من View الحالية إلى الأعلى — يتحقق SwiftUI من بيئة View الحالية، ثم الأصل، وهكذا حتى الجذر.
  • أول كائن يتم العثور عليه — يتم استخدام أول كائن مطابق يتم العثور عليه عند الصعود في التسلسل.
  • خطأ fatal — إذا لم يتم العثور على كائن في أي مستوى، يتعطل التطبيق مع رسالة “No ObservableObject found”.

@EnvironmentObject ضد @ObservedObject: مقارنة

كل من @EnvironmentObject و @ObservedObject يؤديان نفس الوظيفة الأساسية — يشتركان View في التغييرات على ObservableObject. الفرق يكمن في آلية تمرير الكائن. يتطلب @ObservedObject تمريراً صريحاً عبر مُهيئ، بينما يسترجع @EnvironmentObject الكائن من البيئة دون تحديد صريح في كل View وسيطة.

الخاصية@EnvironmentObject@ObservedObject
التمريرعبر .environmentObject() على مستوى التسلسلعبر مُهيئ كل View
وضوح التبعياتمخفي — غير مرئي في توقيع Viewصريح — مرئي في init الخاص بـ View
View الوسيطةلا تعرف عن الكائنيجب أن تمرر الكائن إلى الأمام
خطر الخطأعطل وقت التشغيل عند فقدان الكائنفحص وقت التجميع (إذا كانت المعلمة إجبارية)
Prop drillingيزيلهايتطلب تمريراً يدوياً

يعتمد الاختيار بين @EnvironmentObject و @ObservedObject على البنية. إذا كان الكائن مطلوباً في أعماق التسلسل وعبر العديد من الشاشات — فإن @EnvironmentObject أكثر ملاءمة. إذا كانت البنية تتطلب تحديداً صريحاً للتبعيات من أجل الاختبار والقراءة — فإن @ObservedObject هو المفضل.

أمثلة على استخدام @EnvironmentObject

السيناريو الأكثر شيوعاً هو جلسة المستخدم التي يجب أن تكون متاحة على جميع شاشات التطبيق. من خلال حقن UserSession عبر .environmentObject() في جذر التطبيق، يمكن لأي شاشة الوصول إلى بيانات المستخدم وحالة التفويض.

swift
struct ProfileView: View {
    @EnvironmentObject var session: UserSession
    
    var body: some View {
        VStack {
            if session.isLoggedIn {
                Text("Hello, \(session.userName)")
                Button("Logout") {
                    session.isLoggedIn = false
                }
            } else {
                LoginView()
            }
        }
    }
}

struct SettingsView: View {
    @EnvironmentObject var session: UserSession
    
    var body: some View {
        Form {
            Text("Logged in as \(session.userName)")
        }
    }
}

لاحظ أنه ولا ProfileView ولا SettingsView تتلقى الجلسة عبر مُهيئ. إنهما ببساطة تعلنان @EnvironmentObject var session: UserSession، ويجد SwiftUI تلقائياً الكائن في البيئة. هذا يسمح بإضافة شاشات جديدة دون تغيير الكود الموجود لنقل البيانات.

الأخطاء الشائعة والمخاطر

الخطر الرئيسي لـ @EnvironmentObject هو عطل وقت التشغيل إذا لم يتم حقن الكائن في البيئة. على عكس المعلمات الاختيارية، لا يمكن أن يكون @EnvironmentObject nil. إذا ظهرت View مع @EnvironmentObject على الشاشة ولم تستدع View الأصل .environmentObject() لذلك النوع، يتعطل التطبيق فوراً مع رسالة “Fatal error: No ObservableObject of type X found”.

كيفية الحماية من الأعطال

  • الحقن العالمي — احقن الكائن في أعلى مستوى (WindowGroup) ليكون متاحاً لجميع الشاشات.
  • التحقق في Preview — أضف دائماً .environmentObject() في SwiftUI Preview، وإلا فسيتعطل Preview.
  • التوثيق والاختبارات — وثق ما يتوقعه View من @EnvironmentObject واكتب اختبارات تتحقق من وجودها.
  • الاستبدال بـ @ObservedObject — إذا كان الكائن مطلوباً لشاشة واحدة فقط، استخدم @ObservedObject مع تمرير صريح.

مشكلة المثيلات المتعددة

إذا قمت بحقن كائنين من نفس النوع على مستويات مختلفة من التسلسل، فستستقبل View التابعة الأقرب حسب التسلسل. هذا قد يؤدي إلى ارتباك إذا كان المطور يتوقع أن يكون الكائن من البيئة الجذرية متاحاً في نافذة مشروطة لها بيئتها الخاصة مع كائن من نفس النوع.

بدائل @EnvironmentObject

مع تطور SwiftUI، ظهرت طرق بديلة لإدارة التبعيات تعالج بعض عيوب @EnvironmentObject — في المقام الأول ضمنية التبعيات وخطر أعطال وقت التشغيل.

  • property wrapper @Environment — لقيم البيئة المضمنة (colorScheme, locale, sizeCategory). غير مناسب لـ ObservableObject المخصص، فقط لمفاتيح EnvironmentValues القياسية.
  • Custom EnvironmentKey — يمكنك تعريف مفتاح بيئة مخصص لأنواع القيم. لا ينصح بتخزين ObservableObject في EnvironmentValues بسبب دلالات المرجع.
  • @ObservedObject مع تمرير صريح — نهج آمن مع فحص وقت التجميع. لا يمكن أن تظهر View بدون الكائن المطلوب — يجب تمريره عبر init.
  • حاوية حقن التبعيات — حاوية DI خارجية (مثل Resolver أو Swinject) لإدارة التبعيات خارج SwiftUI.

يعتمد اختيار النهج على حجم الفريق وتعقيد التطبيق. للمشاريع الصغيرة، يعمل @EnvironmentObject بشكل رائع. للمشاريع الكبيرة ذات العشرات من الشاشات ومتطلبات الاختبار الصارمة، يفضل التمرير الصريح عبر @ObservedObject أو حاوية DI.

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

هل يمكنني استخدام عدة @EnvironmentObject في View واحد؟

نعم، يمكن لـ View تعريف أي عدد من @EnvironmentObject من أنواع مختلفة. يبحث SwiftUI عن كل نوع بشكل مستقل في البيئة. هذا مفيد عندما تحتاج View إلى الوصول إلى جلسة المستخدم والإعدادات وسلة التسوق في وقت واحد — يتم حقن كل كائن على حدة.

ماذا يحدث إذا حقنت @EnvironmentObject في Preview بدون .environmentObject()؟

سيتعطل Preview بخطأ وقت التشغيل عند محاولة عرض View. أضف دائماً .environmentObject() في Preview لـ View التي تستخدم @EnvironmentObject. استخدم كائنات وهمية ببيانات اختبارية ليعمل Preview بشكل صحيح ويظهر حالة واقعية.

هل يمكنني استخدام @EnvironmentObject مع البروتوكولات؟

لا، @EnvironmentObject يعمل فقط مع نوع فئة محدد يتوافق مع ObservableObject. للبروتوكولات، تحتاج إلى استخدام type erasure أو غلاف: أنشئ فئة غلاف تحتوي على مرجع لكائن من نوع البروتوكول، واحقن الغلاف عبر @EnvironmentObject.

كيف أختبر View يستخدم @EnvironmentObject؟

أنشئ مثيل ObservableObject ببيانات اختبارية ومرره إلى View عبر .environmentObject(testObject) في الاختبار. هذا هو النمط القياسي لاختبار UI في SwiftUI. لاختبارات الوحدة، اعزل المنطق في ObservableObject واختبره بشكل منفصل عن View.

هل يؤثر @EnvironmentObject على الأداء مع العديد من الشاشات؟

@EnvironmentObject لا يخلق عبئاً إضافياً على الأداء لأنه فقط يمرر مرجعاً للكائن، وليس نسخة. ومع ذلك، فإن التحديثات المتكررة لخصائص @Published في كائن عام يمكن أن تتسبب في إعادة رسم العديد من View في وقت واحد، مما قد يؤثر على الأداء.

الخلاصة

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

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

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

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

اقرأ أيضًا