حقن التبعية مع DITranquillity

Dependency Injection هو نمط شائع إلى حد ما يسمح لك بتكوين النظام بمرونة وبناء تبعيات مكونات هذا النظام بشكل صحيح على بعضها البعض. بفضل الكتابة ، يتيح لك Swift استخدام أطر ملائمة يمكنك من خلالها وصف رسم التبعية بإيجاز شديد. اليوم أريد أن أتحدث قليلاً عن أحد هذه الأطر - DITranquillity .


سيغطي هذا البرنامج التعليمي ميزات المكتبة التالية:


  • اكتب التسجيل
  • التهيئة النشر
  • التضمين في متغير
  • التبعيات الدورية للمكون
  • استخدام المكتبة مع UIStoryboard

وصف المكون


سيتألف التطبيق من المكونات الرئيسية التالية: ViewController ، Router ، Presenter ، Networking - هذه مكونات شائعة جدًا في أي تطبيق iOS.


هيكل المكون

سيتم تقديم ViewController وجهاز Router إلى بعضها البعض بشكل دوري.


تدريب


أولاً ، قم بإنشاء تطبيق عرض فردي في Xcode ، أضف DITranquillity باستخدام CocoaPods . قم بإنشاء التسلسل الهرمي الضروري للملفات ، ثم أضف وحدة تحكم ثانية إلى Main.storyboard وقم بتوصيلها باستخدام StoryboardSegue . نتيجة لذلك ، يجب الحصول على بنية الملف التالية:


هيكل الملف

قم بإنشاء تبعيات في الفصول كما يلي:


إعلان المكون
 protocol Presenter: class { func getCounter(completion: @escaping (Int) -> Void) } class MyPresenter: Presenter { private let networking: Networking init(networking: Networking) { self.networking = networking } func getCounter(completion: @escaping (Int) -> Void) { // Implementation } } 

 protocol Networking: class { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) } class MyNetworking: Networking { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) { // Implementation } } 

 protocol Router: class { func presentNewController() } class MyRouter: Router { unowned let viewController: ViewController init(viewController: ViewController) { self.viewController = viewController } func presentNewController() { // Implementation } } 

 class ViewController: UIViewController { var presenter: Presenter! var router: Router! } 

قيود


على عكس الفئات الأخرى ، لا يتم إنشاء ViewController من قبلنا ، ولكن عن طريق مكتبة UIKit داخل تطبيق UIStoryboard.instantiateViewController ، وبالتالي ، باستخدام لوحة العمل ، لا يمكننا حقن التبعيات في ورثة UIViewController باستخدام UIViewController . هذا هو الحال مع ورثة UIView و UITableViewCell .


لاحظ أن الكائنات المخفية وراء البروتوكولات مضمنة في جميع الفئات. هذه واحدة من المهام الرئيسية لتنفيذ التبعيات - لجعل التبعيات ليس على التطبيقات ، ولكن على الواجهات. سيساعد ذلك في المستقبل على توفير تطبيقات بروتوكول مختلفة لإعادة استخدام المكونات أو اختبارها.


حقن التبعية


بعد إنشاء جميع مكونات النظام ، ننتقل إلى اتصال الكائنات فيما بينها. في DITranquillity ، نقطة البداية هي DIContainer ، التي تضيف التسجيل باستخدام طريقة container.register(...) . لفصل التبعيات إلى أجزاء ، DIPart DIFramework و DIPart ، والتي يجب تنفيذها. للراحة ، DIFramework فئة واحدة فقط من ApplicationDependency ، والتي ستنفذ DIFramework وستكون بمثابة مكان تسجيل لجميع التبعيات. تتطلب واجهة DIFramework تطبيق طريقة واحدة فقط - load(container:) .


 class ApplicationDependency: DIFramework { static func load(container: DIContainer) { // registrations will be placed here } } 

لنبدأ بأبسط تسجيل ليس له أي تبعيات - MyNetworking


 container.register(MyNetworking.init) 

يستخدم هذا التسجيل التنفيذ من خلال أداة التهيئة. على الرغم من حقيقة أن المكون نفسه لا يوجد لديه تبعيات ، يجب توفير المُهيئ لتوضيح كيفية إنشاء المكون للمكتبة.


وبالمثل ، سجل MyPresenter و MyRouter .


 container.register1(MyPresenter.init) container.register1(MyRouter.init) 

ملاحظة: لاحظ أنه لا يتم استخدام التسجيل ، ولكن register1 . لسوء الحظ ، من الضروري الإشارة إلى ما إذا كان الكائن يحتوي على تبعية واحدة فقط في المُهيئ. وهذا هو ، إذا كان هناك 0 أو اثنين أو أكثر من التبعيات ، تحتاج فقط إلى استخدام register . هذا التقييد عبارة عن خطأ في الإصدار 4.0 من Swift.


حان الوقت لتسجيل ViewController لدينا. إنها تحقن الكائنات ليس من خلال أداة التهيئة ، ولكن مباشرة في المتغير ، لذلك سيظهر وصف التسجيل أكثر من ذلك بقليل.


 container.register(ViewController.self) .injection(cycle: true, \.router) .injection(\.presenter) 

بناء جملة النموذج \.presenter هو SwiftKeyPath ، وبفضل ذلك يمكنك تنفيذ التبعيات بإيجاز. نظرًا لأن Router و ViewController دوريًا على بعضهما البعض ، فيجب الإشارة بوضوح إلى ذلك باستخدام cycle: true . يمكن للمكتبة نفسها حل هذه التبعيات دون إشارة صريحة ، ولكن تم تقديم هذا الشرط حتى يفهم الشخص الذي يقرأ الرسم البياني على الفور أن هناك دورات في سلسلة التبعية. لاحظ أيضًا أن NOT ViewController.init ، ولكن ViewController.self . تم توضيح ذلك أعلاه في قسم القيود .


من الضروري أيضًا تسجيل UIStoryboard باستخدام طريقة خاصة.


 container.registerStoryboard(name: "Main") 

الآن وصفنا الرسم البياني التبعية بأكمله لشاشة واحدة. ولكن ليس هناك إمكانية الوصول إلى هذا الرسم البياني حتى الآن. يجب عليك إنشاء DIContainer يسمح لك بالوصول إلى الكائنات الموجودة فيه.


 static let container: DIContainer = { let container = DIContainer() // 1 container.append(framework: ApplicationDependency.self) // 2 assert(container.validate(checkGraphCycles: true)) // 3 return container }() 

  1. تهيئة الحاوية
  2. أضف وصفًا للرسم البياني.
  3. نتحقق من أننا فعلنا كل ما هو صواب. إذا تم ارتكاب خطأ ، فلن يتعطل التطبيق أثناء حل التبعيات ، ولكن فورًا عند إنشاء رسم بياني

ثم تحتاج إلى جعل الحاوية نقطة انطلاق التطبيق. للقيام بذلك ، AppDelegate بتطبيق طريقة didFinishLaunchingWithOptions في didFinishLaunchingWithOptions بدلاً من تحديد Main.storyboard كنقطة انطلاق في إعدادات المشروع.


 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) let storyboard: UIStoryboard = ApplicationDependency.container.resolve() window?.rootViewController = storyboard.instantiateInitialViewController() window?.makeKeyAndVisible() return true } 

إطلاق


في البداية ، سيحدث انخفاض وسيفشل التحقق من الصحة للأسباب التالية:


  • لن تجد الحاوية أنواع Router أو Presenter أو Networking لأننا سجلنا الكائنات فقط. إذا كنا نريد منح الوصول ليس للتطبيقات ، ولكن للواجهات ، يجب علينا تحديد الواجهات بشكل صريح
  • لا تفهم الحاوية كيفية حل التبعية الدورية ، لأنه من الضروري الإشارة صراحةً إلى الأشياء التي لا ينبغي إعادة إنشائها في كل مرة يحل فيها الرسم البياني.

من السهل إصلاح الخطأ الأول - هناك طريقة خاصة تسمح لك بتحديد البروتوكولات التي تتوفر بها الطريقة في الحاوية.


 container.register(MyNetworking.init) .as(check: Networking.self) {$0} 

لوصف التسجيل على النحو التالي ، نقول: كائن MyNetworking يمكن الوصول إليه عبر بروتوكول Networking . يجب أن يتم ذلك لجميع الكائنات المخفية تحت البروتوكولات. {$0} نضيفه للتحقق الصحيح من الأنواع بواسطة المترجم.


الخطأ الثاني هو أكثر تعقيدا قليلا. من الضروري استخدام scope المسمى ، والذي يصف عدد المرات التي يتم فيها إنشاء الكائن ومدى الحياة. لكل تسجيل مشارك في تبعية دائرية ، يجب عليك تحديد scope يساوي objectGraph . سيوضح ذلك للحاوية أنه أثناء الحل ، من الضروري إعادة استخدام نفس الكائنات التي تم إنشاؤها ، بدلاً من إنشاء كل مرة. وبالتالي ، اتضح:


 container.register(ViewController.self) .injection(cycle: true, \.router) .injection(\.presenter) .lifetime(.objectGraph) container.register1(MyRouter.init) .as(check: Router.self) {$0} .lifetime(.objectGraph) 

بعد إعادة التشغيل ، تنجح الحاوية في التحقق من الصحة بنجاح ، ويفتح ViewController لدينا مع التبعيات التي تم إنشاؤها. يمكنك وضع نقطة توقف في viewDidLoad وتأكد.


الانتقال بين الشاشات


بعد ذلك ، قم بإنشاء فئتين صغيرتين ، SecondViewController و SecondPresenter ، وأضف SecondViewController إلى لوحة العمل وقم بإنشاء Segue بينهما باستخدام المعرف "RouteToSecond" ، والذي يسمح لك بفتح وحدة التحكم الثانية من الأولى.


أضف تسجيلين آخرين إلى ApplicationDependency لكل فئة من الفئات الجديدة:


 container.register(SecondViewController.self) .injection(\.secondPresenter) container.register(SecondPresenter.init) 

.as هناك حاجة لتحديد .as ، لأننا لم نخفي SecondPresenter وراء البروتوكول ، ولكن استخدم التطبيق مباشرة. بعد ذلك ، في طريقة viewDidAppear التحكم الأولى ، نسميها performSegue(withIdentifier: "RouteToSecond", sender: self) ، ابدأ ، يتم فتح وحدة التحكم الثانية ، والتي يجب أن تكون ملحقة بها secondPresenter . كما ترون ، شهدت الحاوية إنشاء وحدة تحكم ثانية من UIStoryboard ونجح في إخماد التبعيات.


استنتاج


تتيح لك هذه المكتبة العمل بشكل مريح مع التبعيات الدورية ، ولوحات القصة ، واستخدام الاستدلال التلقائي للنوع بالكامل في Swift ، والذي يوفر بناء جملة قصيرًا ومرنًا لوصف رسم بياني التبعية.


مراجع


رمز عينة كاملة في مكتبة جيثب


الهدوء على جيثب


المادة الانجليزية

Source: https://habr.com/ru/post/ar450788/


All Articles