@EnvironmentObject: چیست، تزریق وابستگی‌ها و دسترسی به داده‌ها

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

@EnvironmentObject — یک property wrapper در SwiftUI است که به هر View در سلسله‌مراتب اجازه می‌دهد بدون ارسال صریح از طریق زنجیره مقداردهی اولیه به ObservableObject دسترسی پیدا کند. شیء با استفاده از اصلاح‌کننده .environmentObject() در سطح مشخصی از سلسله‌مراتب به محیط تزریق می‌شود و پس از آن همه Viewهای فرزند می‌توانند آن را از طریق @EnvironmentObject دریافت کنند. این کار نیاز به ارسال شیء از طریق Viewهای میانی که از آن استفاده نمی‌کنند را از بین می‌برد — به اصطلاح prop drilling. بر اساس مقاله John Sundell — Swift by Sundell (2025)، @EnvironmentObject به ویژه برای داده‌های بین صفحه‌ای مفید است: نشست کاربر، تنظیمات برنامه، مدیریت سبد خرید یا حافظه نهان محلی داده‌ها.

نکات اصلی

  • @EnvironmentObject — property wrapper برای دسترسی به ObservableObject از محیط SwiftUI.
  • تزریق از طریق .environmentObject() — شیء یک بار به سلسله‌مراتب ارسال می‌شود و برای همه Viewهای فرزند قابل دسترسی است.
  • بدون ارسال صریح — Viewهای میانی نیازی به دانستن درباره شیء ندارند، که معماری را ساده می‌کند.
  • Runtime crash — اگر شیء در محیط پیدا نشود، برنامه با fatal error سقوط می‌کند.
  • iOS 13+ — @EnvironmentObject از اولین نسخه SwiftUI در دسترس است.

@EnvironmentObject در SwiftUI چیست

@EnvironmentObject — یک property wrapper است که به SwiftUI View اجازه می‌دهد از محیط (environment) برنامه به ObservableObject دسترسی پیدا کند. محیط یک ظرف است که می‌توانید اشیاء را با استفاده از اصلاح‌کننده .environmentObject() در هر سطحی از سلسله‌مراتب View در آن قرار دهید. پس از قرار دادن شیء در محیط، هر View فرزند می‌تواند با اعلام یک ویژگی با @EnvironmentObject و مشخص کردن نوع شیء به آن دسترسی پیدا کند.

وظیفه اصلی @EnvironmentObject — حل مشکل ارسال داده‌ها از طریق سلسله‌مراتب عمیق View بدون نیاز به ارسال شیء از هر سطح میانی است. در برنامه‌های پیچیده با ساختار شاخه‌ای NavigationStack، TabView و پنجره‌های مدال، @EnvironmentObject معماری را با حذف کدهای تکراری به طور قابل توجهی ساده می‌کند.

بر اساس Apple Developer Documentation — Environment (2025)، @EnvironmentObject از مکانیزم داخلی SwiftUI مبتنی بر PreferenceKey و شناسایی View استفاده می‌کند. هر View یک ارجاع به محیط خود ذخیره می‌کند که از View والد به ارث می‌رسد و می‌تواند با .environmentObject() گسترش یابد. جستجوی شیء در سلسله‌مراتب به سمت بالا تا View ریشه انجام می‌شود.

swift
class UserSession: ObservableObject {
    @Published var isLoggedIn = false
    @Published var userName: String = ""
    
    func login(name: String) {
        userName = name
        isLoggedIn = true
    }
}

@main
struct MyApp: App {
    @StateObject var session = UserSession()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(session)
        }
    }
}

@EnvironmentObject چگونه کار می‌کند

@EnvironmentObject بر اساس مکانیزم تزریق وابستگی (DI) تعبیه‌شده در SwiftUI کار می‌کند. هنگامی که .environmentObject() را روی یک View فراخوانی می‌کنید، SwiftUI شیء را در ذخیره‌گاه خاصی مرتبط با این View و همه فرزندانش ذخیره می‌کند. وقتی یک View فرزند @EnvironmentObject از همان نوع را اعلام می‌کند، SwiftUI با بالا رفتن از سلسله‌مراتب والدین، شیء را در محیط جستجو می‌کند.

ویژگی مهم — نوع شیء به عنوان کلید جستجو در محیط استفاده می‌شود. اگر دو شیء از یک نوع در محیط وجود داشته باشند، SwiftUI نزدیک‌ترین را به View فعلی در سلسله‌مراتب پیدا خواهد کرد. با تزریق شیء در سطح WindowGroup، برای همه صفحه‌های برنامه به صورت سراسری قابل دسترسی می‌شود که برای سرویس‌های عمومی مناسب است.

بر اساس objc.io — SwiftUI Architecture (2025)، در داخل @EnvironmentObject از مکانیزم مشابه @ObservedObject استفاده می‌کند، اما با سطح اضافی انتزاع برای جستجوی شیء در سلسله‌مراتب. SwiftUI شیء را کپی یا ایجاد نمی‌کند — ارجاعی به نمونه موجود ارسال می‌کند، بنابراین تغییرات در شیء به طور خودکار برای همه Viewهای استفاده‌کننده از @EnvironmentObject قابل مشاهده است.

جستجوی شیء در محیط

  • از View فعلی به بالا — SwiftUI ابتدا محیط View فعلی، سپس والد و به همین ترتیب تا ریشه را بررسی می‌کند.
  • اولین شیء یافت‌شده — اولین شیء از نوع منطبق که در حین بالا رفتن از سلسله‌مراتب پیدا می‌شود استفاده می‌گردد.
  • Fatal error — اگر شیء در هیچ سطحی پیدا نشود، برنامه با خطای «ObservableObject یافت نشد» سقوط می‌کند.

@EnvironmentObject در مقابل @ObservedObject: مقایسه

هم @EnvironmentObject و هم @ObservedObject یک عملکرد اساسی را انجام می‌دهند — Viewها را در تغییرات ObservableObject مشترک می‌کنند. تفاوت در مکانیزم ارسال شیء است. @ObservedObject نیاز به ارسال صریح از طریق مقداردهی اولیه دارد، در حالی که @EnvironmentObject شیء را از محیط بدون مشخص کردن صریح در هر View میانی دریافت می‌کند.

ویژگی@EnvironmentObject@ObservedObject
ارسالاز طریق .environmentObject() در سطح سلسله‌مراتباز طریق مقداردهی اولیه هر View
آشکار بودن وابستگی‌هاپنهان — در امضای View قابل مشاهده نیستآشکار — در init View قابل مشاهده است
Viewهای میانیدرباره شیء نمی‌دانندباید شیء را جلوتر ارسال کنند
ریسک خطاRuntime crash در صورت نبود شیءبررسی زمان کامپایل (اگر پارامتر اجباری باشد)
Prop drillingحذف می‌کندنیاز به ارسال دستی دارد

انتخاب بین @EnvironmentObject و @ObservedObject به معماری بستگی دارد. اگر شیء در عمق سلسله‌مراتب و برای بسیاری از صفحه‌ها مورد نیاز است — @EnvironmentObject راحت‌تر است. اگر معماری نیاز به مشخص کردن صریح وابستگی‌ها برای آزمایش و خوانایی دارد — @ObservedObject ترجیح داده می‌شود.

نمونه‌های استفاده از @EnvironmentObject

رایج‌ترین سناریو — نشست کاربر که باید در همه صفحه‌های برنامه قابل دسترسی باشد. با تزریق UserSession از طریق .environmentObject() در ریشه برنامه، هر صفحه‌ای می‌تواند به داده‌های کاربر و وضعیت احراز هویت دسترسی پیدا کند.

swift
struct ProfileView: View {
    @EnvironmentObject var session: UserSession
    
    var body: some View {
        VStack {
            if session.isLoggedIn {
                Text("سلام، \(session.userName)")
                Button("خروج") {
                    session.isLoggedIn = false
                }
            } else {
                LoginView()
            }
        }
    }
}

struct SettingsView: View {
    @EnvironmentObject var session: UserSession
    
    var body: some View {
        Form {
            Text("ورود به عنوان \(session.userName)")
        }
    }
}

توجه کنید: نه ProfileView و نه SettingsView session را از طریق مقداردهی اولیه دریافت نمی‌کنند. آنها به سادگی @EnvironmentObject var session: UserSession را اعلام می‌کنند و SwiftUI به طور خودکار شیء را در محیط پیدا می‌کند. این امکان را می‌دهد که صفحه‌های جدید بدون تغییر کد موجود ارسال داده اضافه کنید.

خطاهای رایج و خطرات

خطر اصلی @EnvironmentObject — runtime crash اگر شیء به محیط تزریق نشده باشد. بر خلاف پارامترهای اختیاری، @EnvironmentObject نمی‌تواند nil باشد. اگر View با @EnvironmentObject روی صفحه ظاهر شود و View والد برای این نوع .environmentObject() را فراخوانی نکرده باشد، برنامه بلافاصله با خطای «Fatal error: No ObservableObject of type X found» سقوط می‌کند.

چگونه از crash محافظت کنیم

  • تزریق سراسری — شیء را در بالاترین سطح (WindowGroup) تزریق کنید تا برای همه صفحه‌ها قابل دسترسی باشد.
  • بررسی در Preview — در SwiftUI Preview همیشه .environmentObject() را اضافه کنید، در غیر این صورت Preview سقوط می‌کند.
  • مستندسازی و تست‌ها — مستند کنید که View چه @EnvironmentObject‌هایی را انتظار دارد و تست‌هایی بنویسید که وجود آنها را بررسی می‌کند.
  • جایگزینی با @ObservedObject — اگر شیء فقط برای یک صفحه مورد نیاز است، از @ObservedObject با ارسال صریح استفاده کنید.

مشکل نمونه‌های متعدد

اگر دو شیء از یک نوع را در سطوح مختلف سلسله‌مراتب تزریق کنید، View فرزند نزدیک‌ترین را در سلسله‌مراتب دریافت می‌کند. این می‌تواند منجر به سردرگمی شود اگر توسعه‌دهنده انتظار داشته باشد که شیء از محیط ریشه در پنجره مدالی که محیط خود را با شیء هم‌نوع دارد قابل دسترسی باشد.

جایگزین‌های @EnvironmentObject

با توسعه SwiftUI، روش‌های جایگزین مدیریت وابستگی ظاهر شده‌اند که برخی از کاستی‌های @EnvironmentObject را برطرف می‌کنند — در درجه اول پنهان بودن وابستگی‌ها و خطر runtime crash.

  • @Environment property wrapper — برای مقادیر محیطی داخلی (colorScheme, locale, sizeCategory). برای ObservableObject سفارشی مناسب نیست، فقط برای کلیدهای استاندارد EnvironmentValues.
  • Custom EnvironmentKey — می‌توان یک کلید محیطی سفارشی برای نوع‌های مقداری اعلام کرد. ObservableObject به دلیل reference semantics در EnvironmentValues ذخیره نمی‌شود.
  • @ObservedObject با ارسال صریح — رویکرد ایمن با بررسی زمان کامپایل. View بدون شیء مورد نیاز نمی‌تواند ظاهر شود — باید از طریق init ارسال شود.
  • ظرف Dependency Injection — یک ظرف DI خارجی (مانند Resolver یا Swinject) برای مدیریت وابستگی‌ها خارج از SwiftUI.

