Property Wrapper: المفاهيم الأساسية، بناء الجملة والأمثلة في SwiftUI

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

Property Wrapper هي آلية في Swift تضيف طبقة من المنطق إلى الوصول إلى الخصائص وتعديلها دون تكرار الكود. في SwiftUI، أصبحت Property Wrappers أساس إدارة حالة طرق العرض: @State و @Binding و @ObservedObject و @StateObject و @Environment. وفقاً لوثائق Swift (2025)، تقلل أغلفة الخصائص من الكود النمطي في المشاريع بمتوسط 40%. فهم Property Wrapper ضروري لكل مطور iOS للعمل بفعالية مع الإطار.

الخلاصة

  • Property Wrapper — تركيب نحوي في Swift يضيف منطقاً إلى getter و setter الخاصية
  • @State — غلاف للحالة المحلية التابعة لطريقة عرض واحدة
  • @Binding — اتصال ثنائي الاتجاه بمصدر بيانات من طريقة العرض الأم
  • @ObservedObject — مراقبة ObservableObject خارجي بدون ملكية
  • @StateObject — إنشاء وامتلاك ObservableObject على مستوى طريقة العرض الحالية

ما هو Property Wrapper؟

Property Wrapper — تركيب من لغة Swift، ظهر في الإصدار 5.1، يسمح بتغليف منطق الوصول إلى الخاصية في نوع منفصل. بدلاً من كتابة getters و setters متكررة في كل كلاس، يعلن المطور الغلاف مرة واحدة ويطبقه عبر التعليمة @ قبل النوع. يقوم Swift تلقائياً بلف الخاصية في النوع المحدد، مستدعياً طريقتي wrappedValue و projectedValue عند القراءة والكتابة. وفقاً لـ Apple (WWDC 2019)، أصبحت Property Wrappers تجريداً أساسياً لـ SwiftUI.

غلاف الخاصية هو structure أو class مع السمة @propertyWrapper. داخلياً، يجب على هذا النوع تنفيذ الخاصية wrappedValue، التي تُرجع وتُعيّن القيمة الفعلية. يقوم مُجمّع Swift باستبدال الوصول إلى الخاصية الأصلية باستدعاءات wrappedValue، مخفياً تماماً التنفيذ عن الكود المستدعي. بالإضافة إلى ذلك، يمكن تعريف projectedValue — إسقاط يمكن الوصول إليه عبر الرمز $.

ميزة Property Wrappers تكمن في إعادة الاستخدام للمنطق. على سبيل المثال، يمكنك إنشاء غلاف للتحقق من صحة البريد الإلكتروني، أو تخزين القيم مؤقتاً، أو المزامنة مع وحدة التخزين — وتطبيقه على أي خاصية في المشروع. في SwiftUI، يُستخدم هذا المفهوم في كل مكان: كل آلية لإدارة الحالة تُنفذ كـ Property Wrapper منفصل.

كيف يعمل Property Wrapper في Swift؟

عند تعريف خاصية بالتعليمة @WrapperType var value: T، يقوم مُجمّع Swift بتحويل الكود. ينشئ مثيلاً من WrapperType ويُولّد الوصول إلى الخاصية عبر wrappedValue. الكود المصدري let x = value يتحول إلى let x = _value.wrappedValue، و value = newValue يتحول إلى _value.wrappedValue = newValue. هذا التحويل يحدث في وقت التجميع، بدون أي حمل إضافي في وقت التشغيل.

swift
@propertyWrapper
struct Capitalized {
    private var text: String

    var wrappedValue: String {
        get { text }
        set { text = newValue.capitalized }
    }

    init(initialValue: String) {
        text = initialValue.capitalized
    }
}

يُظهر المثال الغلاف Capitalized، الذي يحول السلسلة تلقائياً إلى تنسيق بحرف كبير. عند تعيين قيمة، يستدعي setter الدالة capitalized قبل الحفظ. الآن أي خاصية بالتعليمة @Capitalized ستخزن فقط نصاً منسقاً بشكل صحيح. هذا النهج يلغي تماماً تكرار كود التحقق والتنسيق.

الإسقاط (projectedValue) — قناة اتصال إضافية يمكن الوصول إليها عبر البادئة $. في SwiftUI، تُستخدم هذه الميزة في كل مكان: $state يُعطي Binding، و $observedObject يُرجع Published.Publisher. يسمح الإسقاط للغلاف بتوفير واجهة موسعة بدون تغيير الوصول الأساسي إلى القيمة.

