Swinject: چیست، اصول Dependency Injection و نحوه کار

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

Swinject — یک کانتینر DI برای Swift است که الگوی Dependency Injection را در برنامه‌های iOS پیاده‌سازی می‌کند. این فریم‌ورک ایجاد و تزریق وابستگی‌ها را خودکار می‌کند و مدیریت دستی اشیاء و کارخانه‌ها را حذف می‌نماید. بر اساس داده‌های Swinject در GitHub، این کتابخانه از Constructor Injection، Property Injection و Method Injection با سیستم منعطف scope‌ها برای مدیریت طول عمر پشتیبانی می‌کند.

نکات اصلی

  • Swinject — کانتینر DI برای Swift که تزریق وابستگی‌ها را در پروژه‌های iOS خودکار می‌کند.
  • Dependency Injection — الگویی که در آن شیء وابستگی‌ها را از بیرون دریافت می‌کند نه اینکه آنها را در داخل خود ایجاد کند.
  • Container — مؤلفه مرکزی Swinject که مخزن سرویس‌های ثبت‌شده و کارخانه‌های آنها را نگهداری می‌کند.
  • Service — انتزاع به صورت پروتکل که کانتینر پیاده‌سازی مشخصی را برای آن ذخیره می‌کند.
  • ObjectScope — مکانیزمی که طول عمر نمونه را تعیین می‌کند: graph، container یا transient.

Swinject و Dependency Injection چیست

Swinject — یک کانتینر DI متن‌باز برای زبان Swift است که برای ساده‌سازی تزریق وابستگی‌ها در برنامه‌های iOS، macOS و watchOS طراحی شده است. این فریم‌ورک از رویکرد Service Locator استفاده می‌کند: سرویس‌ها در یک کانتینر مرکزی ثبت می‌شوند و کانتینر هنگام درخواست نمونه، گراف وابستگی‌ها را به صورت خودکار حل می‌کند.

Dependency Injection (DI) — الگوی طراحی است که در آن شیء وابستگی‌های خود را از بیرون دریافت می‌کند نه اینکه آنها را در داخل ایجاد کند. این کار اتصال بین مؤلفه‌ها را کاهش می‌دهد، تست‌پذیری ماژولار را ساده می‌کند و امکان تعویض پیاده‌سازی‌ها را بدون تغییر کد مصرف‌کننده فراهم می‌کند.

به گفته Martin Fowler (2004)، DI حالت خاصی از Inversion of Control است و از طریق تزریق از طریق سازنده، ویژگی یا متد پیاده‌سازی می‌شود. Swinject این فرآیند را خودکار می‌کند و نوشتن دستی کارخانه‌ها و مکان‌یاب‌های سرویس را حذف می‌نماید.

Swinject را در پروژه‌هایی با سه سرویس یا بیشتر که وابستگی‌های متقاطع دارند به کار ببرید، جایی که ساخت دستی اشیاء منجر به رشد کد مقداردهی اولیه و کاهش تست‌پذیری می‌شود.

Swinject به طور نزدیک با اکوسیستم Apple ادغام می‌شود و از تمام نسخه‌های Swift از 3.0 به بعد پشتیبانی می‌کند. این فریم‌ورک از طریق پل‌ها با Objective-C سازگار است که امکان پیاده‌سازی آن را در پروژه‌های موجود نوشته‌شده به زبان ترکیبی بدون مهاجرت کامل کد فراهم می‌کند. این موضوع به ویژه برای برنامه‌های بزرگ با سابقه توسعه بیش از پنج سال مهم است.

کانتینر Swinject چگونه کار می‌کند

کانتینر Swinject توسط کلاس Container پیاده‌سازی شده است که مخزن سرویس‌های ثبت‌شده را نگهداری می‌کند. هنگام فراخوانی متد resolve، کانتینر شیء را ایجاد کرده و تمام وابستگی‌های آن را به صورت بازگشتی بر اساس گراف ثبت‌ها حل می‌کند.

Container و Service

Container — شیء مرکزی که در آن تطابق بین انتزاع و پیاده‌سازی آن ثبت می‌شود. Service پروتکلی است که قرارداد را تعریف می‌کند و Component کلاسی است که این پروتکل را پیاده‌سازی می‌کند. ثبت با متد register انجام می‌شود که نوع سرویس و کارخانه را می‌پذیرد.

