Property Wrapper: مفاهیم کلیدی، نحو و مثال‌ها در SwiftUI

نویسنده: IT Sectr منتشر شده: 2026-06-19 زمان مطالعه: 8 دقیقه

Property Wrapper مکانیزمی در Swift است که لایه‌ای از منطق را به دسترسی و تغییر ویژگی‌ها بدون تکرار کد اضافه می‌کند. در SwiftUI، Property Wrapperها به پایه مدیریت وضعیت نمایش‌ها تبدیل شده‌اند: @State، @Binding، @ObservedObject، @StateObject و @Environment. طبق مستندات Swift (2025)، wrapperهای ویژگی کدهای تکراری را در پروژه‌ها به طور متوسط ۴۰٪ کاهش می‌دهند. درک Property Wrapper برای هر توسعه‌دهنده iOS برای کار مؤثر با فریم‌ورک ضروری است.

نکات اصلی

  • Property Wrapper — ساختار نحوی Swift که به getter و setter ویژگی منطق اضافه می‌کند
  • @State — wrapper برای وضعیت محلی متعلق به یک view
  • @Binding — ارتباط دوطرفه با منبع داده از view والد
  • @ObservedObject — مشاهده ObservableObject خارجی بدون مالکیت
  • @StateObject — ایجاد و مالکیت ObservableObject در سطح view فعلی

Property Wrapper چیست؟

Property Wrapper ساختاری زبانی در Swift است که در نسخه ۵٫۱ معرفی شد و امکان کپسوله‌سازی منطق دسترسی به ویژگی را در یک نوع مجزا فراهم می‌کند. به جای نوشتن getter و setterهای تکراری در هر کلاس، توسعه‌دهنده یکبار wrapper را اعلام کرده و آن را از طریق annotation @ قبل از نوع اعمال می‌کند. Swift به طور خودکار ویژگی را در نوع مشخص شده می‌پیچد و هنگام خواندن و نوشتن متدهای wrappedValue و projectedValue آن را فراخوانی می‌کند. به گفته Apple (WWDC 2019)، Property Wrapperها به انتزاع اصلی برای SwiftUI تبدیل شده‌اند.

wrapper ویژگی یک struct یا class با attribute @propertyWrapper است. در داخل، چنین نوعی باید ویژگی wrappedValue را پیاده‌سازی کند که مقدار فعلی را بازگردانده و تنظیم می‌کند. کامپایلر Swift ارجاعات به ویژگی اصلی را با فراخوانی wrappedValue جایگزین می‌کند و پیاده‌سازی را کاملاً از کد فراخوان پنهان می‌کند. علاوه بر این می‌توان projectedValue را تعریف کرد — پروجکشنی که از طریق نماد $ قابل دسترسی است.

مزیت Property Wrapperها در استفاده مجدد منطق است. برای مثال می‌توان wrapper برای اعتبارسنجی ایمیل، ذخیره‌سازی مقادیر یا همگام‌سازی با حافظه ایجاد کرد — و آن را به هر ویژگی در پروژه اعمال کرد. در SwiftUI این مفهوم در همه جا استفاده می‌شود: هر مکانیزم مدیریت وضعیت به عنوان یک Property Wrapper جداگانه پیاده‌سازی شده است.

Property Wrapper در Swift چگونه کار می‌کند؟

هنگام اعلام ویژگی با annotation @WrapperType var value: T کامپایلر Swift کد را تبدیل می‌کند. نمونه‌ای از WrapperType ایجاد کرده و دسترسی به ویژگی را از طریق wrappedValue تولید می‌کند. کد منبع let x = value به let x = _value.wrappedValue و value = newValue به _value.wrappedValue = newValue تبدیل می‌شود. این تبدیل در مرحله کامپایل بدون سربار run-time انجام می‌شود.