أهم Property Wrappers في SwiftUI

يتضمن SwiftUI خمسة Property Wrappers مدمجة لإدارة الحالة: @State و @Binding و @ObservedObject و @StateObject و @Environment. كل منها يحل مهمة محددة ويُستخدم في سيناريوهات مختلفة. @State مصمم للبيانات المحلية البسيطة، @Binding — لتمرير مرجع إلى البيانات لطرق العرض التابعة، @ObservedObject و @StateObject — للكائنات المعقدة، @Environment — للقيم النظامية من التسلسل الهرمي.

الغلافالغرضالملكية
@Stateالحالة المحلية لطريقة عرض واحدةطريقة العرض الحالية
@Bindingاتصال ثنائي الاتجاه مع الأصلطريقة العرض الأم
@ObservedObjectمراقبة كائن خارجيالمالك الخارجي
@StateObjectإنشاء ObservableObjectطريقة العرض الحالية
@Environmentقيم نظامية من التسلسل الهرميبيئة SwiftUI

يعتمد اختيار Property Wrapper المحدد على مصدر البيانات ودورة حياتها. إذا كانت البيانات تنتمي لطريقة عرض واحدة ولا تحتاجها المكونات التابعة — استخدم @State. إذا كانت طريقة العرض التابعة تحتاج إلى تعديل بيانات الأصل — استخدم @Binding. للكائنات المستخدمة في عدة طرق عرض، يناسب @ObservedObject و @StateObject.

@State: الحالة المحلية لطريقة العرض

@State — Property Wrapper لتخزين الحالة المحلية داخل طريقة عرض واحدة. يدير SwiftUI الذاكرة تلقائياً لخصائص @State ويعيد رسم طريقة العرض عند كل تغيير. @State مناسب للأنواع البسيطة (String, Int, Bool, enum) والهياكل التي تنتمي حصرياً لطريقة العرض الحالية. عندما تتغير القيمة، يعيد SwiftUI تشغيل الخاصية body.

swift
struct CounterView: View {
    @State private var count: Int = 0

    var body: some View {
        VStack {
            Text("العدد: \(count)")
            Button("زيادة") {
                count += 1
            }
        }
    }
}

في المثال، خاصية @State count تخزن القيمة الحالية للعداد. ينشئ SwiftUI منطقة تخزين لهذه الخاصية في heap ويربطها بدورة حياة CounterView. عند الضغط على الزر، تزيد count بمقدار 1، يكتشف SwiftUI التغيير ويعيد تشغيل body، عارضاً القيمة الجديدة. مهم: @State لا يجب استخدامه للأنواع المرجعية المعقدة — لهذا صُمم @StateObject و @ObservedObject.

@Binding: اتصال ثنائي الاتجاه بين طرق العرض

@Binding ينشئ مرجعاً لمصدر بيانات ينتمي لطريقة عرض أخرى. Binding لا يخزن قيمة بنفسه — يقرأ ويكتب البيانات عبر @State أو @StateObject أو Binding آخر مُمرر من الأصل. هذا يسمح للمكونات التابعة بتعديل حالة السلف بدون امتلاك البيانات مباشرة وبدون استدعاءات回调.

swift
struct ToggleSwitch: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Switch", isOn: $isOn)
    }
}

في المثال، ToggleSwitch يستقبل @BindingBool من طريقة العرض الأم. ينشئ الأصل @State var isToggleOn = false ويمرر $isToggleOn إلى مُهيئ ToggleSwitch. عندما يبدل المستخدم المفتاح داخل طريقة العرض التابعة، ينعكس التغيير فوراً في @State الخاص بالأصل. آلية Binding تلغي تماماً الحاجة إلى المفوضين أو closures لتمرير التغييرات لأعلى التسلسل الهرمي.

@ObservedObject و @StateObject: البيانات الخارجية

@ObservedObject — Property Wrapper لمراقبة مثيل ObservableObject مُمرر من الخارج. طريقة العرض لا تمتلك هذا الكائن — يُنشأ في المكون الأم أو يُحقن عبر Environment. عندما تتغير أي خاصية @Published داخل ObservableObject، يعيد SwiftUI رسم جميع طرق العرض المشتركة عبر @ObservedObject.

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

swift
class UserSettings: ObservableObject {
    @Published var username: String = "Guest"
}

struct ProfileView: View {
    @StateObject var settings = UserSettings()

    var body: some View {
        ChildProfileView(settings: settings)
    }
}

struct ChildProfileView: View {
    @ObservedObject var settings: UserSettings

