@StateObject هو property wrapper في SwiftUI يقوم بإنشاء وامتلاك مثيل ObservableObject طوال دورة حياة View بأكملها. عندما تظهر View لأول مرة على الشاشة، يقوم @StateObject بتهيئة الكائن وتخزينه حتى يتم إزالة View من الذاكرة. هذا يضمن عدم إعادة تعيين البيانات عند إعادة بناء الواجهة — على سبيل المثال، عند تغيير السمة أو تحديث View الأصل. وفقاً لوثائق مطوري Apple (2025)، يجب استخدام @StateObject كمصدر رئيسي للحقيقة (source of truth) لـ ObservableObject في تسلسل SwiftUI، بينما تتلقى Views الفرعية الكائن المنشأ بالفعل عبر @ObservedObject أو @EnvironmentObject.
الخلاصة
@StateObject هو property wrapper تم تقديمه في iOS 14 يسمح لـ View بإنشاء وامتلاك مثيل لفئة تتوافق مع بروتوكول ObservableObject. على عكس @State الذي يعمل مع أنواع القيم (structs)، فإن @StateObject مصمم لأنواع المرجع — الفئات التي يمكنها إخطار SwiftUI بالتغييرات في خصائصها.
عندما تستخدم View @StateObject var viewModel: MyViewModel، يقوم SwiftUI تلقائياً بإنشاء مثيل MyViewModel عند أول ظهور للـ View ويخزنه في مخزن خاص بالإطار. عند كل تحديث للـ View (على سبيل المثال، عندما تتغير حالة الأصل)، لا يقوم SwiftUI بإعادة إنشاء الكائن — بل يستخدم المثيل الحالي حتى تتم إزالة View من التسلسل الهرمي.
وفقاً لـ Apple WWDC Session 10137 (2024)، يحل @StateObject مشكلة فقدان البيانات التي كانت موجودة في iOS 13 عند إعادة بناء الـ Views، مما أجبر المطورين على إنشاء ObservableObject في View الأصل وتمريره عبر المُهيئ. أدى هذا إلى تكرار الكود وخطر إعادة إنشاء الكائن عن طريق الخطأ.
import SwiftUI
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct CounterView: View {
@StateObject var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("Count: \(viewModel.count)")
Button("Increment", action: viewModel.increment)
}
}
}
آلية @StateObject تعتمد على تكامل SwiftUI مع إطار Combine. عندما يضع ObservableObject علامة على خصائصه بالسمة @Published، يشترك SwiftUI تلقائياً في التغييرات عبر الناشر المدمج في بروتوكول ObservableObject. عندما تتغير خاصية منشورة، يرسل الكائن إشارة عبر ناشر objectWillChange، مما يؤدي إلى إعادة رسم جميع الـ Views التي تراقب هذا الكائن.
يخزن SwiftUI مثيل ObservableObject في مخزن خاص مرتبط بمثيل View معين. يتم إنشاء هذا المخزن مرة واحدة أثناء أول عرض ويوجد حتى يتم تدمير الـ View. لهذا السبب يضمن @StateObject استقرار المرجع — يدير SwiftUI الذاكرة تلقائياً، دون الاعتماد على مُهيئ الـ View.
وفقاً لـ objc.io — Thinking in SwiftUI (2025)، يستخدم التنفيذ الداخلي لـ @StateObject آلية مشابهة لـ @State ولكن لأنواع المرجع: يقوم SwiftUI بإنشاء غلاف (boxing) حول الكائن ويدير دورة حياته من خلال مخصص الذاكرة الخاص به، المُحسّن لإعادة بناء التسلسل الهرمي للـ Views بشكل متكرر.
الفرق الرئيسي بين @StateObject و @ObservedObject يكمن في من يملك الكائن. @StateObject ينشئ ويخزن الكائن — هو المالك. @ObservedObject يراقب فقط الكائن الذي تم إنشاؤه في مكان آخر وتم تمريره عبر المُهيئ أو الخاصية.
| الخاصية | @StateObject | @ObservedObject |
|---|---|---|
| الملكية | ينشئ ويمتلك الكائن | يراقب فقط |
| التهيئة | داخل الـ View عبر init/افتراضي | خارجية، تُمرر عبر معامل |
| دورة الحياة | مربوطة بدورة حياة الـ View | لا تتحكم بها الـ View |
| إعادة الإنشاء | لا يُعاد إنشاؤه عند التحديث | يمكن استبداله خارجياً |
| إصدار iOS | iOS 14+ | iOS 13+ |
القاعدة بسيطة: إذا كانت الـ View تنشئ ObservableObject — استخدم @StateObject. إذا كانت الـ View فقط تستلم كائنًا منشأً مسبقاً من الأصل — استخدم @ObservedObject. مخالفة هذه القاعدة تؤدي إما إلى فقدان البيانات (إذا تم استخدام @ObservedObject للملكية) أو إلى إنشاء مفرط للكائنات (إذا تم استخدام @StateObject للمراقبة).
يجب استخدام @StateObject في الـ Views التي تمثل مصدر الحقيقة لمجموعة محددة من البيانات. تشمل السيناريوهات النموذجية الشاشات التي تحتوي على view model خاص بها، والشاشات الجذرية لأكوام التنقل، والعروض التقديمية المشروطة التي تدير حالتها الخاصة.
struct ProfileView: View {
@StateObject var viewModel = ProfileViewModel()
var body: some View {
NavigationStack {
Form {
TextField("Name", text: $viewModel.name)
TextField("Email", text: $viewModel.email)
Button("Save") {
viewModel.saveProfile()
}
}
.navigationTitle("Profile")
}
}
}
تهيئة @StateObject مع معاملات تتطلب صيغة خاصة، حيث أن SwiftUI يدير إنشاء الكائن بنفسه. لا يمكنك ببساطة تمرير معاملات إلى المُهيئ — بل تحتاج إلى استخدام إغلاق هارب (escaping closure) أو طريقة مصنع منفصلة.
وفقاً لـ Swift by Sundell (2024)، أنظف طريقة هي استخدام طريقة مصنع أو إغلاق سيقوم SwiftUI باستدعائه عند إنشاء الكائن لأول مرة. الطريقة البديلة هي تهيئة ObservableObject في View الأصل وتمريره عبر @StateObject باستخدام المُهيئ القياسي.
class UserViewModel: ObservableObject {
@Published var user: User
init(user: User) {
self.user = user
}
}
struct UserDetailView: View {
@StateObject var viewModel: UserViewModel
init(user: User) {
_viewModel = StateObject(wrappedValue: UserViewModel(user: user))
}
var body: some View {
Text(viewModel.user.name)
}
}
من المهم تذكر أن مُهيئ View مع @StateObject يجب أن يستخدم شرطة سفلية قبل اسم الخاصية (_viewModel) للوصول إلى property wrapper نفسه، وليس قيمته. هذا نمط Swift قياسي للعمل مع property wrappers في المُهيئات.
الخطأ الأكثر شيوعاً هو استخدام @ObservedObject بدلاً من @StateObject لـ View التي يجب أن تمتلك الكائن. في هذه الحالة، في كل مرة يُعاد بناء الأصل، سيتم إعادة إنشاء الكائن، مما يؤدي إلى فقدان جميع البيانات المتراكمة. هذا الخطأ خطير بشكل خاص في التسلسلات الهرمية المعقدة مع NavigationStack أو TabView.
لتجنب هذه المشكلات، اتبع قاعدة بسيطة: @StateObject واحد لكل مصدر حقيقة. إذا كان يجب مشاركة البيانات عبر عدة شاشات — أنشئ @StateObject مرة واحدة في View الجذر ومرره عبر @ObservedObject أو @EnvironmentObject إلى العناصر الفرعية.
// ❌ Wrong: @ObservedObject for owning an object
struct BadView: View {
@ObservedObject var vm = ViewModel() // will be recreated on each update!
}
// ✅ Correct: @StateObject for owning
struct GoodView: View {
@StateObject var vm = ViewModel() // created once for View lifetime
}
الأسئلة الشائعة
@State يعمل مع أنواع القيم (structs، سلاسل نصية، أرقام) ويخزن القيمة مباشرة في مخزن SwiftUI. @StateObject يعمل مع أنواع المرجع — الفئات التي تتوافق مع ObservableObject. @State مناسب للحالات المحلية البسيطة، @StateObject للأشياء المعقدة مع المنطق والخصائص المنشورة.
لا، @StateObject متاح فقط من iOS 14 فما فوق. لـ iOS 13، استخدم @ObservedObject وأنشئ ObservableObject في View الأصل عبر @State مع إدارة يدوية لدورة الحياة. البديل هو استخدام @State مع struct بدلاً من class للبيانات التي لا تتطلب دلالات المرجع.
الـ View الفرعية ستنشئ نسختها الخاصة من ObservableObject، مستقلة تماماً عن نسخة الأصل. التغييرات في إحداهما لن تؤثر على الأخرى. هذا دائمًا خطأ تقريباً: استخدم @ObservedObject لاستقبال كائن من الأصل و @StateObject فقط لإنشاء كائن جديد داخل الـ View.
يتم تدمير الكائن عندما يتم إزالة الـ View التي أنشأته بالكامل من تسلسل SwiftUI. لشاشة في NavigationStack، يحدث هذا عند الخروج من مكدس التنقل. لنافذة مشروطة — عند إغلاقها. لـ TabView — عند تبديل التبويب، إذا لم تكن الـ View مخزنة مؤقتاً.
استخدم init مخصص مع الوصول إلى property wrapper عبر الشرطة السفلية: _viewModel = StateObject(wrappedValue: MyViewModel(param: value)). هذا النمط يسمح بتمرير أي معاملات إلى ObservableObject مع الحفاظ على ضمان إنشاء الكائن لمرة واحدة خلال عمر الـ View.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا