سويفت تحت الغطاء: تطبيق عام

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

كل من كتب على سويفت يستخدم الوراثة. Array ، Dictionary ، Set - الخيارات الأساسية لاستخدام الأدوية من المكتبة القياسية. كيف يتم تمثيلهم في الداخل؟ دعونا نرى كيف يتم تطبيق هذه الميزة الأساسية للغة من قبل مهندسي Apple.


يمكن أن تكون المعلمات العامة محدودة بواسطة البروتوكولات أو غير محدودة ، على الرغم من أنه يتم استخدام الوراثة بشكل أساسي بالاقتران مع البروتوكولات التي تصف ما يمكن القيام به بالضبط مع معلمات الطريقة أو حقول الكتابة.


لتطبيق الأدوية العامة ، يستخدم Swift طريقتين:


  1. وقت التشغيل - الشفرة العامة عبارة عن غلاف (Boxing).
  2. طريقة Compiletime - يتم تحويل الشفرة العامة إلى نوع معين من التعليمات البرمجية لتحسين (التخصص).

الملاكمة


النظر في طريقة بسيطة مع المعلمة العامة بروتوكول غير محدود:


 func test<T>(value: T) -> T { let copy = value print(copy) return copy } 

يقوم المحول البرمجي السريع بإنشاء كتلة واحدة من التعليمات البرمجية التي سيتم استدعاؤها للعمل مع أي <T> . أي بغض النظر عما إذا كنا نكتب test(value: 1) أو test(value: "Hello") ، سيتم استدعاء الرمز نفسه وسيتم نقل معلومات إضافية حول نوع <T> يحتوي على جميع المعلومات اللازمة إلى الطريقة. .


لا يمكن فعل الكثير باستخدام مثل هذه المعلمات غير المحدودة للبروتوكول ، ولكن لتنفيذ هذه الطريقة بالفعل ، يجب عليك معرفة كيفية نسخ المعلمة ، وتحتاج إلى معرفة حجمها من أجل تخصيص ذاكرة لها في وقت التشغيل ، تحتاج إلى معرفة كيفية تدميرها عندما تغادر المعلمة الحقل الرؤية. يتم استخدام Value Witness Table ( VWT ) لتخزين هذه المعلومات. يتم إنشاء VWT في مرحلة التجميع لجميع الأنواع ويضمن المترجم أنه في وقت التشغيل سيكون هناك فقط مثل هذا الشكل للكائن. اسمحوا لي أن أذكرك بأن الهياكل في Swift يتم تمريرها حسب القيمة ، والفئات حسب المرجع ، لذلك سيتم القيام بأشياء مختلفة let copy = value مع T == MyClass و T == MyStruct .


جدول الشهود القيمة

بمعنى أن استدعاء طريقة test بتمرير البنية المعلنة هناك سيبدو في النهاية مثل هذا:


 //  ,  metadata   let myStruct = MyStruct() test(value: myStruct, metadata: MyStruct.metadata) 

تصبح الأمور أكثر تعقيدًا عندما يكون MyStruct بحد ذاته هيكلًا عامًا ويأخذ شكل MyStruct<T> . اعتمادًا على <T> داخل MyStruct ، ستكون البيانات التعريفية و VWT مختلفة عن الأنواع MyStruct<Int> و MyStruct<Bool> . هذان هما نوعان مختلفان في وقت التشغيل. لكن إنشاء البيانات الوصفية لكل مجموعة ممكنة من MyStruct و T فعال للغاية ، لذلك فإن Swift يذهب في الاتجاه الآخر ، وفي هذه الحالات تقوم بإنشاء البيانات الأولية في وقت التشغيل أثناء التنقل. ينشئ المحول البرمجي نمط بيانات تعريف واحدًا للهيكل العام ، والذي يمكن دمجه بنوع معين ، ونتيجة لذلك ، يتلقى معلومات الكتابة الكاملة في وقت التشغيل مع VWT الصحيح.


 //   ,  metadata   func test<T>(value: MyStruct<T>, tMetadata: T.Type) { //       let myStructMetadata = get_generic_metadata(MyStruct.metadataPattern, tMetadata) ... } let myStruct = MyStruct<Int>() test(value: myStruct) //   test(value: myStruct, tMetadata: Int.metadata) //      

عندما نجمع المعلومات ، نحصل على البيانات الوصفية التي يمكننا التعامل معها (نسخ ، نقل ، إتلاف).


لا يزال الأمر أكثر تعقيدًا عند إضافة قيود البروتوكول إلى الأدوية العامة. على سبيل المثال ، Equatable <T> بروتوكول Equatable . فليكن طريقة بسيطة جدًا تقارن بين الوسيطتين اللتين تم تمريرهما. والنتيجة هي مجرد غلاف على طريقة المقارنة.


 func isEquals<T: Equatable>(first: T, second: T) -> Bool { return first == second } 

لكي يعمل البرنامج بشكل صحيح ، يجب أن يكون لديك مؤشر لطريقة المقارنة static func ==(lhs:T, rhs:T) . كيف تحصل عليه؟ من الواضح أن نقل VWT ليس كافيًا ، فهو لا يحتوي على هذه المعلومات. لحل هذه المشكلة ، هناك Protocol Witness Table أو PWT . تشبه هذه VWT ويتم إنشاؤها في مرحلة VWT للبروتوكولات وتصف هذه البروتوكولات.


 isEquals(first: 1, second: 2) //   //     isEquals(first: 1, // 1 second: 2, metadata: Int.metadata, // 2 intIsEquatable: Equatable.witnessTable) // 3 

  1. مرت حجتان
  2. تمرير بيانات التعريف لـ Int حتى تتمكن من نسخ / نقل / تدمير الكائنات
  3. نمرر المعلومات التي تنفذها Int .