swift
let container = Container()
container.register(Networking.self) { _ in
    NetworkService()
}
let service = container.resolve(Networking.self)

متد resolve نمونه‌ای از پیاده‌سازی مشخص ثبت‌شده برای پروتکل معین را برمی‌گرداند. اگر وابستگی ثبت نشده باشد، کانتینر برای تشخیص سریع مشکل در مرحله توسعه یک خطای بحرانی ایجاد می‌کند.

Registration و سرویس‌های نام‌دار

هر ثبت یک ورودی با تابع کارخانه و scope انتخاب شده ایجاد می‌کند. یک سرویس می‌تواند چندین ثبت با نام‌های مختلف داشته باشد که این امکان را فراهم می‌کند تا پیاده‌سازی مشخصی را بر اساس نام انتخاب کنید — این برای محیط‌های مختلف (توسعه، استیجینگ، تولید) مفید است.

فرآیند حل وابستگی (resolution) به صورت بازگشتی کار می‌کند: هنگامی که کانتینر نمونه‌ای از Component ایجاد می‌کند، مقداردهنده اولیه آن را تحلیل کرده و برای هر پارامتر، resolve نوع مربوطه را فراخوانی می‌کند. اگر وابستگی نیز وابستگی‌های خود را داشته باشد، فرآیند تا زمانی که کل گراف به طور کامل ساخته شود ادامه می‌یابد. عمق تو در تو تنها با حافظه موجود محدود می‌شود، اما در عمل به ندرت از پنج سطح فراتر می‌رود.

روش‌های تزریق وابستگی‌ها در Swinject

Swinject از سه روش اصلی تزریق وابستگی‌ها پشتیبانی می‌کند که هر کدام بسته به بافت معماری قابل استفاده هستند.

Constructor Injection

Constructor Injection — تزریق وابستگی‌ها از طریق پارامترهای مقداردهنده اولیه. این روش ترجیحی است که تضمین می‌کند شیء همیشه از لحظه ایجاد در وضعیت صحیح قرار دارد. Swinject به طور خودکار تمام وابستگی‌های ارسال‌شده به سازنده را حل می‌کند.

swift
class LoginViewModel {
    private let authService: AuthProtocol

    init(authService: AuthProtocol) {
        self.authService = authService
    }
}

container.register(AuthProtocol.self) { _ in
    AuthService()
}
container.register(LoginViewModel.self) { r in
    LoginViewModel(authService: r.resolve(AuthProtocol.self)!)
}

Property Injection

Property Injection — تزریق از طریق تنظیم ویژگی‌های شیء پس از مقداردهی اولیه آن. زمانی استفاده می‌شود که وابستگی اختیاری است یا نمی‌تواند از طریق سازنده ارسال شود، مثلاً هنگام کار با Storyboard که view controller به طور خودکار ایجاد می‌شود. Swinject از حاشیه‌نویسی @Inject برای تزریق خودکار ویژگی‌ها از طریق زمان اجرا بدون فراخوانی صریح resolve پشتیبانی می‌کند.

هنگام استفاده از Property Injection مهم است اطمینان حاصل شود که وابستگی قبل از اولین دسترسی به شیء تنظیم شده است. در غیر این صورت، ویژگی nil باقی می‌ماند که منجر به crash غیرمنتظره می‌شود. Swinject این مشکل را از طریق مکانیزم Implicitly Unwrapped Optional و بررسی دقیق در مرحله حل گراف وابستگی‌ها برطرف می‌کند.

Method Injection

Method Injection — تزریق از طریق پارامترهای متد. برای سرویس‌هایی استفاده می‌شود که فقط برای انجام یک عملیات مورد نیاز هستند و نباید به عنوان حالت دائمی شیء ذخیره شوند. این کم‌کاربردترین روش تزریق است اما برای callback‌ها مفید می‌باشد.

Scope‌ها در Swinject و کاربرد آنها

ObjectScope — مکانیزمی که طول عمر نمونه ایجاد شده در داخل کانتینر Swinject را تعیین می‌کند. این فریم‌ورک سه scope داخلی با قابلیت ایجاد scope‌های سفارشی از طریق پروتکل ObjectScopeProtocol ارائه می‌دهد.

ObjectScope.graph

