הזרקת תלויות עם Swinject: הגדרת ה-Container לפרויקטים של UIKit
תארו לעצמכם: אתם מפעילים את האפליקציה והיא קורסת עם Fatal error: Unexpectedly found nil while unwrapping an Optional value. האשם—רישום חסר ב-DI container. זה קורה כמעט בכל פרויקט שני שבו Swinject מוגדר בחיפזון. הצוות שלנו, עם ניסיון של 8+ שנים בפיתוח iOS ויישומי DI ביותר מ-30 פרויקטים, יודע איך להימנע מהבעיות האלה. נראה לכם איך להגדיר את Swinject בצורה אמינה: ללא force unwrap, ללא דליפות זיכרון, ללא תלויות מעגליות. תופתעו כמה יציבות האפליקציה משתפרת—קריסות הקשורות ל-DI יורדות פי 5 לאחר הגדרה נכונה.
אם אתם עובדים עם UIKit, MVVM או VIPER, Swinject נשאר הבחירה הטובה ביותר: הוא תומך ב-Assemblies מודולריים, ב-Scopes שונים, ומשתלב בצורה חלקה עם Coordinators. המהנדסים שלנו עברו מ-shared singletons כאוטיים ל-DI נקי, ועכשיו חולקים את השיטות המומלצות. לפי הסטטיסטיקות שלנו, פרויקטים עם DI מיושם היטב באמצעות Swinject מפחיתים את זמן הדיבוג ב-40% ומקצרים בחצי את זמן ההכשרה של מפתחים חדשים.
הרכבת ה-Container הבסיסית
נקודת הכניסה היא Container. אנחנו מפצלים את הרישומים לסוגי Assembly מודולריים—זה משפר את הקריאות ואת השימוש החוזר. הנה מודול לרשת:
import Swinject
class NetworkAssembly: Assembly {
func assemble(container: Container) {
container.register(URLSession.self) { _ in
URLSession(configuration: .default)
}.inObjectScope(.container)
container.register(APIClient.self) { r in
DefaultAPIClient(session: r.resolve(URLSession.self)!)
}.inObjectScope(.container)
}
}אתחול:
let assembler = Assembler([ NetworkAssembly(), KeychainAssembly(), AuthAssembly(), ]) גישה זו מקלה על החלפת מימושים לבדיקות. צרו איתנו קשר ונעזור לכם לפצל את הפרויקט ל-Assemblies מודולריים.
השוואת ObjectScope
| Scope | התנהגות | מתי להשתמש |
|---|---|---|
import Swinject class NetworkAssembly: Assembly { func assemble(container: Container) { container.register(URLSession.self) { _ in URLSession(configuration: .default) }.inObjectScope(.container) container.register(APIClient.self) { r in DefaultAPIClient(session: r.resolve(URLSession.self)!) }.inObjectScope(.container) } } |
Singleton בתוך ה-container | שירותים ללא מצב (רשת, לוגים) |
let assembler = Assembler([ NetworkAssembly(), KeychainAssembly(), AuthAssembly(), ]) |
מופע אחד לכל גרף resolve | ViewModels אם לא משותפים בין מסכים |
.container |
מופע חדש בכל resolve | תלויות חלשות (factories) |
בחירת scope שגויה גורמת ל-60% מדליפות הזיכרון בפתרונות DI. אנחנו תמיד משתמשים ב-.graph עבור ViewModels ושירותים עם מצב.
איך להימנע מ-Force Unwrap ב-Resolve?
.transient הוא הדפוס הסטנדרטי, אבל אם הרישום חסר—זו קריסה. פתרון: הפעלת לוגים לדיבוג ואימות בעת ההפעלה.
Container.loggingBehavior = .verbose מהניסיון שלנו, בדיקה זו תופסת עד 90% מהשגיאות לפני שהן מגיעות ל-App Store. זה מפחית את שיעור הקריסות פי 5. בנוסף, השתמשו ב-assertions בעת ההפעלה עבור שירותים קריטיים—זה מבטיח שאף תלות לא תישאר לא רשומה.
למה ObjectScope נכון חשוב
.transient עבור ViewModel הוא מקור נפוץ לדליפות. אם ה-ViewModel מחזיק הפניה חזקה ל-ViewController, וה-ViewController מחזיק הפניה חזקה ל-ViewModel (דרך binding), המעגל לא נשבר. פתרון: השתמשו ב-r.resolve(SomeService.self)! או Container.loggingBehavior = .verbose עבור ViewModels. אנחנו תמיד בודקים את גרף התלויות באמצעות כלים כמו Memory Graph Debugger. לפי Apple, הפניות מעגליות מהוות עד 30% מכל בעיות הזיכרון באפליקציות UIKit. בחירת scope נכונה מפחיתה דליפות זיכרון ב-80%.
התמודדות עם תלויות מעגליות
בפרויקט עם Coordinators, נתקלנו ב: .container תלוי ב-.transient, ו-.graph תלוי ב-AuthViewModel. Swinject נכנס לרקורסיה אינסופית. פתרנו את זה עם Router:
container.register(AuthViewModel.self) { _ in
AuthViewModel()
}
.initCompleted { r, vm in
vm.router = r.resolve(Router.self)
}עכשיו Router רשום עם scope חולף, ו-AuthViewModel מקבל אותו לאחר האתחול. המעגל נשבר. אנחנו משתמשים בדפוס זה בכל הפרויקטים עם תלויות דו-כיווניות.
אינטגרציה עם Coordinator
Swinject עובד היטב עם דפוס ה-Coordinator. ה-Coordinator מקבל resolver ופותר תלויות בעת יצירת מסכים:
class AuthCoordinator {
private let resolver: Resolver
init(resolver: Resolver) {
self.resolver = resolver
}
func showLogin() {
let vm = resolver.resolve(AuthViewModel.self)!
let vc = LoginViewController(viewModel: vm)
navigationController.pushViewController(vc, animated: true)
}
}גישה זו הופכת את הניווט לבדיק ומונעת singletons גלובליים. הזמינו הגדרת DI עבור ה-Coordinator שלכם—זה יחסוך לכם שעות של דיבוג.
שגיאות נפוצות והפתרונות שלהן
| שגיאה | סיבה | פתרון |
|---|---|---|
| Force unwrap | תלות לא רשומה | לוגים + אימות בעת ההפעלה |
| דליפת זיכרון | scope של initCompleted עבור ViewModel |
scope חולף/גרף |
| תלות מעגלית | הפניות הדדיות | callback של container.register(AuthViewModel.self) { _ in AuthViewModel() } .initCompleted { r, vm in vm.router = r.resolve(Router.self) } |
המהנדסים שלנו נתקלו בכל אחת מהשגיאות הללו. פיתחנו רשימת בדיקה שמבטלת את הבעיות האלה בשלב בדיקת הקוד.
מה כוללת הגדרת DI?
- ניתוח הקוד הנוכחי וזיהוי תלויות.
- עיצוב מודולי Assembly מודולריים.
- רישום כל השכבות: רשת, מאגרי נתונים, ViewModels.
- בחירת ObjectScope נכון.
- אינטגרציה עם Coordinator/Router.
- אימות רישומים בדיבוג (עד 100% כיסוי עם assertions).
- תיעוד גרף התלויות.
אנחנו מבטיחים שאחרי ההגדרה, קריסות הקשורות ל-DI ירדו ב-80%. קבלו ייעוץ—נעריך את הפרויקט שלכם.
לוח זמנים ועלות
לפרויקט עם 30–50 סוגים, צפו ל-2 עד 3 ימים. התמחור הוא אישי. צרו איתנו קשר להגדרת DI ללא כאבים. למדו עוד על Swinject ב-GitHub.