إذا تطلب التقييد تنفيذ بروتوكول آخر ، على سبيل المثال ، T: Equatable & MyProtocol ، MyProtocol إضافة معلومات حول MyProtocol باستخدام المعلمة التالية:


 isEquals(..., intIsEquatable: Equatable.witnessTable, intIsMyProtocol: MyProtocol.witnessTable) 

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


التخصص العام


للقضاء على الحاجة غير الضرورية للحصول على معلومات أثناء تنفيذ البرنامج ، تم استخدام ما يسمى نهج التخصص العام. يسمح لك باستبدال برنامج تغليف عام بنوع معين مع تطبيق معين. على سبيل المثال ، بالنسبة إلى isEquals(first: 1, second: 2) إلى isEquals(first: 1, second: 2) و isEquals(first: "Hello", second: "world") ، بالإضافة إلى تطبيق "wrapper" الرئيسي ، نسختان إضافيتان مختلفتان تمامًا من طريقة Int و لسلسلة.


شفرة المصدر


أولاً ، قم بإنشاء ملف generic.swift واكتب وظيفة عامة صغيرة سنأخذها في الاعتبار.


 func isEquals<T: Equatable>(first: T, second: T) -> Bool { return first == second } isEquals(first: 10, second: 11) 

أنت الآن بحاجة إلى فهم ما يتحول في النهاية إلى مترجم.
يمكن رؤية ذلك بوضوح عن طريق تجميع ملف SWIFT الخاص بنا بلغة Swift Intermediate Language أو SIL .


قليلا عن SIL وعملية تجميع


SIL هو نتيجة واحدة من عدة مراحل تجميع سريع.


خط أنابيب المترجم

يتم تمرير التعليمات البرمجية المصدر .swift إلى Lexer ، مما ينشئ شجرة بناء جملة مجردة ( AST ) للغة ، بناءً على نوع التحقق والتحليل الدلالي للشفرة الذي يتم تنفيذه. يقوم SilGen بتحويل AST إلى SIL ، والذي يطلق عليه raw SIL ، على أساس الكود الذي تم تحسينه canonical SIL الحصول على canonical SIL المحسن ، والذي يتم تمريره إلى IRGen لتحويله إلى IR - تنسيق خاص يفهمه LLVM ، والذي سيتم تحويله إلى , . , . SIL`.


ومرة أخرى للأدوية


إنشاء ملف SIL من التعليمات البرمجية المصدر لدينا.


 swiftc generic.swift -O -emit-sil -o generic-sil.s 

نحصل على ملف جديد بالملحق *.s . بالنظر إلى الداخل ، سنرى رمزًا أقل قابلية للقراءة من النص الأصلي ، ولكن لا يزال واضحًا نسبيًا.


سيل الخام

ابحث عن السطر الذي يحتوي على التعليق // isEquals<A>(first:second:) . هذه هي بداية وصف طريقتنا. ينتهي بـ تعليق // end sil function '$s4main8isEquals5first6secondSbx_xtSQRzlF' . قد يكون اسمك مختلفًا قليلاً. دعنا نحلل وصف الطريقة قليلا.


  • %0 و %1 على السطر 21 هما المعلمتان first second ، على التوالي
  • في السطر 24 ، نحصل على معلومات الكتابة ونمررها إلى %4
  • في السطر 25 ، نحصل على مؤشر لطريقة المقارنة من معلومات النوع
  • on line 26 نسميها الطريقة عن طريق المؤشر ، ونمررها المعلمات ونوع المعلومات
  • على الخط 27 نعطي النتيجة.

نتيجة لذلك ، نرى: من أجل تنفيذ الإجراءات اللازمة في تنفيذ الطريقة العامة ، نحتاج إلى الحصول على معلومات من وصف نوع <T> أثناء تنفيذ البرنامج.


ننتقل مباشرة إلى التخصص.


في ملف SIL isEquals ، مباشرة بعد الإعلان عن الأسلوب isEquals العام ، يلي إعلان المتخصص للنوع Int .



المتخصصة سيل

على السطر 39 ، بدلاً من الحصول على الطريقة في وقت التشغيل من معلومات النوع ، "cmp_eq_Int64" استدعاء طريقة مقارنة الأعداد الصحيحة "cmp_eq_Int64" الفور.


من أجل طريقة "التخصص" ، يجب تمكين التحسين . تحتاج أيضا إلى معرفة ذلك


لا يمكن للمحسن إجراء التخصص إلا إذا كان تعريف التعريف العام مرئيًا في الوحدة النمطية الحالية ( المصدر )

بمعنى ، لا يمكن تخصيص الطريقة بين وحدات Swift المختلفة (على سبيل المثال ، الطريقة العامة من مكتبة Cocoapods). استثناء من ذلك هو مكتبة Swift القياسية ، والتي فيها أنواع أساسية مثل Array و Set و Dictionary . جميع الوراثة من المكتبة الأساسية تتخصص في أنواع محددة.


ملاحظة: تم @inlinable السمات @inlinable و @usableFromInline في Swift 4.2 ، مما يسمح للمحسن برؤية مجموعات الأساليب من الوحدات النمطية الأخرى ويبدو أن هناك فرصة @usableFromInline ، لكن لم يتم اختبار هذا السلوك من قِبلي ( المصدر )


مراجع


  1. وصف الأدوية
  2. الأمثل في سويفت
  3. عرض أكثر تفصيلا وتعمقا حول هذا الموضوع.
  4. المادة الانجليزية

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


All Articles