swift
@propertyWrapper
struct Capitalized {
    private var text: String

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

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

در لیستینگ wrapper Capitalized نشان داده شده است که به طور خودکار متن را با حرف بزرگ فرمت می‌کند. هنگام تخصیص مقدار، setter قبل از ذخیره capitalized را فراخوانی می‌کند. اکنون هر ویژگی با annotation @Capitalized فقط متن با فرمت صحیح را ذخیره می‌کند. این رویکرد تکرار کد اعتبارسنجی و فرمت‌بندی را کاملاً حذف می‌کند.

پروجکشن (projectedValue) — کانال ارتباطی اضافی قابل دسترسی از طریق پیشوند $ است. در SwiftUI این قابلیت در همه جا استفاده می‌شود: $state یک Binding می‌دهد و $observedObject یک Published.Publisher برمی‌گرداند. پروجکشن به wrapper اجازه می‌دهد بدون تغییر دسترسی اصلی به مقدار، رابط توسعه‌یافته‌ای ارائه دهد.

Property Wrapperهای اصلی در SwiftUI

SwiftUI شامل پنج Property Wrapper داخلی برای مدیریت وضعیت است: @State، @Binding، @ObservedObject، @StateObject و @Environment. هر کدام وظیفه خاصی را حل کرده و در سناریوهای مختلف استفاده می‌شوند. @State برای داده‌های محلی ساده، @Binding برای ارسال ارجاع به داده‌ها در viewهای فرزند، @ObservedObject و @StateObject برای اشیاء پیچیده، @Environment برای مقادیر سیستمی از سلسله‌مراتب.

Wrapperکاربردمالکیت
@Stateوضعیت محلی یک viewview فعلی
@Bindingارتباط دوطرفه با والدview والد
@ObservedObjectمشاهده شیء خارجیمالک خارجی
@StateObjectایجاد ObservableObjectview فعلی
@Environmentمقادیر سیستمی از سلسله‌مراتبمحیط SwiftUI

انتخاب Property Wrapper خاص به منبع داده و چرخه حیات آنها بستگی دارد. اگر داده متعلق به یک view است و به اجزای فرزند نیاز ندارد — از @State استفاده کنید. اگر view فرزند نیاز به تغییر داده‌های والد دارد — @Binding را اعمال کنید. برای اشیایی که در چندین view استفاده می‌شوند @ObservedObject و @StateObject مناسب هستند.

@State: وضعیت محلی نمایش

@State Property Wrapper برای ذخیره وضعیت محلی درون یک view است. SwiftUI به طور خودکار حافظه را برای ویژگی‌های @State مدیریت کرده و view را با هر تغییر دوباره ترسیم می‌کند. @State برای انواع ساده (String, Int, Bool, enum) و structهایی که منحصراً به view فعلی تعلق دارند مناسب است. با تغییر مقدار، 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 یک واحد افزایش می‌یابد، SwiftUI تغییر را تشخیص داده و body را دوباره اجرا کرده و مقدار جدید را نمایش می‌دهد. مهم: @State نباید برای انواع ارجاعی پیچیده استفاده شود — برای این منظور @StateObject و @ObservedObject طراحی شده‌اند.

@Binding: ارتباط دوطرفه بین viewها

@Binding ارجاعی به منبع داده متعلق به view دیگر ایجاد می‌کند. Binding مقدار را به طور مستقل ذخیره نمی‌کند — از طریق @State، @StateObject یا Binding دیگری که از والد ارسال شده داده‌ها را می‌خواند و می‌نویسد. این به اجزای فرزند اجازه می‌دهد وضعیت والد را بدون مالکیت مستقیم داده و بدون callback تغییر دهند.

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

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

در لیستینگ ToggleSwitch یک @BindingBool از view والد دریافت می‌کند. والد @State var isToggleOn = false ایجاد کرده و $isToggleOn را به سازنده ToggleSwitch ارسال می‌کند. وقتی کاربر Toggle را درون view فرزند تغییر می‌دهد، تغییر بلافاصله در @State والد منعکس می‌شود. مکانیزم Binding نیاز به delegateها یا closureها برای انتقال تغییرات به بالای سلسله‌مراتب را کاملاً حذف می‌کند.

@ObservedObject و @StateObject: داده‌های خارجی

@ObservedObject Property Wrapper برای مشاهده نمونه ObservableObject ارسال شده از خارج است. view مالک این شیء نیست — در کامپوننت والد ایجاد شده یا از طریق Environment تزریق می‌شود. وقتی هر ویژگی @Published درون ObservableObject تغییر می‌کند، SwiftUI تمام viewهای مشترک از طریق @ObservedObject را دوباره ترسیم می‌کند.

@StateObject wrapper برای ایجاد و مالکیت ObservableObject مستقیماً در view است. بر خلاف @ObservedObject، @StateObject یک نمونه منحصر به فرد از شیء را در کل چرخه حیات view تضمین می‌کند. حتی اگر SwiftUI ساختار view را دوباره ایجاد کند (که اغلب اتفاق می‌افتد)، @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 هر دو view به‌روز می‌شوند. اگر ChildProfileView به جای @ObservedObject از @StateObject استفاده می‌کرد، در هر رندر نمونه جدیدی با مقدار اولیه ایجاد می‌شد.

قاعده کلیدی: @StateObject در viewای که شیء را ایجاد می‌کند (منبع حقیقت) و @ObservedObject در viewای که شیء آماده را از والد دریافت می‌کند اعمال می‌شود. نقض این قاعده منجر به از دست رفتن وضعیت یا بازآفرینی غیرمنتظره داده‌ها می‌شود.

ایجاد Property Wrapper سفارشی

Swift امکان ایجاد Property Wrapperهای سفارشی را برای هر منطق تکراری دسترسی به ویژگی فراهم می‌کند. کافی است struct یا class با attribute @propertyWrapper اعلام کرده و wrappedValue را پیاده‌سازی کنید. در زیر wrapper 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
}