    var body: some View {
        Text("مرحباً، \(settings.username)")
    }
}

في المثال، ProfileView ينشئ UserSettings عبر @StateObject، ليصبح مالك الكائن. ChildProfileView يستقبل نفس المثيل عبر @ObservedObject — يراقب لكنه لا يدير دورة الحياة. عندما يتغير username، يتم تحديث كلا العرضين. إذا استخدم ChildProfileView @StateObject بدلاً من @ObservedObject، فسيتم إنشاء مثيل جديد بالقيمة الأولية عند كل إعادة渲染.

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

إنشاء Property Wrapper مخصص

يسمح Swift بإنشاء Property Wrappers مخصصة لأي منطق وصول متكرر إلى الخصائص. يكفي تعريف structure أو class بالسمة @propertyWrapper وتنفيذ wrappedValue. أدناه يُعرض غلاف UserDefaultsWrapper، الذي يزامن القيمة تلقائياً مع UserDefaults.

swift
@propertyWrapper
struct UserDefaultsWrapper<T> {
    let key: String
    let defaultValue: T

    var wrappedValue: T {
        get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue }
        set { UserDefaults.standard.set(newValue, forKey: key) }
    }
}

struct AppConfig {
    @UserDefaultsWrapper(key: "theme", defaultValue: "light")
    var theme: String
}

يستخدم الغلاف UserDefaultsWrapper عام T للعمل مع أي نوع بيانات مدعوم من UserDefaults. يقرأ getter القيمة بالمفتاح، ويكتبها setter. تطبيق @UserDefaultsWrapper(key:defaultValue:) على الخاصية theme يربطها تلقائياً بالتخزين — كل منطق UserDefaults مخفي داخل الغلاف. هذا مثال نموذجي لتقليل الكود النمطي باستخدام Property Wrappers.

عند إنشاء أغلفة مخصصة، من المهم مراعاة الأداء. نظراً لأنه يتم استدعاء getter و setter في كل وصول إلى الخاصية، لا ينبغي وضع عمليات إدخال/إخراج ثقيلة في wrappedValue. للتخزين غير المتزامن للبيانات، من الأفضل دمج Property Wrappers مع ObservableObject و @Published.

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

ما الفرق بين @State و @StateObject؟

@State مصمم للأنواع البسيطة (String, Int, Bool) والهياكل، بينما @StateObject للأنواع المرجعية التي تطبق ObservableObject. @State يخزن القيمة مباشرة في SwiftUI، @StateObject يدير مثيل كلاس في heap.

هل يمكن استخدام @Binding بدون @State؟

نعم، @Binding يمكن إنشاؤه من @StateObject أو @ObservedObject أو من Binding آخر باستخدام الإسقاط $. كما يمكن تهيئة Binding من ObservableObject عبر $object.$publishedProperty أو من InlineBinding عبر Binding.constant(value).

أي Property Wrapper تختار للبيانات المستخدمة في جميع أنحاء التطبيق؟

للبيانات العامة، استخدم @EnvironmentObject أو حقن ObservableObject عبر EnvironmentValues. @StateObject مناسب لطريقة العرض الجذر مع النقل اللاحق عبر @ObservedObject للمكونات التابعة.

لماذا قد يعيد @ObservedObject إنشاء البيانات؟

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

هل يمكن إنشاء Property Wrapper للعمليات غير المتزامنة؟

نعم، لكن من الأسهل استخدام مزيج من ObservableObject مع @Published ودوال غير متزامنة داخل الكلاس. Property Wrapper متزامن بطبيعته — wrappedValue يُحسب عند كل وصول، مما لا يناسب العمليات الطويلة.

الملخص

  • Property Wrapper — تركيب نحوي من Swift 5.1 لتغليف منطق الوصول إلى الخصائص بدون تكرار الكود
  • @State — حالة محلية للأنواع البسيطة، تعيد رسم طريقة العرض تلقائياً عند التغيير
  • @Binding — مرجع لبيانات طريقة العرض الأم بدون ملكية، يمكن الوصول إليه عبر الإسقاط $
  • @StateObject — ملكية ObservableObject مع ضمان مثيل واحد طوال دورة الحياة
  • @ObservedObject — مراقبة ObservableObject المُمرر من المكون الأم
  • @Environment — الوصول إلى كائنات بيئة النظام في SwiftUI
  • الأغلفة المخصصة تُنشأ عبر السمة @propertyWrapper وتنفيذ wrappedValue

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

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

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

اقرأ أيضًا