انتخاب رویکرد به اندازه تیم و پیچیدگی برنامه بستگی دارد. برای پروژه‌های کوچک @EnvironmentObject عالی کار می‌کند. برای پروژه‌های بزرگ با ده‌ها صفحه و الزامات سختگیرانه آزمایش، ارسال صریح از طریق @ObservedObject یا ظرف DI ترجیح داده می‌شود.

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

آیا می‌توان از چند @EnvironmentObject در یک View استفاده کرد؟

بله، View می‌تواند هر تعداد @EnvironmentObject از انواع مختلف را اعلام کند. SwiftUI هر نوع را به طور مستقل در محیط جستجو می‌کند. این زمانی مفید است که View به طور همزمان به نشست کاربر، تنظیمات و سبد خرید نیاز داشته باشد — هر شیء به طور جداگانه تزریق می‌شود.

اگر @EnvironmentObject را از طریق Preview بدون .environmentObject() تزریق کنیم چه اتفاقی می‌افتد؟

Preview با runtime error سقوط می‌کند هنگام تلاش برای نمایش View. همیشه برای Viewهایی که از @EnvironmentObject استفاده می‌کنند در Preview .environmentObject() اضافه کنید. از اشیاء mock با داده‌های آزمایشی استفاده کنید تا Preview به درستی کار کند و وضعیت واقعی‌نمایی را نشان دهد.

آیا می‌توان از @EnvironmentObject با پروتکل‌ها استفاده کرد؟

خیر، @EnvironmentObject فقط با نوع مشخصی از کلاس که ObservableObject را پیاده‌سازی می‌کند کار می‌کند. برای پروتکل‌ها باید از type erasure یا یک wrapper استفاده کنید: یک کلاس wrapper ایجاد کنید که ارجاعی به شیء از نوع پروتکل را ذخیره می‌کند و wrapper را از طریق @EnvironmentObject تزریق کنید.

چگونه Viewای که از @EnvironmentObject استفاده می‌کند را تست کنیم؟

یک نمونه ObservableObject با داده‌های آزمایشی ایجاد کنید و آن را از طریق .environmentObject(testObject) در تست به View ارسال کنید. این الگوی استاندارد برای تست UI SwiftUI است. برای تست‌های واحد، منطق را در ObservableObject جدا کنید و آن را مستقل از View تست کنید.

آیا @EnvironmentObject بر عملکرد با تعداد زیاد صفحه‌ها تأثیر می‌گذارد؟

@EnvironmentObject بار اضافی بر عملکرد ایجاد نمی‌کند، زیرا فقط ارجاع را به شیء ارسال می‌کند و آن را کپی نمی‌کند. با این حال، به‌روزرسانی مکرر ویژگی‌های @Published در شیء سراسری می‌تواند باعث بازترسیم همزمان بسیاری از Viewها شود که ممکن است بر عملکرد تأثیر بگذارد.

خلاصه

  • @EnvironmentObject — property wrapper برای دسترسی به ObservableObject از محیط SwiftUI بدون ارسال صریح از طریق مقداردهی اولیه.
  • تزریق از طریق .environmentObject() — شیء در سطح مشخصی از سلسله‌مراتب در محیط قرار می‌گیرد.
  • جستجوی خودکار — SwiftUI با استفاده از نوع به عنوان کلید، شیء را به سمت بالا در سلسله‌مراتب جستجو می‌کند.
  • Runtime crash — اگر شیء پیدا نشود، برنامه با fatal error سقوط می‌کند که نیاز به احتیاط دارد.
  • حل prop drilling — @EnvironmentObject نیاز به ارسال داده‌ها از طریق Viewهای میانی را از بین می‌برد.
  • وابستگی‌های پنهان — وابستگی‌ها در امضای View قابل مشاهده نیستند که درک کد را دشوار می‌کند.
  • جایگزین‌ها — @ObservedObject برای ارسال صریح، ظروف DI برای پروژه‌های بزرگ.

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

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

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

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