سحر SwiftUI أو حول منشئي الوظيفة


هل حاولت إضافة أكثر من 10 طرق عرض إلى VStack؟


 var body: some View { VStack { Text("Placeholder1") Text("Placeholder2") // ...    3  10 . . . Text("Placeholder11") } } 

حاولت - أنها لا تجمع. نعم ، لقد فوجئت في البداية ودخلت في دراسة منتدى سويفت وجيثب. كانت نتيجة دراستي - "لا تزال غير مجمّعة ¯\_(ツ)_/¯ ". لكن مهلا ، دعنا نرى لماذا.


بناء وظيفة


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


إذن ما هذا؟


من الصعب وصف ذلك باختصار ، ولكن باختصار - هذه هي الآلية التي تتيح لك سرد الحجج ، وبعض المحتوى في نص الكنز ، وإعطاء نتيجة عامة من كل هذا.
اقتباس من الاقتراح: SE-XXXX :


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

الأصلي
الفكرة الأساسية هي أننا نأخذ نتائج التعبير "المتجاهلة" لمجموعة من العبارات - بما في ذلك في المواضع المتداخلة مثل نصوص بيانات التبديل - وننشئها في قيمة نتيجة واحدة تصبح قيمة الإرجاع للدالة الحالية. يتم التحكم في طريقة تنفيذ هذه المجموعة بواسطة باني afunction ، وهو نوع جديد من أنواع السمة المخصصة فقط ؛

تسمح لك هذه الآلية بكتابة شفرة تشبه الشجرة ، بدون أحرف ترقيم غير ضرورية:


 let myBody = body { let chapter = spellOutChapter ? "Chapter" : "" div { if useChapterTitles { h1(chapter + "1. Loomings.") } p { "Call me Ishmael. Some years ago" } p { "There is now your insular city" } } } 

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


لماذا هذا؟


لذا تريد Apple تقديم الدعم لـ DSL بلغة المجال المحددة .
يشير John McCall و Doug Gregor إلى أن مثل هذا الرمز أسهل في القراءة والكتابة - وهذا يبسط بناء الجملة ، ويجعله أكثر إيجازًا ، ونتيجة لذلك ، يصبح الرمز أكثر دعمًا. في الوقت نفسه ، لاحظوا أن حلهم ليس DSL عالميًا.
يركز هذا الحل على مجموعة محددة من المشكلات ، بما في ذلك وصف الهياكل الخطية والأشجار مثل XML ، JSON ، عرض التسلسلات الهرمية ، وما إلى ذلك.


كيف تعمل معها؟


يمكنك إنشاء أداة إنشاء الوظائف الخاصة بك ، فقد كان من الأسهل بالنسبة لي أن أفهم كيف تعمل. النظر في مثال بدائي لباني أن يسلسل السلاسل.


 // 1.  Builder @_functionBuilder struct MyBuilder { static func buildBlock(_ atrs: String...) -> String { return atrs.reduce("", + ) } } 

 // 2.          func stringsReduce(@MyBuilder block: () -> String) -> String { return block() } 

 // 3.     let result = stringsReduce { "1" "2" } print(result) // "12" 

تحت الغطاء ، هذا سوف ينجح مثل هذا:


 let result = stringsReduce { return MyBuilder.build("1", "2") } 

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


 static func buildBlock(_ <*atrs*>: <*String*>...) -> <*String*> 

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


 stringsReduce { if .random() { //   Bool "one string" } else { "another one" } "fixed string" } 

لدعم بناء الجملة هذا ، يحتاج المنشئ إلى تطبيق buildEither(first:/second:)


 static func buildEither(first: String) -> String { return first } static func buildEither(second: String) -> String { return second } 

استجابة المجتمع


والشيء المضحك هو أن هذا لم يتم بعد في Swift 5.1 ، أي أن طلب السحب مع هذه الميزة لم يتم سكبه بعد ، ولكن مع ذلك ، أضافته Apple بالفعل إلى الإصدار التجريبي Xcode 11. وفي Function builders → Pitches → Swift Forums ، يمكنك رؤية رد فعل المجتمع على هذا الاقتراح.


ViewBuilder


عُد الآن إلى VStack وانظر وثائق التهيئة الخاصة ببرنامج التهيئة الأولي (المحاذاة: التباعد: content :) .
يبدو مثل هذا:


 init(alignment: HorizontalAlignment = .center, spacing: ? = nil, @ViewBuilder content: () -> Content) 

وقبل جدول المحتويات ، هناك سمة مخصصة ViewBuilder
أعلن على النحو التالي:


 @_functionBuilder public struct ViewBuilder { /// Builds an empty view from an block containing no statements, `{ }`. public static func buildBlock() -> EmptyView /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) through /// unmodified. public static func buildBlock<Content>(_ content: Content) -> Content where Content : View } 

سمة المستخدم هي @ _functionBuilder ، والتي يتم تسجيلها في بداية الإعلان الخاص بها.


وإذا نظرت إلى الوثائق أقل ، فيمكنك رؤية العديد من أساليب buildBlock الثابتة التي تختلف في عدد الوسائط.


وهذا يعني أن رمز العرض


 var body: some View { VStack { Text("Placeholder1") Text("Placeholder2") Text("Placeholder3") } } 

تحت غطاء محرك السيارة يتم تحويلها إلى مثل هذا


  var body: some View { VStack(alignment: .leading) { ViewBuilder.buildBlock(Text("Placeholder1"), Text("Placeholder2"), Text("Placeholder3")) } } 

أي يلبي buildBlock (:: _ :) طريقة البناء.


من القائمة بأكملها ، تكون الطريقة ذات الحد الأقصى لعدد الوسائط هي guy buildBlock (:::::::::: :) :) (10 وسائط):


 extension ViewBuilder { public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View, C8 : View, C9 : View } 

وبناءً على ذلك ، عند العودة إلى المثال الأصلي ، عندما تحاول رفع VStack وإحدى عشرة طريقة عرض داخلية ، يحاول المترجم العثور على طريقة ViewBuilder'a buildBlock ، التي تحتوي على 11 وسيطة إدخال. ولكن لا توجد مثل هذه الطريقة: ومن هنا جاء خطأ التحويل البرمجي.
هذا صحيح بالنسبة لجميع المجموعات التي تستخدم التهيئة مع السمةViewBuilder في أداة التهيئة: V | H | Z-Stack ، و List ، و Group ، وغيرها ، والتي يمكنك من خلالها إعلان أكثر من عرض كعد.
وهذا محزن.


MEM (آسف ، لم أجد ميمي لائق)


كيف تكون


يمكننا التحايل على هذا القيد باستخدام ForEach


 struct TestView : View { var body: some View { VStack { ForEach(texts) { i in Text(«\(i)») } } } var texts: [Int] { var result: [Int] = [] for i in 0...150 { result.append(i) } return result } } 

أو مجموعات التداخل:


 var body: some View { VStack { VStack { Text("Placeholder_1") Text("Placeholder_2") //   8 } Group { Text("11") Text("12") //   8 } } } 

لكن مثل هذه القرارات تبدو وكأنها عكازات وليس هناك سوى أمل لمستقبل أكثر إشراقا. ولكن ما هو هذا المستقبل؟
سويفت بالفعل المعلمات المتغير . هذه هي قدرة طريقة على قبول الوسائط بالتعداد. على سبيل المثال ، تسمح لك طريقة print المعروفة للجميع بكتابة كل من print(1, 2) print(1, 2, 3, 4) وهذا بدون حمولات زائدة غير ضرورية من الطريقة.


 print(items: Any...) 

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


  static func buildBlock<…Component>(Component...) -> TupleView<(Component...)> where Component: View 

وأبل ملزمة ببساطة لإضافة هذا. كل شيء يقع ضد هذه الآلية الآن. ويبدو لي أنهم ببساطة لم يتمكنوا من الانتهاء منه بحلول WWDC 2019 (ولكن هذه مجرد تكهنات).

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


All Articles