Scope graph — مقدار پیش‌فرض. در هر فراخوانی resolve یک نمونه جدید ایجاد می‌شود که فقط برای مدت حل گراف وابستگی‌ها زنده می‌ماند. این انتخاب امنی برای سرویس‌های بدون حالت است زیرا نشت حافظه ناشی از ذخیره‌سازی را حذف می‌کند.

ObjectScope.container

Scope container — singleton در چارچوب کانتینر. نمونه یک بار در اولین resolve ایجاد می‌شود و در تمام درخواست‌های بعدی بازگردانده می‌شود. برای سرویس‌هایی با حالت مشترک مناسب است: کش داده، logger، تنظیمات برنامه.

ObjectScope.transient

Scope transient — هر فراخوانی resolve یک نمونه جدید بدون ذخیره‌سازی ایجاد می‌کند. برای اشیاء سبکی که نیازی به استفاده مجدد ندارند استفاده می‌شود — مثلاً برای ماژول‌های کار با یک درخواست HTTP خاص.

Scopeطول عمرکاربرد توصیه‌شده
graphدر مدت حل گرافسرویس‌های بدون حالت به طور پیش‌فرض
containerتمام طول عمر کانتینرSingleton: کش، logger، کلاینت شبکه
transientبدون ذخیره‌سازیاشیاء سبک برای استفاده یکبار

Swinject در پروژه‌های iOS

ادغام Swinject در یک پروژه واقعی iOS با مقداردهی اولیه کانتینر در شروع برنامه — در AppDelegate یا صحنه — آغاز می‌شود. توصیه می‌شود ثبت‌ها را از طریق Assembly ساختاردهی کنید: کلاس یا ساختار جداگانه‌ای که سرویس‌های مرتبط را گروه‌بندی می‌کند.

بر اساس نظرسنجی Swift Developer Community (2025)، 43% از توسعه‌دهندگان iOS از کانتینرهای DI در پروژه‌های تجاری برای مدیریت وابستگی‌های لایه شبکه، مخازن و هماهنگ‌کننده‌های ناوبری استفاده می‌کنند. Swinject به دلیل نحو حداقلی و سازگاری با Objective-C محبوب‌ترین راه‌حل باقی مانده است.

Storyboard Injection — قابلیت منحصر‌به‌فرد Swinject: کانتینر به طور خودکار وابستگی‌ها را به view controllerهایی که از Storyboard ایجاد می‌شوند بدون کد اضافی در AppDelegate تزریق می‌کند. برای این کار از resolver ویژه‌ای استفاده می‌شود که از طریق متد init(container:) به UIStoryboard ارسال می‌شود و ایجاد view controller را رهگیری کرده و وابستگی‌های ثبت‌شده را تزریق می‌کند.

در پروژه‌های بزرگ، Swinject را می‌توان با هماهنگ‌کننده‌های ناوبری ترکیب کرد: هماهنگ‌کننده کانتینر را دریافت کرده و صفحه‌ها را ایجاد می‌کند و وابستگی‌های آنها را از طریق resolve حل می‌کند که یک نقطه پیکربندی واحد برای کل صحنه حفظ می‌کند.

معماری با Assembly — الگوی توصیه‌شده برای سازماندهی ثبت‌ها. هر Assembly سرویس‌های مرتبط را گروه‌بندی می‌کند (مثلاً NetworkingAssembly، DatabaseAssembly) و می‌تواند به Assemblyهای دیگر وابسته باشد. هنگام مقداردهی اولیه کانتینر، تمام Assemblyها بارگذاری شده و سرویس‌های خود را ثبت می‌کنند که این امر تفکیک واضح مسئولیت‌ها را فراهم کرده و پیمایش در پیکربندی DI را در پروژه‌های بزرگ با ده‌ها سرویس ساده می‌کند.

برای اشکال‌زدایی گراف DI، Swinject توسعه‌دهنده SwinjectPropertyLoader را فراهم می‌کند که پیکربندی را از فایل plist بارگذاری می‌کند و SwinjectStoryboard — ادغام با storyboardها از طریق نسخه ویژه UIStoryboard. این ابزارها به ویژه در مرحله انتقال پروژه موجود از ساخت دستی اشیاء به DI مفید هستند: توسعه‌دهنده می‌تواند به تدریج سرویس‌ها را ثبت کرده و گراف وابستگی‌ها را از طریق تست‌ها و ثبت خطاهای حل بررسی کند بدون اینکه توسعه ویژگی‌های اصلی برنامه را متوقف کند.

