
منذ ثلاث سنوات ، كتبت
مقالة عن مكتبة DI للغة سويفت. من تلك اللحظة فصاعدًا ، تغيرت المكتبة كثيرًا وأصبحت
أفضل منافسها اللطيف لـ Swinject ، متجاوزةً لها في العديد من النواحي. المقالة مكرسة لقدرات المكتبة ، ولكن لها أيضًا اعتبارات نظرية. لذلك ، المهتمين بمواضيع DI ، DIP ، IoC ، أو من يختار بين Swinject و Swinject ، أطلب cat:
ما هو DIP ، IoC وماذا يأكل؟
نظرية DIP و IoC
النظرية هي واحدة من أهم المكونات في البرمجة. نعم ، يمكنك كتابة التعليمات البرمجية دون تعليم ، ولكن على الرغم من ذلك ، يقرء المبرمجون المقالات باستمرار ، يهتمون بممارسات مختلفة ، إلخ. هذا هو ، بطريقة أو بأخرى أحصل على المعرفة النظرية من أجل وضعها موضع التنفيذ.
أحد الموضوعات التي يحب الناس طلبها لإجراء المقابلات هو
SOLID . لا مقال لا يتعلق به على الإطلاق ، لا تشعر بالقلق. لكننا نحتاج إلى خطاب واحد ، لأنه يرتبط ارتباطًا وثيقًا بمكتبتي. هذه هي الرسالة `D` - مبدأ انعكاس التبعية.
ينص مبدأ انعكاس التبعية على:
- يجب ألا تعتمد وحدات المستوى العلوي على وحدات المستوى الأدنى. يجب أن يعتمد كلا النوعين من الوحدات على التجريد.
- يجب ألا تعتمد التجريدات على التفاصيل. يجب أن تعتمد التفاصيل على التجريد.
يفترض الكثير من الناس خطأً أنهم إذا استخدموا البروتوكولات / الواجهات ، فإنهم يلتزمون تلقائيًا بهذا المبدأ ، لكن هذا ليس صحيحًا تمامًا.
العبارة الأولى تقول شيئًا عن التبعيات بين الوحدات - يجب أن تعتمد الوحدات على التجريدات. مهلا ، ما هو التجريد؟ - من الأفضل أن تسأل نفسك ليس ما هو التجريد ، ولكن ما هو التجريد؟ وهذا يعني أنك تحتاج إلى فهم ماهية العملية ، وستكون نتيجة هذه العملية مجرد تجريد.
التجريد هو إلهاء في عملية الإدراك من الأطراف غير الأساسية والخصائص والعلاقات من أجل إبراز العلامات الأساسية المنتظمة.
يمكن أن يكون للكائن نفسه ، بناءً على الأهداف ، تجريدات مختلفة. على سبيل المثال ، يحتوي الجهاز من وجهة نظر المالك على الخصائص المهمة التالية: اللون والأناقة والراحة. ولكن من وجهة نظر ميكانيكي ، كل شيء مختلف إلى حد ما: العلامة التجارية ، النموذج ، التعديل ، عدد الكيلومترات ، والمشاركة في حادث. تم للتو تسمية تجريدين مختلفين لكائن واحد - الجهاز.
لاحظ أنه في Swift ، من المعتاد استخدام بروتوكولات التجريد ، لكن هذا ليس شرطا. لا أحد يكترث بعمل فصل ، وتخصيص مجموعة من الأساليب العامة منه ، وترك تفاصيل التنفيذ خاصة. من حيث التجريد ، لا شيء مكسور. يجب أن نتذكر الأطروحة المهمة - "التجريد ليس مرتبطًا باللغة" - هذه عملية تحدث باستمرار في أذهاننا ، وكيف يتم نقلها إلى الكود ليس مهمًا جدًا. هنا يمكن أن نذكر أيضًا
التغليف ، كمثال على ما يرتبط باللغة. كل لغة لها وسائلها الخاصة من أجل توفيرها. في Swift ، هذه فئات وحقول وصول وبروتوكولات ؛ على واجهات Obj-C والبروتوكولات وفصل الملفات h و m.
البيان الثاني أكثر إثارة للاهتمام ، لأنه يتم تجاهله أو يساء فهمه. يتحدث عن تفاعل التجريدات مع التفاصيل ، وما هي التفاصيل؟ هناك اعتقاد خاطئ بأن التفاصيل عبارة عن فئات تنفذ البروتوكولات - نعم هذا صحيح ، لكنه غير كامل. يجب أن تفهم أن التفاصيل ليست مرتبطة بلغات البرمجة - حيث لا تحتوي لغة C على بروتوكولات ولا فئات ، ولكن هذا المبدأ يعمل أيضًا عليها. من الصعب بالنسبة لي أن أشرح نظريًا ما هو المصيد ، لذلك سأقدم مثالين ، ثم أحاول أن أثبت سبب صحة المثال الثاني.
لنفترض أن هناك سيارة من فئة ومحرك فئة. حدث أن نحتاج إلى توصيلهم - يحتوي الجهاز على محرك. نحن ، كمبرمجين أكفاء ، نحدد محرك البروتوكول وننفذ البروتوكول وننقل تنفيذ البروتوكول إلى فئة الماكينة. يبدو كل شيء جيدًا وصحيحًا - يمكنك الآن استبدال تطبيق المحرك بسهولة وعدم التفكير في حدوث شيء ما. بعد ذلك ، يضاف ميكانيكي المحرك إلى الدائرة. انه مهتم في المحرك خصائص مختلفة تماما عن السيارة. نحن بصدد توسيع البروتوكول والآن يحتوي على مجموعة أكبر من الميزات من البداية. تتكرر القصة لصاحب السيارة ، وللمحركات المنتجة في المصنع ، إلخ.

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

