البرمجة الموجهة للبروتوكول ، الجزء 3

المادة الأخيرة على البرمجة الموجهة للبروتوكول.


في هذا الجزء ، سنبحث في كيفية تخزين ونسخ متغيرات النوع العام وكيف تعمل طريقة الإرسال معها.


نسخة غير مشاركة


protocol Drawable { func draw() } func drawACopy(local: Drawable) { local.draw() } let line = Line() drawACopy(line) let point = Point() drawACopy(point) 

كود بسيط جدا تأخذ drawACopy معلمة من النوع Drawable وتدعو إلى طريقة السحب - هذا كل شيء.


نسخة معممة


لنلقِ نظرة على الإصدار المعمم من الكود أعلاه:


 func drawACopy<T: Drawable>(local: T) { local.draw() } ... 

لا شيء يبدو أنه قد تغير. لا يزال drawACopy استدعاء وظيفة drawACopy ، drawACopy ، وليس أكثر من ذلك ، ولكن الأكثر إثارة للاهتمام كالمعتاد تحت الغطاء.
يحتوي الرمز المعمم على ميزتين مهمتين:


  1. تعدد الأشكال الاستاتيكي (المعروف أيضًا باسم حدودي)
  2. نوع محدد وفريد ​​في سياق المكالمة (يتم تعريف النوع العام T في وقت الترجمة)

النظر في هذا مع مثال:


 func foo<T: Drawable>(local: T) { bar(local) } func bar<T: Drawable>(local: T) { ... } let point = Point(...) foo(point) 

الجزء الأكثر إثارة للاهتمام يبدأ عندما نسميها وظيفة foo . يعرف المترجم نوع point المتغيرة بالضبط - إنه مجرد نقطة. علاوة على ذلك ، يمكن استنتاج النوع T: Drawable في الدالة foo بحرية بواسطة المترجم من اللحظة التي نمر فيها متغير من نوع Point المعروف إلى هذه الوظيفة: T = Point. جميع الأنواع معروفة في وقت التحويل البرمجي ويمكن للمترجم تنفيذ جميع التحسينات الرائعة - وأهم شيء هو foo الاتصال المباشر.


 This: ```swift let point = Point(...) foo<T = Point>(point) Becomes this: ```swift bar<T = Point>(point) 

يقوم المترجم ببساطة بتضمين استدعاء foo مع تنفيذه ويعرض النوع العام لشريط T: Drawable أيضًا. بمعنى آخر ، يقوم المترجم أولاً بتضمين استدعاء إلى الأسلوب foo بنوع T = Point ، ثم يقوم بتضمين نتيجة التضمين السابق - طريقة الشريط مع النوع T = Point.


تنفيذ الطرق العامة


 func drawACopy<T: Drawable>(local: T) { local.draw() } drawACopy(Point(...)) 

داخليًا ، يستخدم drawACopy Swift جدولًا لطريقة البروتوكول (يحتوي على جميع تطبيقات طريقة T) وجدول دورة حياة (يحتوي على جميع أساليب دورة الحياة لمثيل T). في الرمز الكاذب ، يبدو كما يلي:


 func drawACopy<T: Drawable>(local: T, pwt: T.PWT, vwt: T.VWT) {...} drawACopy(Point(...), Point.pwt, Point.vwt) 

VWT و PWT من الأنواع المرتبطة (المرتبطة) في T - كأسماء مستعارة للنوع (typealias) ، فقط أفضل. Point.pwt و Point.vwt هي خصائص ثابتة.


نظرًا لأن مثالنا T هو Point ، يتم تعريف T بشكل جيد ، وبالتالي ، فإن إنشاء الحاوية غير مطلوب. في الإصدار السابق drawACopy لـ drawACopy (محلي: Drawable) ، تم إنشاء حاوية وجودية حسب الحاجة - درسنا هذا في الجزء الثاني من المقالة.


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


مطلوب جدول دورة الحياة في وظائف عامة بسبب استخدام أساليب لمعلمات رمز عام.


معمم أم غير معمم؟


هل صحيح أن استخدام الأنواع العامة يجعل تنفيذ التعليمات البرمجية أسرع من استخدام أنواع البروتوكول فقط؟ هل الدالة المعممة func foo<T: Drawable>(arg: T) أسرع من نظيرها fun foo(arg: Drawable) -like fun foo(arg: Drawable) ؟


لقد لاحظنا أن الشفرة العامة تعطي شكلاً ثابتًا من تعدد الأشكال. يتضمن أيضًا تحسينات برنامج التحويل البرمجي تسمى "التخصص رمز عام". لنرى:


مرة أخرى لدينا نفس الرمز:


 func drawACopy<T: Drawable>(local: T) { local.draw() } drawACopy(Point(...)) drawACopt(Line(...)) 

