تطوير التطبيقات الأصيلة لطالما شكّل تحديًا أكبر مقارنةً بتطوير الويب، خاصةً بالنسبة لنماذج اللغات الكبيرة (LLMs). تتميز التطبيقات بمعايير وتخطيطات أسلوب خاصة بكل منصة، ويجب أن تمنح المستخدم شعورًا أصيلاً ومصممًا خصيصًا لجهازه. علاوة على ذلك، يتمتع الويب بانفتاح وعالمية لا تضاهيهما التطبيقات ومتاجرها، مما يعني أن LLMs تفتقر نسبيًا إلى بيانات تدريب كافية في هذا المجال. لقد حاولت العديد من منصات الترميز التوليدي معالجة هذه المشكلة عن طريق تقديم حلول وسط، مثل إنشاء تطبيقات باستخدام Expo/React Native أو Flutter، أو حتى مجرد عروض ويب. لكننا لم نجد أن أيًا من هذه الأساليب يوفر تجربة مستخدم مثالية، إذ دائمًا ما تضحي بشيء ما، سواء كان ذلك التوافق عبر المنصات، أو الأدوات الأصلية، أو الأداء، أو الميزات. لذلك، صُمم Dactyl لمساعدتك في بناء تطبيقات أصلية دون هذه التنازلات.
اليوم، نكشف الستار عن الآليات الداخلية لمُحرِّك Dactyl لعرض SwiftUI عبر المنصات، والذي يمثل منفذ Wasm لمُحاكي iOS، وكيف نتمكن من جلب بيئة Apple المتكاملة إلى أجهزة Android والويب.
معاينات iOS مباشرة في المتصفح
اخترنا Swift كلغة أساسية في Dactyl لبساطتها ودلالات التخطيط المحددة جيدًا. في الواقع، صُمم Dactyl في البداية ليكون منصة تطوير حصرية لنظام iOS. عادةً، تتطلب معاينة تطبيق iOS دون جهاز Mac استئجار جهاز فعلي وبث محاكي حقيقي، مع عمليات تمهيد وتثبيت وتشغيل تستغرق وقتًا طويلاً قبل أن تتمكن من رؤية التطبيق وتشغيله. هذا النهج مكلف ولا يمكن توسيعه إلا بحدود ميزانيتك. ولكن ماذا لو كان بإمكانك الحصول على مُحاكي داخل متصفحك مباشرةً؟ حسنًا، هذا من شأنه أن يجعله قابلًا للتوسع بلا حدود، وهذا ما قمنا ببنائه.
يُعد محرك المعاينة الخاص بـ Dactyl بمثابة إعادة تنفيذ لـ SwiftUI في Swift، بالإضافة إلى الأطر البرمجية التي تستخدمها التطبيقات. يتم تنفيذ العديد من الوحدات، بما في ذلك Charts وSpriteKit وSceneKit وRealityKit وARKit وMapKit وStoreKit وMusicKit وPencelKit وWidgetKit وUIKit وCoreGraphics وMetal وVision. يتم بناء التطبيقات لتعمل على Wasm وربطها بمُحرِّك SwiftUI الخاص بنا بدلًا من ذلك الخاص بشركة Apple.
يُصدّر المحرك تدفقًا ثنائيًا من أوامر الرسم إلى ذاكرته الخطية الخاصة، ويقوم مضيف JavaScript بقراءة هذا البروتوكول ورسمه على Canvas2D. يتم حل التخطيط بالكامل قبل كتابة أي أمر، ويحتفظ المُصالح (reconciler) بشجرة العرض بين التمريرات، مع وضع علامة على طرق العرض التي تتطلب تحديثًا في هذا الإطار فقط (مثل الرسوم المتحركة وحاويات التمرير وما شابه). يقوم المضيف برسم كل طبقة مرة واحدة على لوحة قماشية خارج الشاشة مرتبطة بتجزئة محتوياتها، ثم يتم تركيبها على إطارات لاحقة. كل هذا يعني أن Dactyl يمكنه عرض تخطيطات SwiftUI المعقدة بسلاسة وكفاءة، مباشرةً في المتصفح، دون أي خطوة تثبيت أو أجهزة إضافية.
الربط الديناميكي في Swift
تستخدم SwiftWasm الروابط الثابتة، وهذا ليس مثاليًا بالنسبة لنا، لأن حجم المحرك يقارب 100 ميجابايت، والربط الثابت يعني ربط وشحن مئات الميجابايت إلى المتصفح مع كل تعديل. التطبيق نفسه أصغر بكثير، لذلك قمنا بتقسيم البنية.
يرتبط المحرك والمكتبة القياسية مرة واحدة كوحدة أساسية (Base Module)، والتي تُعالج المحتوى. بهذا، يقوم المتصفح بجلبها وتجميعها مرة واحدة والاحتفاظ بها عبر التعديلات. بينما يتم تجميع تطبيقك كوحدة جانبية مستقلة عن الموضع (Side Module) بحد ذاتها، وتتراوح أحجامها من 8 كيلو بايت إلى 300 كيلو بايت. عند التحميل، يتم وضعها في إزاحة ذاكرة وقت التشغيل، وترتبط وارداتها (imports) بصادرات الوحدة الأساسية، ويتم تسجيل بيانات تعريف نوع Swift الخاصة بها في وقت تشغيل الوحدة الأساسية. يحدث التحديث السريع (hot swapping) ضمن التطبيق قيد التشغيل. يتم التقاط لقطة لـ @State قبل التحديث واستعادتها بعده، مما يحافظ على موضع التمرير وحقول النص وبيانات التطبيق عبر عمليات التحرير.
دعم Android عبر Jetpack Compose
على نظام Android، يتم تجميع المحرك بشكل أصيل لـ aarch64-unknown-linux-android24 باستخدام Swift Android SDK إلى مكتبة مشتركة. يقوم مضيف Jetpack Compose بتحميلها وتشغيلها عبر JNI. يتم قراءة تدفق الأوامر مباشرة من الذاكرة الأصلية، دون نسخ، مما يعني أن مضيف Compose يفك تشفير نفس وحدات البايت التي يفك تشفيرها المتصفح.
عندما يرسم مضيف المتصفح عنصر تحكم على اللوحة القماشية (Canvas)، يقوم مضيف Compose بتثبيت عنصر تحكم مادي حقيقي، مثل حقل نص أو منتقي تاريخ، في المستطيل المحدد، مع التركيز الأصيل والتموج ولوحة المفاتيح وإمكانية الوصول. يعمل كلا الهاتفين أدناه بنفس مصدر SwiftUI المكون من 71 سطرًا: اليسار يمثل محاكي iPhone، واليمين هو ملف APK يعمل على محاكي Android.
تُعد رموز SF ورموز Material مجموعات أيقونات مختلفة بأسماء مختلفة، لذلك يُغطي جدول تعيين مُنشأ كلا المضيفين. يتم تثبيت كثافة Compose على مقياس تخطيط المحرك بدلًا من الجهاز، لذا تقع عناصر التحكم تمامًا على المستطيلات التي حلها المحرك. لا تقوم ملفات APK لكل تطبيق بإعادة بناء المضيف؛ بل يتم دمج المكتبة المشتركة واسم التطبيق والرمز في قالب مُنشأ مسبقًا. يُعد دعم iPad مماثلًا، حيث يستخدم نفس المحرك مع واصف جهاز مختلف، بينما تتولى تخطيطات SwiftUI التكيفية التعامل مع التغييرات في إطار العرض والمناطق الآمنة وفئات الحجم.
حلقة التطوير الفعالة
يتم تطوير المحرك بشكل آلي للغاية باستخدام أسطول من الوكلاء (Agents). يستخدم هؤلاء الوكلاء عناصر SDK الخاصة بشركة Apple لإنشاء مرجع واجهة برمجة التطبيقات (API). نقوم بعد ذلك بعرض مجموعة متنوعة من المشاهد في مُحاكي iOS، والتقاطها، ثم تحديد الاختلافات بكسلًا تلو الآخر بينها وبين مخرجات المحرك. وعندما يتم إصدار نظام iOS جديد، نعيد عرض المصدر الحقيقي مقابل المُحاكي الجديد.
تعمل هذه الوكلاء باستمرار على مصدر إطار عمل المحرك، مما يتيح التحرير السريع والفعال. إليك بعض الأمثلة للملفات التي تُعدّل حاليًا أو تم تعديلها مؤخرًا:
- Charts/ChartRender.swift
- SwiftUI/Core/ShapeStyle.swift
- SwiftUI/Modifiers/TextLayoutModifiers.swift
- SwiftUI/Generated/LayoutParams.swift
- SwiftUI/Core/ColorGradient.swift
- Charts/ChartPlots.swift
- SwiftUI/Modifiers/ScrollModifiers.swift
- SwiftUI/Core/SemanticColors.swift
- UIKit/GestureScrollControl.swift
- Charts/ChartViewModifiers.swift
- SwiftUI/Core/Animatable.swift
- CoreGraphics/CGPath.swift
- … والمزيد
تفتقر لقطات الشاشة إلى أي حركة، لذلك يقوم مشغل شريط الأفلام بتشغيل ساعة افتراضية: فهو يُصدر إشارات للمحرك بخطوات ثابتة، ويختبر الإطارات على مخطط زمني، ويُطلق نقرات صناعية وضغطات مفاتيح في لحظات محددة. بالنسبة للألعاب، نُقارن الحالات والأحداث، مثل المكان الذي تظهر فيه الشخصية فعليًا على الشاشة في إطار معين. يُنتج محركنا والمحاكي الحقيقي نفس مخطط العقدة لكل إطار (الموضع، والدوران، والمقياس، والألفا، والسرعة الفيزيائية)، ثم تُقارن المسارات لكل عقدة ولكل إطار.
يتم إرسال كل فرق (diff) إلى نموذج لغوي بصري كبير (Large Visual Language Model) يُحدد المشكلات، ثم يقوم بتوزيع الوكلاء لتعديل مصدر إطار عمل المحرك حتى يصبح الفرق أقل من الحد الأدنى. هذه العملية فعالة بشكل مدهش، وتجعلنا قريبين جدًا من تنفيذ إطار عمل متحرك باستمرار مثل SwiftUI.
شاهد Dactyl وهو يعمل
هذا كان مجرد لمحة عن التفاصيل التقنية وراء الكواليس. لم نتطرق حتى إلى كيفية مساعدة Dactyl في تثبيت تطبيقاتك على جهازك للاختبار، أو كيف يرشدك خلال عملية إدراج التطبيقات في متجر التطبيقات. ولكن ربما من الأفضل أن ترى ذلك بنفسك.
ما عليك سوى وصف تطبيق ما، وسيقوم كل ما وصفناه هنا بإنشائه وتشغيله مباشرةً في متصفحك. جرب Dactyl الآن.