الآن ، لنرسم تشابهاً مع حالة أخرى ، لأن هذه الحجج قد لا تكون واضحة.
هناك خلفية وهناك حاجة إلى بعض الوظائف منه. يوفر لنا Backend طريقة كبيرة تحتوي على مجموعة من البيانات ، ويقول - "أنت بحاجة إلى هذه الحقول الثلاثة من 1000"
قصة صغيرةيمكن أن يقول الكثيرون أن هذا لا يحدث. وسيكونون على حق نسبيًا - يحدث أن يتم كتابة الواجهة الخلفية بشكل منفصل لتطبيق الهاتف المحمول. لقد حدث أنني عملت في شركة حيث الخلفية هي خدمة لها تاريخ من 10 سنوات ترتبط ، من بين أمور أخرى ، بواجهة برمجة التطبيقات للولاية. لأسباب عديدة ، لم يكن من المعتاد بالنسبة للشركة أن تكتب طريقة منفصلة للجوال ، واضطررت إلى استخدام ما كان. وكان هناك طريقة واحدة رائعة بها حوالي مائة معلمة في الجذر ، وبعضها كانت قواميس متداخلة. تخيل الآن 100 معلمة ، 20٪ منها تحتوي على معلمات متداخلة ، وداخل كل واحدة متداخلة هناك 20-30 معلمة أخرى لها نفس التداخل. لا أتذكر بالضبط ، ولكن عدد المعلمات تجاوز 800 للكائنات البسيطة ، وبالنسبة للمعقدة يمكن أن تكون أعلى من 1000.
لا تبدو جيدة جدا ، أليس كذلك؟ عادةً ما تكتب الواجهة الخلفية طريقة لمهام محددة للواجهة الأمامية ، والواجهة الأمامية هي العميل / المستخدم لهذه الطرق. حسنًا ... ولكن إذا فكرت في الأمر ، فإن الواجهة الخلفية هي المحرك ، والواجهة الأمامية هي السيارة - الجهاز يحتاج إلى بعض خصائص المحرك ، وليس المحرك بحاجة إلى إعطاء خصائص السيارة. فلماذا ، على الرغم من هذا ، نواصل كتابة بروتوكول المحرك ووضعه أقرب إلى تنفيذ المحرك ، وليس الجهاز؟ الأمر كله يتعلق بالمقياس - في معظم برامج iOS ، من النادر جدًا توسيع نطاق الوظيفة بحيث يصبح هذا الحل مشكلة.
ثم ما هو DI
هناك استبدال للمفاهيم - DI ليست اختصارًا لـ DIP ، ولكن اختصار مختلف تمامًا ، على الرغم من حقيقة أنه يتقاطع بشكل وثيق مع DIP. DI هي حقن التبعية أو حقن التبعية ، وليس الانعكاس. يتحدث الانعكاس عن كيفية تفاعل الطبقات والبروتوكولات مع بعضها البعض ، ويخبرك التطبيق بمكان الحصول عليها. بشكل عام ، يمكنك تنفيذه بطرق مختلفة - بدءًا من حيث تأتي التبعيات: مُنشئ ، خاصية ، طريقة ؛ تنتهي مع أولئك الذين يقومون بإنشائها وكيف الآلية هذه العملية. النهج مختلفة ، ولكن في رأيي ، أكثرها ملاءمة هي حاويات لحقن التبعية. باختصار ، يتلخص معناها بالكامل في قاعدة بسيطة: نقول للحاوية أين وكيف يتم تنفيذها وبعد ذلك يتم تنفيذ كل شيء بشكل مستقل. يتوافق هذا النهج مع "التنفيذ الفعلي للتبعيات" - هذا عندما لا تعرف الفئات التي يتم تضمين التبعيات فيها شيئًا عن كيفية حدوث ذلك ، أي أنها سلبية.
في العديد من اللغات ، يتم استخدام الطريقة التالية لهذا التطبيق: في فئات / ملفات منفصلة ، يتم وصف قواعد التنفيذ باستخدام بناء جملة اللغة ، وبعد ذلك يتم تجميعها وتنفيذها تلقائيًا. لا يوجد سحر - لا يحدث أي شيء تلقائيًا ، فقط المكتبات تتكامل بشكل وثيق مع الوسائل الأساسية للغة ، وتزيد من طرق الإنشاء. لذلك بالنسبة لـ Swift / Obj-C ، من المقبول عمومًا أن نقطة البداية هي UIViewController ، ويمكن للمكتبات دمج نفسها بسهولة في ViewController الذي تم إنشاؤه من لوحة العمل. صحيح ، إذا لم تستخدم لوحة العمل ، فسيتعين عليك القيام بجزء من العمل باستخدام الأقلام.
نعم ، لقد نسيت - إجابة السؤال الرئيسي ، "لماذا نحتاج إلى هذا؟" مما لا شك فيه ، يمكنك رعاية حقن التبعية بنفسك ، ووصف كل شيء مع الأقلام. ولكن تنشأ المشاكل عندما تصبح الرسوم البيانية كبيرة - عليك أن تذكر الكثير من الاتصالات بين الفئات ، يبدأ الكود في النمو بشكل كبير. لذلك ، فإن المكتبات التي تقوم تلقائيًا بتطبيق التبعيات العودية (وحتى الدورية) تأخذ هذه الرعاية على عاتقها وتحكم ، كمكافأة ، في حياتها. أي أن المكتبة لا تفعل شيئًا غير الطبيعي - فهي ببساطة تبسط حياة المطور. صحيح ، لا تعتقد أنه يمكنك كتابة مثل هذه المكتبة في يوم واحد - إنها شيء واحد أن تكتب بالقلم المكتبي جميع التبعيات لحالة معينة ، بل هو شيء آخر لتعليم الكمبيوتر أن ينفذ عالميا وبشكل صحيح.
تاريخ المكتبة
لن تكتمل القصة إذا لم أخبر القصة لفترة وجيزة. إذا كنت تتابع المكتبة من الإصدار التجريبي ، فلن تكون مثيرة للاهتمام بالنسبة لك ، ولكن بالنسبة لأولئك الذين يرونها للمرة الأولى ، أعتقد أنه من المفيد فهم كيف ظهرت والأهداف التي اتبعها المؤلف (أي أنا).
كانت المكتبة مشروعي الثاني الذي قررت ، لأغراض التعليم الذاتي ، أن أكتب في سويفت. قبل ذلك ، تمكنت من كتابة مسجل ، لكنني لم أحمله إلى المجال العام - إنه أفضل وأفضل.
ولكن مع شركة DI ، فإن القصة أكثر إثارة للاهتمام. عندما بدأت في القيام بذلك ، تمكنت من العثور على مكتبة واحدة فقط على Swift - Swinject. في ذلك الوقت ، كان لديها 500 نجم وحشرة لا تتم معالجتها بشكل طبيعي. نظرت إلى كل هذا و ... وصفت سلوكي بشكل أفضل من خلال العبارة المفضلة لدي "ثم عانى أوستاب" - مررت 5-6 لغات ، ونظرت إلى ما هو في هذه اللغات ، وقراءة المقالات حول هذا الموضوع وأدركت أنه يمكن القيام به بشكل أفضل. والآن ، بعد ما يقرب من ثلاث سنوات ، يمكنني أن أقول بثقة أن الهدف قد تحقق ، في الوقت الحالي ، تعد Ditranquillity الأفضل في نظري للعالم.
دعونا نفهم ما مكتبة DI جيدة:
- ينبغي أن توفر جميع التطبيقات الأساسية: المنشئ ، والخصائص ، والأساليب
- لا ينبغي أن يؤثر على قانون العمل.
- يجب أن تصف بوضوح ما حدث من خطأ.
- يجب أن تفهم مقدما حيث توجد أخطاء ، وليس في وقت التشغيل.
- يجب أن تكون متكاملة مع الأدوات الأساسية (القصة المصورة)
- يجب أن يكون بناء جملة موجزة وموجزة.
- يجب أن تفعل كل شيء بسرعة وكفاءة.
- (اختياري) يجب أن يكون هرميًا
هذه هي المبادئ التي أحاول الالتزام بها أثناء تطوير المكتبة.
ميزات وفوائد المكتبة
أولاً ، رابط إلى المستودع:
github.com/ivlevAstef/DITranquillityالميزة التنافسية الرئيسية ، التي تعد مهمة للغاية بالنسبة لي ، هي أن المكتبة تتحدث عن أخطاء بدء التشغيل. بعد بدء التطبيق واستدعاء الوظيفة المطلوبة ، سيتم الإبلاغ عن جميع المشاكل ، القائمة منها والمحتملة. هذا هو بالضبط معنى اسم "الهدوء" بالمكتبة - في الواقع ، بعد بدء البرنامج ، تضمن المكتبة وجود جميع التبعيات المطلوبة وعدم وجود دورات غير قابلة للحل. في الأماكن التي يوجد فيها غموض ، ستحذر المكتبة من احتمال وجود مشاكل محتملة.
هذا يبدو على ما يرام بالنسبة لي. لا توجد أي أعطال أثناء تنفيذ البرنامج ، إذا نسي المبرمج شيئًا ما ، فسيتم الإبلاغ عن ذلك فورًا.
يتم استخدام وظيفة سجل لوصف المشاكل ، والتي أوصي بشدة باستخدام. تسجيل لديه 4 مستويات: خطأ ، تحذير ، معلومات ، مطوّل. الثلاثة الأولى مهمة جدا. هذا الأخير غير مهم - يكتب كل ما يحدث - أي كائن تم تسجيله ، أي كائن بدأ تقديمه ، وما هو الكائن الذي تم إنشاؤه ، وما إلى ذلك.
ولكن هذه ليست كل المكتبة تفتخر:
- أمان كامل للخيط - يمكن إجراء أي عملية من أي خيط وسيعمل كل شيء. معظم الناس لا يحتاجون إلى ذلك ، لذلك فيما يتعلق بسلامة الخيوط ، تم العمل لتحسين سرعة التنفيذ. لكن مكتبة المنافس ، على الرغم من الوعود ، تنخفض إذا بدأت بالتسجيل واستلام شيء في نفس الوقت.
- سرعة التنفيذ السريع. على جهاز حقيقي ، DITranquillity هو ضعف سرعة منافسها. صحيح على جهاز محاكاة ، وسرعة التنفيذ يكافئ تقريبا. اختبار الارتباط
- حجم صغير - تزن المكتبة أقل من Swinject + SwinjectStoryboad + SwinjectAutoregistration ، ولكنها تفوق هذه الحزمة في القدرات
- ملاحظة موجزة ، موجزة ، على الرغم من الإدمان
- التسلسل الهرمي. بالنسبة للمشروعات الكبيرة ، التي تتكون من العديد من الوحدات ، تعد هذه ميزة كبيرة جدًا ، حيث أن المكتبة قادرة على العثور على الفئات اللازمة عن بُعد من الوحدة الحالية. أي إذا كان لديك تطبيقك الخاص لبروتوكول واحد في كل وحدة ، فستحصل في كل وحدة على التطبيق المطلوب دون بذل أي جهد
مظاهرة
لذلك دعونا نبدأ. كما آخر مرة سيتم النظر في المشروع:
SampleHabr . على وجه التحديد ، لم أبدأ في تغيير المثال - حتى تتمكن من مقارنة كيفية تغير كل شيء. والمثال يعرض العديد من ميزات المكتبة.
فقط في حالة عدم وجود سوء فهم ، نظرًا لأن المشروع معروض ، يستخدم العديد من الميزات. لكن لا أحد يزعج استخدام المكتبة بطريقة مبسطة - تم تنزيلها ، إنشاء حاوية ، تسجيل اثنين من الفئات ، استخدام الحاوية.
أولاً نحتاج إلى إنشاء إطار عمل (اختياري):
public class AppFramework: DIFramework {
وفي بداية البرنامج ، قم بإنشاء الحاوية الخاصة بك ، مع إضافة هذا الإطار:
let container = DIContainer()
القصة المصورة
بعد ذلك ، تحتاج إلى إنشاء شاشة أساسية. عادةً ما يتم استخدام Twitter لهذا الغرض ، وفي هذا المثال سأستخدمه ، ولكن لا أحد يزعجك استخدام UIViewControllers.
بادئ ذي بدء ، نحتاج إلى تسجيل لوحة العمل. للقيام بذلك ، قم بإنشاء "جزء" (اختياري - يمكنك كتابة جميع الكود في الإطار) مع لوحة العمل المسجلة فيه:
import DITranquillity class AppPart: DIPart { static func load(container: DIContainer) { container.registerStoryboard(name: "Main", bundle: nil) .lifetime(.single)
وإضافة جزء إلى AppFramework:
container.append(part: AppPart.self)
كما ترون ، تحتوي المكتبة على بناء جملة مناسب لتسجيل Storyboard ، وأنا أوصي بشدة باستخدامه. من حيث المبدأ ، يمكنك كتابة رمز مكافئ بدون هذه الطريقة ، لكنه سيكون أكبر ولن يكون قادرًا على دعم StoryboardReferences. وهذا هو ، لن تعمل هذه القصة المصورة من آخر.
الشيء الوحيد المتبقي الآن هو إنشاء لوحة عمل وإظهار شاشة البدء. يتم ذلك في AppDelegate ، بعد التحقق من الحاوية:
window = UIWindow(frame: UIScreen.main.bounds)
إنشاء لوحة العمل باستخدام مكتبة ليس أكثر تعقيدًا من المعتاد. في هذا المثال ، يمكن تفويت الاسم ، نظرًا لأن لدينا لوحة عمل واحدة فقط - ستكون المكتبة قد خمنت أنك وضعت في الاعتبار. ولكن في بعض المشاريع هناك الكثير من القصص المصورة ، لذلك لا تفوت الاسم مرة أخرى.
مقدم و ViewController
انتقل إلى الشاشة نفسها. لن نقوم بتحميل المشروع مع بنيات معقدة ، لكننا سنستخدم MVP المعتاد. علاوة على ذلك ، أنا كسول لدرجة أنني لن أقوم بإنشاء بروتوكول لمقدم. سيكون البروتوكول لاحقًا لفئة أخرى ، وهنا من المهم إظهار كيفية التسجيل وربط مقدم العرض و ViewController.
للقيام بذلك ، أضف التعليمات البرمجية التالية إلى AppPart:
container.register(YourPresenter.init) container.register(YourViewController.self) .injection(\.presenter)
هذه الخطوط الثلاثة سوف تسمح لنا بتسجيل فصلين ، وإقامة اتصال بينهما.
قد يتساءل الأشخاص الغريبون - لماذا يعد بناء الجملة الذي وضعه Swinject في مكتبة منفصلة هو الرئيسي في المشروع؟ تكمن الإجابة في الأهداف - بفضل بناء الجملة هذا ، تخزن المكتبة جميع الروابط مسبقًا ، بدلاً من حسابها في وقت التشغيل. يمنحك بناء الجملة هذا الوصول إلى العديد من الميزات غير المتوفرة للمكتبات الأخرى.
نبدأ التطبيق ، ويعمل كل شيء ، يتم إنشاء جميع الطبقات.
معطيات
حسنًا ، نحتاج الآن إلى إضافة فئة وبروتوكول لتلقي البيانات من الخادم:
public protocol Server { func get(method: String) -> Data? } class ServerImpl: Server { init(domain: String) { ... } func get(method: String) -> Data? { ... } }
وللجمال ، سننشئ فئة منفصلة من ServerPart DI للخادم ، حيث نقوم بتسجيلها. اسمحوا لي أن أذكركم بأن هذا ليس ضروريًا ويمكن تسجيله مباشرةً في الحاوية ، لكننا لا نبحث عن طرق سهلة :)
import DITranquillity class ServerPart: DIPart { static func load(container: DIContainer) { container.register{ ServerImpl(domain: "https:
في هذا الرمز ، كل شيء ليس شفافًا كما في الشفرات السابقة ، ويتطلب توضيحًا. أولاً ، داخل السجل الوظيفي ، يتم إنشاء فئة بمعلمة مرت.
ثانياً ، هناك وظيفة "as" - حيث تشير إلى أن الفصل سيكون متاحًا بنوع آخر - هو البروتوكول. الطرف الغريب لهذه العملية في شكل {{0 $} هو جزء من الاسم `check:`. وهذا يعني أن هذا الرمز يضمن أن ServerImpl هو خليفة لـ Server. ولكن هناك بناء جملة آخر: "باسم (Server.self)" والذي سيفعل نفس الشيء ، ولكن دون التحقق. لمعرفة ما الذي سينتج عن المترجم في كلتا الحالتين ، يمكنك إزالة تنفيذ البروتوكول.
قد يكون هناك العديد من وظائف "as" - وهذا يعني أن النوع متاح بأي من هذه الأسماء. أود أن ألفت انتباهكم إلى أن هذا سيكون تسجيلًا واحدًا ، مما يعني أنه إذا كانت الفئة مفردة ، فستكون النسخة نفسها متاحة لأي نوع محدد.
من حيث المبدأ ، إذا كنت تريد حماية نفسك من إمكانية إنشاء فئة حسب نوع التنفيذ ، أو أنك لم تعتاد بعد على بناء الجملة هذا ، فيمكنك الكتابة:
container.register{ ServerImpl(domain: "https://github.com/") as Server }
والتي ستكون معادلة لبعض ، ولكن دون القدرة على تحديد عدة أنواع منفصلة.
يمكنك الآن تنفيذ الخادم في مقدم العرض ، لذلك سنقوم بإصلاح مقدم العرض بحيث يقبل الخادم:
class YourPresenter { init(server: Server) { ... } }
نبدأ تشغيل البرنامج ، ويقع على وظائف "التحقق من الصحة" في AppDelegate ، مع ظهور رسالة تفيد بأنه لم يتم العثور على type `Server` ، ولكن مطلوب من قبل` YourPresenter`. ما الأمر؟ يرجى ملاحظة أن الخطأ حدث في بداية تنفيذ البرنامج ، وليس بعد حدوث الأمر الواقع. والسبب بسيط للغاية - لقد نسوا إضافة `ServerPart` إلى` AppFramework`:
container.append(part: ServerPart.self)
نبدأ - كل شيء يعمل.
مسجل
قبل ذلك ، كان هناك معرفة بفرص ليست مؤثرة للغاية والعديد منهم لديهم. الآن ستكون هناك مظاهرة أن المكتبات الأخرى في Swift لا تعرف كيف.
تم إنشاء
مشروع منفصل تحت المسجل.
أولا ، دعونا نفهم ما سيكون مسجل. للأغراض التعليمية ، لن نقوم بتنفيذ نظام مخادع ، وبالتالي فإن المسجل هو بروتوكول يحتوي على طريقة واحدة والعديد من التطبيقات:
public protocol Logger { func log(_ msg: String) } class ConsoleLogger: Logger { func log(_ msg: String) { ... } } class FileLogger: Logger { init(file: String) { ... } func log(_ msg: String) { ... } } class ServerLogger: Logger { init(server: String) { ... } func log(_ msg: String) { ... } } class MainLogger: Logger { init(loggers: [Logger]) { ... } func log(_ msg: String) { ... } }
المجموع ، لدينا:
- بروتوكول عام
- 3 تطبيقات تسجيل مختلفة ، كل منها يكتب إلى مكان مختلف
- مسجل مركزي واحد يستدعي وظيفة التسجيل لأي شخص آخر
أنشأ المشروع `LoggerFramework` و` LoggerPart`. لن أكتب رمزهم ، لكنني سأكتب فقط الأجزاء الداخلية لـ 'LoggerPart`:
container.register{ ConsoleLogger() } .as(Logger.self) .lifetime(.single) container.register{ FileLogger(file: "file.log") } .as(Logger.self) .lifetime(.single) container.register{ ServerLogger(server: "http://server.com/") } .as(Logger.self) .lifetime(.single) container.register{ MainLogger(loggers: many($0)) } .as(Logger.self) .default() .lifetime(.single)
لقد رأينا بالفعل التسجيلات الثلاثة الأولى ، والأخير يثير أسئلة.
يتم تمرير المعلمة إلى الإدخال. تم عرض سجل مماثل بالفعل عند إنشاء مقدم العرض ، على الرغم من وجود سجل مختصر - تم استخدام طريقة "init" فقط ، لكن لا أحد يكترث بالكتابة هكذا:
container.register { YourPresenter(server: $0) }
إذا كان هناك العديد من المعلمات ، فيمكن للمرء استخدام `$ 1` ،` $ 2` ، `$ 3` ، إلخ. حتى 16.
لكن هذه المعلمة تستدعي الدالة `many`. وهنا تبدأ المتعة. هناك نوعان من المعدلات `many` و` tag` في المكتبة.
النص المخفييوجد معدل "arg" ثالث ، لكنه غير آمن
يقول معدّل `many` أنك تحتاج إلى الحصول على جميع الكائنات المطابقة للنوع المطلوب. في هذه الحالة ، يُتوقع بروتوكول Logger ، بحيث يتم العثور على جميع الفئات الموروثة من هذا البروتوكول وإنشاءها ، مع استثناء واحد - في حد ذاته ، أي بشكل متكرر. لن ينشئ نفسه أثناء التهيئة ، على الرغم من أنه يمكنه القيام بذلك بأمان عند تنفيذه من خلال خاصية.
العلامة ، بدورها ، منفصلة عن أي نوع يجب تحديده أثناء الاستخدام وأثناء التسجيل. أي أن العلامات هي معايير إضافية إذا لم تكن هناك أنواع أساسية كافية.
يمكنك قراءة المزيد عن هذا:
Modifiersإن وجود المعدلات ، خاصة `الكثيرين` ، يجعل المكتبة أفضل من غيرها. على سبيل المثال ، يمكنك تطبيق نمط المراقب على مستوى مختلف تمامًا. بسبب هذه الأحرف الأربعة ، في المشروع ، كان من الممكن إزالة 30-50 سطرًا من التعليمات البرمجية من كل مراقب في المشروع وحل المشكلة بالسؤال - أين ومتى يجب إضافة كائنات إلى الملاحظة. العمل الواضح ليس هو التطبيق الوحيد ، ولكنه مهم.
حسنًا ، سننتهي من عرض الميزات عن طريق إدخال مسجل في YourPresenter:
container.register(YourPresenter.init) .injection { $0.logger = $1 }
هنا ، على سبيل المثال ، يتم كتابتها بشكل مختلف قليلاً عن ذي قبل - ويتم ذلك للحصول على مثال بناء جملة مختلف.
يرجى ملاحظة أن خاصية المسجل اختيارية:
internal var logger: Logger?
وهذا لا يظهر في بناء جملة المكتبة. على عكس الإصدار الأول ، تبدو جميع عمليات الكتابة المعتادة والاختيارية والإجبارية متماثلة. علاوة على ذلك ، يكون المنطق الداخلي مختلفًا - إذا كان النوع اختياريًا ، ولم يتم تسجيله في الحاوية ، فلن يتعطل البرنامج ، ولكن سيستمر التنفيذ.
النتائج
تتشابه النتائج مع آخر مرة ، وأصبح بناء الجملة فقط أقصر وأكثر فاعلية.
ما تمت مراجعته:
ماذا يمكن أن تفعل المكتبة؟
خطط
بادئ ذي بدء ، من المخطط التبديل إلى التحقق من الرسم البياني في مرحلة الترجمة - أي ، تكامل أوثق مع المترجم. هناك تنفيذ أولي باستخدام SourceKitten ، لكن هذا التنفيذ يواجه صعوبات خطيرة في الاستدلال النوعي ، لذلك من المخطط التحول إلى ملف تفريغ - في swift5 أصبح يعمل على مشاريع كبيرة. هنا أود أن أقول شكراً
لنيكيتوس على المساهمة الضخمة في هذا الاتجاه.
ثانياً ، أود أن أتكامل مع خدمات التصور. سيكون هذا مشروعًا مختلفًا قليلاً ، لكن يرتبط ارتباطًا وثيقًا بالمكتبة. ما هي النقطة؟ الآن تقوم المكتبة بتخزين الرسم البياني بالكامل للاتصالات ، أي نظريًا ، يمكن إظهار كل ما تم تسجيله في المكتبة على أنه مخطط لفئة / مكون UML. وسيكون من الجميل أن نرى هذا المخطط في بعض الأحيان.
تم التخطيط لهذه الوظيفة في جزأين - الجزء الأول سيسمح لك بإضافة API للحصول على جميع المعلومات ، والثاني هو بالفعل التكامل مع مختلف الخدمات.
أبسط خيار هو عرض رسم بياني للروابط في شكل نص ، لكنني لم أر خيارات قابلة للقراءة - إذا كان الأمر كذلك ، أقترح خيارات في التعليقات.
WatchOS - أنا نفسي لا أكتب مشاريع لهم. في حياته ، كتب مرة واحدة فقط ، ثم صغيرًا. ولكني أود أن أجعل التكامل مشدودًا ، كما هو الحال مع لوحة العمل.
هذا كل شيء شكرا لاهتمامكم. آمل حقًا الحصول على تعليقات وإجابات على الاستبيان.
عن نفسيإيفليف ألكسندر إفجينييفيتش - قائد فريق / كبير في فريق iOS. لقد عملت في مجال التجارة لمدة 7 سنوات ، بموجب نظام التشغيل iOS 4.5 سنوات - قبل ذلك كنت مطور برامج C ++. لكن تجربة البرمجة الكلية تزيد على 15 عامًا - في المدرسة تعرفت على هذا العالم المذهل ، وقد تأثرت به لدرجة أن هناك فترة تبادل فيها الألعاب والطعام والمرحاض وحلم لكتابة الكود. وفقًا لأحد مقالاتي ، يمكنك تخمين أنني أوليمبياد سابق - وبناءً عليه ، لم يكن من الصعب بالنسبة لي أن أكتب العمل ذي الكفاءة مع الرسوم البيانية. التخصص - أنظمة قياس المعلومات ، وفي وقت من الأوقات كنت مهووسًا بمراعاة التعددية والتوازي - نعم ، أكتب رمزًا أضع فيه افتراضات وأخطاء في مواضيع متشابهة ، لكنني أفهم مجالات المشكلات وأتفهم تمامًا أين يمكنك إهمال mutex ، وأين لا يستحق كل هذا العناء.