تخصص وظيفة عامة يخلق نسخة مع أنواع عامة متخصصة من هذه الوظيفة. على سبيل المثال ، إذا drawACopy بـ drawACopy مع متغير من النوع Point ، فسيقوم المترجم بإنشاء نسخة متخصصة من هذه الوظيفة - drawACopyOfPoint (local: Point) ، ونحصل على:


 func drawACopyOfPoint(local: Point) { local.draw() } func drawACopyOfLine(local: Line) { local.draw() } drawACopy(Point(...)) drawACopt(Line(...)) 

ما الذي يمكن تقليله عن طريق برنامج التحويل البرمجي الخام قبل هذا:


 Point(...).draw() Line(...).draw() 

تتوفر كل هذه الحيل لأنه لا يمكن استدعاء الوظائف العامة إلا إذا تم تعريف جميع الأنواع العامة - في طريقة drawACopy يتم تعريف النوع العام (T) جيدًا.


خصائص مخزنة عامة


النظر في زوج بسيط الهيكل:


 struct Pair { let fst: Drawable let snd: Drawable } let pair = Pair(fst: Line(...), snd: Line(...)) 

عندما نستخدم هذا بهذه الطريقة ، نحصل على تخصيصين على الكومة (ظروف الذاكرة الدقيقة في هذا السيناريو موصوفة في الجزء الثاني) ، لكن يمكننا تجنب ذلك بمساعدة تعليمات برمجية معممة.


الإصدار العام من زوج يشبه هذا:


 struct Pair<T: Drawable> { let fst: T let snd: T } 

من اللحظة التي يتم فيها تعريف النوع T في الإصدار المعمم ، فإن أنواع الخصائص fst و snd نفسها ويتم تعريفها أيضًا. نظرًا لتعريف النوع ، يمكن لبرنامج التحويل البرمجي تخصيص مقدار مخصص من الذاكرة لهاتين الخصائص - fst و snd .


بمزيد من التفاصيل حول مقدار الذاكرة المخصص:


عندما نعمل مع إصدار fst من Pair ، تكون أنواع الخصائص fst و snd قابلة للرسم. أي نوع يمكن أن يتوافق مع Drawable ، حتى لو كان يستغرق 10 كيلو بايت من الذاكرة. بمعنى أن Swift لن يكون قادرًا على استخلاص استنتاج حول حجم هذا النوع وسيستخدم موقع ذاكرة عالمي ، على سبيل المثال ، حاوية وجودية. يمكن تخزين أي نوع في هذه الحاوية. في حالة الشفرة العامة ، يتم التعرف على النوع جيدًا ، كما يمكن التعرف على الحجم الفعلي للخصائص ، ويمكن لـ Swift إنشاء موقع ذاكرة مخصص. على سبيل المثال (الإصدار المعمم):


 let pair = Pair(Point(...), Point(...)) 

اكتب T هو الآن نقطة. نقطة يأخذ N بايت من الذاكرة وفي الزوج نحصل على اثنين منهم. سوف سويفت تخصيص 2 * N كمية الذاكرة ووضع pair هناك.


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


استنتاج


1. الرمز العام المتخصص - أنواع القيمة


لديه أفضل سرعة تنفيذ ، حيث:


  • لا تخصيص كومة عند النسخ
  • رمز عام - تكتب وظيفة لنوع متخصص
  • لا مرجع العد
  • إرسال ثابت الأسلوب

2. الرمز المعمم المتخصص - أنواع المراجع


لديها متوسط ​​سرعة التنفيذ ، حيث:


  • التخصيصات لكل كومة عند إنشاء مثيل
  • هناك عدد مرجعي
  • تقديم طريقة ديناميكية عبر الجدول الافتراضي

3. رمز عام غير متخصص - قيم صغيرة


  • لا تخصيص كومة الذاكرة المؤقتة - يتم وضع القيمة في المخزن المؤقت قيمة الحاوية الوجودية
  • لا يوجد مرجع مرجعي (حيث لا يوجد شيء على الكومة)
  • إرسال طريقة ديناميكية عبر جدول طريقة البروتوكول

4. رمز عام غير متخصص - قيم كبيرة


  • الموضع على الكومة - يتم وضع القيمة في المخزن المؤقت للقيم
  • هناك عدد مرجعي
  • إيفاد ديناميكي عبر جدول طريقة البروتوكول

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


وكذلك لا تدفع مقابل الديناميكية عندما لا تحتاجها . ابحث عن التجريد الصحيح مع أقل متطلبات وقت التشغيل.


  • الأنواع الهيكلية - دلالات المعاني
  • أنواع الصف - الهوية
  • رمز المعمم - تعدد الأشكال ثابت
  • أنواع البروتوكول - تعدد الأشكال الديناميكي

استخدام التخزين غير المباشر للعمل مع القيم الكبيرة.


ولا تنس - إنها مسؤوليتك لاختيار الأداة الصحيحة.
شكرا لاهتمامكم بهذا الموضوع. نأمل أن تكون هذه المقالات قد ساعدتكم وكانت ممتعة.


حظا سعيدا

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


All Articles