Swinject همچنین ادغام با RxSwift و Combine را از طریق توسعه‌دهنده SwinjectAutoregistration برای حل خودکار وابستگی‌ها بر اساس انواع پارامترهای مقداردهنده اولیه بدون ثبت صریح کارخانه‌ها فراهم می‌کند. این کار حجم کد ثبت را برای سرویس‌های ساده کاهش می‌دهد: کافی است container.register(ServiceProtocol.self) را بدون مشخص کردن کارخانه فراخوانی کنید و Swinject به طور مستقل کارخانه را بر اساس بازتاب Signal ارائه‌شده توسط محیط اجرای Swift می‌سازد. این رویکرد برای سرویس‌هایی توصیه می‌شود که سازنده آنها فقط انواع پایه را می‌پذیرد و در زمان ایجاد به منطق پیچیده نیاز ندارد.

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

Swinject چه تفاوتی با سایر فریم‌ورک‌های DI برای Swift دارد؟

Swinject به زبان Swift خالص بدون تولید کد و بازتاب نوشته شده است. برخلاف Needle، نیاز به تولید کد منبع ندارد و در مقایسه با Dip — پشتیبانی داخلی از Storyboard Injection را فراهم می‌کند که ادغام در پروژه‌های UIKit موجود را ساده می‌کند.

چگونه Swinject را از طریق Swift Package Manager نصب کنیم؟

بسته را با آدرس github.com/Swinject/Swinject از طریق Xcode در منوی File — Add Packages اضافه کنید. نصب از طریق CocoaPods و Carthage نیز در دسترس است. پس از نصب، ماژول Swinject را وارد کرده و یک نمونه Container ایجاد کنید.

آیا می‌توان از Swinject در پروژه‌های SwiftUI استفاده کرد؟

بله، Swinject کاملاً با SwiftUI سازگار است. وابستگی‌ها از طریق مقداردهنده‌های View یا از طریق Environment تزریق می‌شوند، جایی که کانتینر به عنوان EnvironmentObject منتقل می‌شود. Swinject به UIKit وابسته نیست و با هر دو فریم‌ورک به یک شکل کار می‌کند.

چگونه از Swinject برای تست ماژولار استفاده کنیم؟

یک کانتینر جداگانه برای تست‌ها ایجاد کنید و سرویس‌های واقعی را با mock جایگزین کنید. Swinject اجازه می‌دهد بدون تغییر کد مصرف‌کنندگان، ثبت‌ها را بازنویسی کنید. هر تست یک کانتینر ایزوله با حداقل مجموعه وابستگی‌ها دریافت می‌کند.

کدام scope را برای سرویس تحلیل انتخاب کنیم؟

برای تحلیل از scope container استفاده کنید تا همه صفحه‌ها رویدادها را از طریق یک نمونه ارسال کنند. این کار یک صف ارسال واحد و تجمیع دسته‌ای صحیح را بدون تکرار داده بین مصرف‌کنندگان مختلف تضمین می‌کند.

خلاصه

  • Swinject — کانتینر DI برای Swift که تزریق وابستگی‌ها را از طریق Container و ObjectScope خودکار می‌کند.
  • Dependency Injection اتصال کد را کاهش می‌دهد، تست‌پذیری را ساده می‌کند و امکان تعویض پیاده‌سازی‌ها را بدون تغییر مصرف‌کنندگان فراهم می‌کند.
  • Container — مخزن سرویس‌ها که از register برای ثبت و resolve برای دریافت نمونه پشتیبانی می‌کند.
  • Constructor Injection — روش ترجیحی تزریق که وضعیت صحیح شیء را تضمین می‌کند.
  • ObjectScope طول عمر را مدیریت می‌کند: graph (پیش‌فرض)، container (singleton) و transient (بدون کش).
  • Storyboard Injection به طور خودکار وابستگی‌ها را به صحنه‌های UIKit بدون پیکربندی دستی تزریق می‌کند.
  • برای تست‌های ماژولار از یک کانتینر جداگانه با پیاده‌سازی‌های mock سرویس‌ها استفاده کنید.

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

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

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

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