@EnvironmentObject هو property wrapper في SwiftUI يسمح لأي View في التسلسل بالوصول إلى ObservableObject دون تمريره صراحة عبر سلسلة من المُهيئات. يتم حقن الكائن في البيئة باستخدام المُعدِل .environmentObject() عند مستوى محدد من التسلسل، وبعد ذلك يمكن لجميع View التابعة الوصول إليه عبر @EnvironmentObject. هذا يلغي الحاجة إلى تمرير الكائن عبر View وسيطة لا تستخدمه — ما يُعرف بـ prop drilling. وفقاً لمقال لجون ساندل — Swift by Sundell (2025)، فإن @EnvironmentObject مفيد بشكل خاص للبيانات المشتركة بين الشاشات: جلسة المستخدم، إعدادات التطبيق، مدير سلة التسوق أو ذاكرة تخزين مؤقت محلية.
النقاط الرئيسية
@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 الجذر.
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 يعمل بناءً على آلية حقن التبعيات (DI) المدمجة في SwiftUI. عندما تستدعي .environmentObject() على View، يخزن SwiftUI الكائن في مخزن خاص مرتبط بتلك View وجميع توابعها. عندما تعلن View تابعة عن @EnvironmentObject من نفس النوع، يبحث SwiftUI عن الكائن في البيئة، صاعداً عبر تسلسل الوالدين.
ميزة مهمة — يتم استخدام نوع الكائن كمفتاح للبحث في البيئة. إذا كان هناك كائنان من نفس النوع في البيئة، يجد SwiftUI الأقرب إلى View الحالية في التسلسل. عندما يتم حقن الكائن على مستوى WindowGroup، يصبح متاحاً عالمياً لجميع شاشات التطبيق، وهو مناسب للخدمات ذات الأغراض العامة.
وفقاً لـ objc.io — SwiftUI Architecture (2025)، يستخدم @EnvironmentObject داخلياً آلية مشابهة لـ @ObservedObject، ولكن مع طبقة إضافية من التجريد للعثور على الكائن في التسلسل. SwiftUI لا ينسخ الكائن ولا ينشئ واحداً جديداً — بل يمرر مرجعاً إلى المثيل الموجود، لذا فإن التغييرات في الكائن تكون مرئية تلقائياً لجميع View التي تستخدم @EnvironmentObject.
كل من @EnvironmentObject و @ObservedObject يؤديان نفس الوظيفة الأساسية — يشتركان View في التغييرات على ObservableObject. الفرق يكمن في آلية تمرير الكائن. يتطلب @ObservedObject تمريراً صريحاً عبر مُهيئ، بينما يسترجع @EnvironmentObject الكائن من البيئة دون تحديد صريح في كل View وسيطة.
| الخاصية | @EnvironmentObject | @ObservedObject |
|---|---|---|
| التمرير | عبر .environmentObject() على مستوى التسلسل | عبر مُهيئ كل View |
| وضوح التبعيات | مخفي — غير مرئي في توقيع View | صريح — مرئي في init الخاص بـ View |
| View الوسيطة | لا تعرف عن الكائن | يجب أن تمرر الكائن إلى الأمام |
| خطر الخطأ | عطل وقت التشغيل عند فقدان الكائن | فحص وقت التجميع (إذا كانت المعلمة إجبارية) |
| Prop drilling | يزيلها | يتطلب تمريراً يدوياً |
يعتمد الاختيار بين @EnvironmentObject و @ObservedObject على البنية. إذا كان الكائن مطلوباً في أعماق التسلسل وعبر العديد من الشاشات — فإن @EnvironmentObject أكثر ملاءمة. إذا كانت البنية تتطلب تحديداً صريحاً للتبعيات من أجل الاختبار والقراءة — فإن @ObservedObject هو المفضل.
السيناريو الأكثر شيوعاً هو جلسة المستخدم التي يجب أن تكون متاحة على جميع شاشات التطبيق. من خلال حقن UserSession عبر .environmentObject() في جذر التطبيق، يمكن لأي شاشة الوصول إلى بيانات المستخدم وحالة التفويض.
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”.
إذا قمت بحقن كائنين من نفس النوع على مستويات مختلفة من التسلسل، فستستقبل View التابعة الأقرب حسب التسلسل. هذا قد يؤدي إلى ارتباك إذا كان المطور يتوقع أن يكون الكائن من البيئة الجذرية متاحاً في نافذة مشروطة لها بيئتها الخاصة مع كائن من نفس النوع.
مع تطور SwiftUI، ظهرت طرق بديلة لإدارة التبعيات تعالج بعض عيوب @EnvironmentObject — في المقام الأول ضمنية التبعيات وخطر أعطال وقت التشغيل.
يعتمد اختيار النهج على حجم الفريق وتعقيد التطبيق. للمشاريع الصغيرة، يعمل @EnvironmentObject بشكل رائع. للمشاريع الكبيرة ذات العشرات من الشاشات ومتطلبات الاختبار الصارمة، يفضل التمرير الصريح عبر @ObservedObject أو حاوية DI.
الأسئلة الشائعة
نعم، يمكن لـ View تعريف أي عدد من @EnvironmentObject من أنواع مختلفة. يبحث SwiftUI عن كل نوع بشكل مستقل في البيئة. هذا مفيد عندما تحتاج View إلى الوصول إلى جلسة المستخدم والإعدادات وسلة التسوق في وقت واحد — يتم حقن كل كائن على حدة.
سيتعطل Preview بخطأ وقت التشغيل عند محاولة عرض View. أضف دائماً .environmentObject() في Preview لـ View التي تستخدم @EnvironmentObject. استخدم كائنات وهمية ببيانات اختبارية ليعمل Preview بشكل صحيح ويظهر حالة واقعية.
لا، @EnvironmentObject يعمل فقط مع نوع فئة محدد يتوافق مع ObservableObject. للبروتوكولات، تحتاج إلى استخدام type erasure أو غلاف: أنشئ فئة غلاف تحتوي على مرجع لكائن من نوع البروتوكول، واحقن الغلاف عبر @EnvironmentObject.
أنشئ مثيل ObservableObject ببيانات اختبارية ومرره إلى View عبر .environmentObject(testObject) في الاختبار. هذا هو النمط القياسي لاختبار UI في SwiftUI. لاختبارات الوحدة، اعزل المنطق في ObservableObject واختبره بشكل منفصل عن View.
@EnvironmentObject لا يخلق عبئاً إضافياً على الأداء لأنه فقط يمرر مرجعاً للكائن، وليس نسخة. ومع ذلك، فإن التحديثات المتكررة لخصائص @Published في كائن عام يمكن أن تتسبب في إعادة رسم العديد من View في وقت واحد، مما قد يؤثر على الأداء.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.