wrapper UserDefaultsWrapper از generic T برای کار با هر نوع داده پشتیبانی شده توسط UserDefaults استفاده می‌کند. Getter مقدار را با کلید می‌خواند، setter می‌نویسد. اعمال @UserDefaultsWrapper(key:defaultValue:) به ویژگی theme به طور خودکار آن را به حافظه متصل می‌کند — تمام منطق کار با UserDefaults درون wrapper پنهان شده است. این یک مثال معمولی از کاهش کد تکراری با Property Wrapperهاست.

هنگام ایجاد wrapperهای سفارشی باید عملکرد را در نظر گرفت. از آنجا که getter و setter در هر دسترسی به ویژگی فراخوانی می‌شوند، نباید عملیات سنگین I/O را در wrappedValue قرار داد. برای ذخیره‌سازی ناهمگام داده‌ها بهتر است Property Wrapperها را با ObservableObject و @Published ترکیب کرد.

سوالات متداول

تفاوت بین @State و @StateObject چیست؟

@State برای انواع ساده (String, Int, Bool) و structها طراحی شده است، در حالی که @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 برای view ریشه با انتقال بعدی از طریق @ObservedObject به اجزای فرزند مناسب است.

چرا @ObservedObject ممکن است داده‌ها را بازآفرینی کند؟

@ObservedObject مالک شیء نیست — اگر view والد دوباره ایجاد شده و نمونه جدیدی ارسال کند، @ObservedObject به آن سوئیچ می‌کند. برای جلوگیری از از دست رفتن وضعیت، view مالک باید از @StateObject استفاده کند.

آیا می‌توان Property Wrapper برای عملیات ناهمگام ایجاد کرد؟

بله، اما ساده‌تر استفاده از ترکیب ObservableObject با @Published و توابع async درون کلاس است. Property Wrapper ذاتاً همگام است — wrappedValue در هر دسترسی محاسبه می‌شود که برای عملیات طولانی مناسب نیست.

خلاصه

  • Property Wrapper — ساختار نحوی Swift 5.1 برای کپسوله‌سازی منطق دسترسی به ویژگی بدون تکرار کد
  • @State — وضعیت محلی برای انواع ساده، به طور خودکار view را با تغییر دوباره ترسیم می‌کند
  • @Binding — ارجاع به داده‌های view والد بدون مالکیت، قابل دسترسی از طریق پروجکشن $
  • @StateObject — مالکیت ObservableObject با تضمین نمونه منحصر به فرد در چرخه حیات
  • @ObservedObject — مشاهده ObservableObject ارسال شده از کامپوننت والد
  • @Environment — دسترسی به اشیاء سیستمی محیط SwiftUI
  • wrapperهای سفارشی از طریق attribute @propertyWrapper و پیاده‌سازی wrappedValue ایجاد می‌شوند

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید