@ObservedObject: ما هو، مراقبة الكائنات وتحديث العروض

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

@ObservedObject هو property wrapper في SwiftUI يسمح للعرض بمراقبة التغييرات في ObservableObject منشأ في مكان آخر من التسلسل الهرمي. بخلاف @StateObject، فإن @ObservedObject لا ينشئ الكائن — إنه فقط يشترك في ناشره objectWillChange ويعيد رسم العرض عندما تتحدث الخصائص المنشورة. هذا يجعل @ObservedObject الاختيار الصحيح للعروض الفرعية التي تتلقى بيانات من الأصل عبر المهيئ. وفقًا لمقال بقلم Paul Hudson — Hacking with Swift (2025)، فإن الهندسة النموذجية لتطبيق SwiftUI تبنى كالآتي: يستخدم العرض الجذري @StateObject لإنشاء view model، وتستقبله جميع العروض الفرعية عبر @ObservedObject، مما يضمن مصدرًا واحدًا للحقيقة دون تكرار البيانات.

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

  • @ObservedObject — property wrapper لمراقبة ObservableObject منشأ في عرض أصلي.
  • لا يملك الكائن — بخلاف @StateObject، فإن @ObservedObject لا يدير دورة حياة الكائن.
  • الاشتراك في التغييرات — عند تغيير خصائص @Published، يتم إعادة رسم العرض تلقائيًا.
  • الإرسال عبر المهيئ — يتم إرسال الكائن إلى العرض الفرعي عبر معلمة المهيئ.
  • iOS 13+ — @ObservedObject متاح منذ أول إصدار من SwiftUI، بخلاف @StateObject (iOS 14+).

ما هو @ObservedObject في SwiftUI

@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

تعتمد آلية @ObservedObject على بروتوكول ObservableObject من إطار Combine. كل فئة تتوافق مع ObservableObject تحصل تلقائيًا على ناشر objectWillChange يرسل إشارة قبل أي تغيير في خاصية @Published. تشترك SwiftUI في هذا الناشر عبر @ObservedObject وعند استلام الإشارة تميز العرض بأنه يحتاج إلى إعادة رسم.

swift
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: متى نستخدم كلًا

الفرق بين @ObservedObject و @StateObject هو الفرق بين مراقب ومالك. @StateObject ينشئ الكائن ويدير دورة حياته. @ObservedObject يراقب فقط كائنًا تم إنشاؤه وتخزينه في مكان آخر. يتم تحديد الاختيار بينهما بناءً على مسؤولية العرض عن البيانات.

السيناريوالتوصيةالسبب
العرض ينشئ بيانات@StateObjectالعرض يملك الكائن ويتحمل مسؤولية دورة حياته
العرض يتلقى بيانات@ObservedObjectالعرض يراقب فقط، الكائن يعيش في الأصل
دعم iOS 13@ObservedObject@StateObject غير متاح، استخدم @ObservedObject مع إدارة يدوية
مكون قابل لإعادة الاستخدام@ObservedObjectالمكون لا يجب أن ينشئ بيانات — يستقبلها من الخارج

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

أمثلة استخدام @ObservedObject

سيناريو استخدام نموذجي لـ @ObservedObject هو قائمة مهام حيث ينشئ العرض الجذري view model وتستقبله كل خلية في القائمة عبر @ObservedObject. يمكن لكل خلية استدعاء طرق view model، وتنعكس التغييرات تلقائيًا عبر القائمة بأكملها لأن جميع الخلايا تراقب نفس الكائن.

swift
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

أكثر الأخطاء شيوعًا هو استخدام @ObservedObject لإنشاء كائن داخل العرض. عندما يتم إعادة بناء العرض (على سبيل المثال، عند تغيير الحالة)، ينشئ SwiftUI مثالًا جديدًا ObservableObject، مما يؤدي إلى فقدان جميع البيانات المتراكمة. هذا الخطأ مؤلم بشكل خاص في NavigationStack، حيث قد يملأ المستخدم نموذجًا ويفقد البيانات عند العودة.

خطأ: @ObservedObject بدلاً من @StateObject

swift
// ❌ فقدان البيانات: @ObservedObject لا يحتفظ بالكائن
struct FormView: View {
    @ObservedObject var formVM = FormViewModel()
    // تم إنشاء formVM جديد عند كل إعادة بناء للعرض!
}

// ✅ صحيح: @StateObject يحتفظ بالكائن
struct FormView: View {
    @StateObject var formVM = FormViewModel()
    // تم إنشاء الكائن مرة واحدة خلال عمر العرض
}

خطأ: تمرير @StateObject حيث يلزم @ObservedObject

إذا أعلن العرض الفرعي نفس ObservableObject عبر @StateObject، فإنه ينشئ نسخة مستقلة. لن تكون التغييرات في الكائن الأصلي مرئية في الفرعي، والعكس. استخدم دائمًا @ObservedObject للعروض الفرعية التي تستقبل الكائن من الخارج.

بدائل @ObservedObject في SwiftUI

في SwiftUI الحديث، توجد عدة بدائل لـ @ObservedObject، كل منها له مزاياه. يعتمد الاختيار على هندسة التطبيق، ونسخة iOS، وحالة الاستخدام المحددة.

  • @EnvironmentObject — يسمح بالحصول على كائن من بيئة SwiftUI دون تمريره صراحة عبر المهيئ. مفيد للكائنات التي تحتاجها عدة شاشات، ولكنه يتطلب حقناً صريحًا عبر .environmentObject().
  • @State + @Binding — لأنواع القيم البسيطة، لا حاجة ObservableObject. استخدم @State للتخزين و @Binding للتمرير إلى العروض الفرعية.
  • @AppStorage — لقيم UserDefaults التي يجب أن تتزامن تلقائيًا مع العرض.
  • @SceneStorage — للحفاظ على الحالة المؤقتة بين إعادات تشغيل المشهد (على سبيل المثال، موقع التمرير في قائمة).

الاختيار بين @ObservedObject و @EnvironmentObject هو مسألة أسلوب وهندسة. @ObservedObject يظهر اعتمادات العرض صراحة عبر المهيئ، مما يجعل الكود أكثر قابلية للتنبؤ. @EnvironmentObject مفيد للتسلسلات الهرمية العميقة ولكنه يخفي الاعتمادات، مما قد يعقد التصحيح.

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

هل يمكن استخدام @ObservedObject بدون @StateObject في العرض الأصلي؟

نعم، إذا تم إنشاء الكائن وتخزينه خارج SwiftUI — على سبيل المثال، في AppDelegate أو singleton. في هذه الحالة، @ObservedObject يشترك ببساطة في تغييرات كائن موجود. ومع ذلك، للكائنات المنشأة داخل تسلسل SwiftUI، يلزم @StateObject في مكان ما في الأعلى.

لماذا لا يقوم @ObservedObject بتحديث العرض أحيانًا؟

السبب الأكثر احتمالًا هو أن الخاصية تتغير ليس عبر @Published أو أن الكائن نفسه لا يتغير بل تتحور بنيته الداخلية دون استدعاء objectWillChange. للمجموعات، استخدم تعيين نسخة جديدة: array.append() لا يكفي — تحتاج إلى إعادة تعيين المصفوفة نفسها عبر array = array + [element].

هل يؤثر @ObservedObject على الأداء؟

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

ما الفرق بين @ObservedObject و @Binding؟

@ObservedObject يراقب فئة ObservableObject كاملة ويعيد رسم العرض عند أي تغيير في خصائصه المنشورة. @Binding ينشئ اتصالًا ثنائي الاتجاه بقيمة محددة (String, Int, Bool) ويسمح بقراءتها وكتابتها. @Binding أخف ولا يتطلب ObservableObject.

هل يمكن الجمع بين @ObservedObject و @Published في نفس الفئة؟

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

الملخص

  • @ObservedObject — property wrapper لمراقبة ObservableObject منشأ في مكان آخر من التسلسل الهرمي.
  • لا يملك الكائن — بخلاف @StateObject، @ObservedObject لا يدير دورة الحياة ولا ينشئ الكائن.
  • الاشتراك عبر Combine — SwiftUI تشترك تلقائيًا في ناشر objectWillChange لـ ObservableObject.
  • iOS 13+ — @ObservedObject متاح منذ أول إصدار من SwiftUI، مهم للمشاريع ذات الدعم القديم.
  • الإرسال عبر المهيئ — يتم إرسال الكائن صراحة إلى العرض الفرعي، مما يجعل الاعتمادات شفافة.
  • خطأ الملكية — استخدام @ObservedObject لإنشاء كائن يؤدي إلى فقدان البيانات عند إعادة بناء العرض.
  • بدائل — @EnvironmentObject للحقن عبر البيئة، @State/@Binding لأنواع القيم.

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

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

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

اقرأ أيضًا