@ObservedObject هو property wrapper في SwiftUI يسمح للعرض بمراقبة التغييرات في ObservableObject منشأ في مكان آخر من التسلسل الهرمي. بخلاف @StateObject، فإن @ObservedObject لا ينشئ الكائن — إنه فقط يشترك في ناشره objectWillChange ويعيد رسم العرض عندما تتحدث الخصائص المنشورة. هذا يجعل @ObservedObject الاختيار الصحيح للعروض الفرعية التي تتلقى بيانات من الأصل عبر المهيئ. وفقًا لمقال بقلم Paul Hudson — Hacking with Swift (2025)، فإن الهندسة النموذجية لتطبيق SwiftUI تبنى كالآتي: يستخدم العرض الجذري @StateObject لإنشاء view model، وتستقبله جميع العروض الفرعية عبر @ObservedObject، مما يضمن مصدرًا واحدًا للحقيقة دون تكرار البيانات.
النقاط الرئيسية
@ObservedObject هو property wrapper يشترك العرض في تغييرات ObservableObject. عندما يقوم كائن معلم بـ @ObservedObject بتغيير أي من خصائصه المعلنة بـ @Published، فإن SwiftUI تعيد رسم العرض تلقائيًا. @ObservedObject لا ينشئ الكائن — إنه فقط ينشئ اتصالًا بين مثال موجود ObservableObject والعرض الذي يجب أن يتفاعل مع تغييراته.
الفرق الرئيسي بين @ObservedObject و @StateObject هو الملكية. @ObservedObject يفترض أن الكائن تم إنشاؤه وتخزينه في مكان أعلى من التسلسل الهرمي للعروض. تتلقى العروض الفرعية مرجعًا إلى هذا الكائن عبر المهيئ وتراقبه فقط. إذا تم إعادة إنشاء العرض الفرعي، فإنه يتلقى نفس المرجع من الأصل — لا تفقد البيانات.
وفقًا لـ Apple Developer Documentation — SwiftUI (2025)، @ObservedObject متاح بدءًا من iOS 13، مما يجعله الخيار الوحيد لمراقبة ObservableObject في المشاريع التي تدعم إصدارات iOS القديمة. في iOS 14+، يُفضل @StateObject لإنشاء الكائنات، ولكن @ObservedObject يبقى ذا صلة لنقل الكائنات الموجودة.
تعتمد آلية @ObservedObject على بروتوكول ObservableObject من إطار Combine. كل فئة تتوافق مع ObservableObject تحصل تلقائيًا على ناشر objectWillChange يرسل إشارة قبل أي تغيير في خاصية @Published. تشترك SwiftUI في هذا الناشر عبر @ObservedObject وعند استلام الإشارة تميز العرض بأنه يحتاج إلى إعادة رسم.
class TaskViewModel: ObservableObject {
@Published var tasks: [Task] = []
@Published var isLoading = false
func loadTasks() async {
isLoading = true
// جلب البيانات
isLoading = false
}
}
struct TaskListView: View {
@ObservedObject var viewModel: TaskViewModel
var body: some View {
List(viewModel.tasks) { task in
Text(task.title)
}
.task { await viewModel.loadTasks() }
}
}
عندما يمرر العرض الأصلي viewModel إلى TaskListView عبر المهيئ، ينشئ SwiftUI اتصالًا بين الكائن والعرض. عند تغيير مصفوفة tasks أو علامة isLoading، يعيد SwiftUI رسم TaskListView. الكائن نفسه يبقى دون تغيير — يتم تخزينه في العرض الأصلي عبر @StateObject.
الفرق بين @ObservedObject و @StateObject هو الفرق بين مراقب ومالك. @StateObject ينشئ الكائن ويدير دورة حياته. @ObservedObject يراقب فقط كائنًا تم إنشاؤه وتخزينه في مكان آخر. يتم تحديد الاختيار بينهما بناءً على مسؤولية العرض عن البيانات.
| السيناريو | التوصية | السبب |
|---|---|---|
| العرض ينشئ بيانات | @StateObject | العرض يملك الكائن ويتحمل مسؤولية دورة حياته |
| العرض يتلقى بيانات | @ObservedObject | العرض يراقب فقط، الكائن يعيش في الأصل |
| دعم iOS 13 | @ObservedObject | @StateObject غير متاح، استخدم @ObservedObject مع إدارة يدوية |
| مكون قابل لإعادة الاستخدام | @ObservedObject | المكون لا يجب أن ينشئ بيانات — يستقبلها من الخارج |
القاعدة الرئيسية: إذا كان العرض ينشئ الكائن — @StateObject. إذا كان العرض يستقبل الكائن — @ObservedObject. انتهاك هذه القاعدة باستخدام @ObservedObject لإنشاء كائن يؤدي إلى فقدان البيانات عند إعادة بناء العرض. انتهاكها باستخدام @StateObject لاستقبال كائن ينشئ مثالًا مكررًا مستقلًا عن الأصل.
سيناريو استخدام نموذجي لـ @ObservedObject هو قائمة مهام حيث ينشئ العرض الجذري view model وتستقبله كل خلية في القائمة عبر @ObservedObject. يمكن لكل خلية استدعاء طرق view model، وتنعكس التغييرات تلقائيًا عبر القائمة بأكملها لأن جميع الخلايا تراقب نفس الكائن.
struct TaskRow: View {
@ObservedObject var viewModel: TaskViewModel
let task: Task
var body: some View {
HStack {
Text(task.title)
Spacer()
Button("تم") {
viewModel.completeTask(task)
}
}
}
}
struct TaskListContainer: View {
@StateObject var viewModel = TaskViewModel()
var body: some View {
List(viewModel.tasks) { task in
TaskRow(viewModel: viewModel, task: task)
}
}
}
في هذا المثال، TaskListContainer ينشئ viewModel عبر @StateObject، وتستقبله كل TaskRow عبر @ObservedObject. عندما يضغط المستخدم على “Done” في أي صف، يغير viewModel.completeTask خاصية منشورة، وتتحدث جميع العروض التي تراقب هذا الكائن تلقائيًا.
أكثر الأخطاء شيوعًا هو استخدام @ObservedObject لإنشاء كائن داخل العرض. عندما يتم إعادة بناء العرض (على سبيل المثال، عند تغيير الحالة)، ينشئ SwiftUI مثالًا جديدًا ObservableObject، مما يؤدي إلى فقدان جميع البيانات المتراكمة. هذا الخطأ مؤلم بشكل خاص في NavigationStack، حيث قد يملأ المستخدم نموذجًا ويفقد البيانات عند العودة.
// ❌ فقدان البيانات: @ObservedObject لا يحتفظ بالكائن
struct FormView: View {
@ObservedObject var formVM = FormViewModel()
// تم إنشاء formVM جديد عند كل إعادة بناء للعرض!
}
// ✅ صحيح: @StateObject يحتفظ بالكائن
struct FormView: View {
@StateObject var formVM = FormViewModel()
// تم إنشاء الكائن مرة واحدة خلال عمر العرض
}
إذا أعلن العرض الفرعي نفس ObservableObject عبر @StateObject، فإنه ينشئ نسخة مستقلة. لن تكون التغييرات في الكائن الأصلي مرئية في الفرعي، والعكس. استخدم دائمًا @ObservedObject للعروض الفرعية التي تستقبل الكائن من الخارج.
في SwiftUI الحديث، توجد عدة بدائل لـ @ObservedObject، كل منها له مزاياه. يعتمد الاختيار على هندسة التطبيق، ونسخة iOS، وحالة الاستخدام المحددة.
الاختيار بين @ObservedObject و @EnvironmentObject هو مسألة أسلوب وهندسة. @ObservedObject يظهر اعتمادات العرض صراحة عبر المهيئ، مما يجعل الكود أكثر قابلية للتنبؤ. @EnvironmentObject مفيد للتسلسلات الهرمية العميقة ولكنه يخفي الاعتمادات، مما قد يعقد التصحيح.
الأسئلة الشائعة
نعم، إذا تم إنشاء الكائن وتخزينه خارج SwiftUI — على سبيل المثال، في AppDelegate أو singleton. في هذه الحالة، @ObservedObject يشترك ببساطة في تغييرات كائن موجود. ومع ذلك، للكائنات المنشأة داخل تسلسل SwiftUI، يلزم @StateObject في مكان ما في الأعلى.
السبب الأكثر احتمالًا هو أن الخاصية تتغير ليس عبر @Published أو أن الكائن نفسه لا يتغير بل تتحور بنيته الداخلية دون استدعاء objectWillChange. للمجموعات، استخدم تعيين نسخة جديدة: array.append() لا يكفي — تحتاج إلى إعادة تعيين المصفوفة نفسها عبر array = array + [element].
@ObservedObject بحد ذاته لا يخلق عبءًا كبيرًا. تنشأ المشاكل مع التغييرات المتكررة لخصائص @Published — كل تغيير يحفز إعادة رسم جميع العروض المراقبة. للتحسين، استخدم EquatableView، قلل عدد الخصائص المنشورة وتجنب التحديثات غير الضرورية.
@ObservedObject يراقب فئة ObservableObject كاملة ويعيد رسم العرض عند أي تغيير في خصائصه المنشورة. @Binding ينشئ اتصالًا ثنائي الاتجاه بقيمة محددة (String, Int, Bool) ويسمح بقراءتها وكتابتها. @Binding أخف ولا يتطلب ObservableObject.
نعم، هذا هو النمط القياسي. @Published داخل ObservableObject يتكامل تلقائيًا مع @ObservedObject. كل خاصية @Published تضيف مراقبًا إلى ناشر objectWillChange. عند تغيير أي منها، يتم إعادة رسم جميع العروض التي تحتوي على @ObservedObject لهذا الكائن.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا