على مدار العامين الماضيين، كان فريق هندسة goodnotes يعمل على مشروع لجلب تطبيق تدوين الملاحظات الناجح من iPad إلى منصات أخرى. تتناول دراسة الحالة هذه كيفية انتقال تطبيق iPad لعام 2022 لعام 2022 إلى الويب وChromeOS وAndroid وWindows الذي يعمل بتقنيات الويب وWebAssembly الذي يعيد استخدام رمز Swift نفسه الذي كان الفريق يعمل عليه لأكثر من عشر سنوات.
أسباب ظهور Goodnotes على الويب وAndroid وWindows
في عام 2021، لم يكن تطبيق Goodnotes متاحًا إلا كتطبيق لأجهزة iOS وiPad. قبل الفريق الهندسي في Goodnotes تحديًا فنيًا كبيرًا، وهو إنشاء إصدار جديد من Goodnotes، ولكن كان يريد إنشاء المزيد من أنظمة التشغيل والأنظمة الأساسية. ويجب أن يكون المنتج متوافقًا بالكامل مع التطبيق وأن يعرض الملاحظات نفسها التي يتضمنها تطبيق iOS. يجب أن تكون أي ملاحظة يتم تسجيلها أعلى ملف PDF أو أي صورة مرفقة مماثلة وأن تعرض الضغطات نفسها التي يعرضها تطبيق iOS. يجب أن يكون أي رسم مضاف معادلاً للرسم الذي يمكن لمستخدمي iOS إنشاؤه، بشكل مستقل عن الأداة التي كان المستخدم يستخدمها - على سبيل المثال، القلم الرصاص أو قلم التظليل أو قلم الحبر أو الأشكال أو الممحاة.
واستنادًا إلى المتطلبات وخبرة الفريق الهندسي، توصل الفريق بسرعة إلى أن إعادة استخدام قاعدة رموز Swift ستكون أفضل مسار للعمل، حيث إنها مكتوبة بالفعل وتم اختبارها جيدًا على مدار سنوات عديدة. ولكن لماذا لا يتم فقط نقل تطبيق iOS/iPad الحالي إلى نظام أساسي آخر أو تقنية أخرى مثل Flutter أو Compose Multiplatform؟ يتطلب الانتقال إلى منصة جديدة إعادة كتابة Goodnotes. قد يؤدي ذلك إلى بدء سباق للتطوير بين تطبيق iOS الذي تم تنفيذه بالفعل وتطبيق سيتم إنشاؤه من تطبيق جديد صفري، أو يتضمن إيقاف التطوير الجديد على التطبيق الحالي بينما يتم استغلال قاعدة التعليمات البرمجية الجديدة. إذا كان بإمكان Goodnotes إعادة استخدام رمز Swift، يمكن للفريق الاستفادة من الميزات الجديدة التي نفّذها فريق iOS أثناء عمل الفريق من عدّة أنظمة أساسية على أساسيات التطبيق والوصول إلى مستوى من الميزات نفسها.
حل المنتج بالفعل عددًا من التحديات المثيرة للاهتمام لنظام iOS لإضافة ميزات مثل:
- جارٍ عرض الملاحظات
- مزامنة المستندات والملاحظات
- حل النزاعات في الملاحظات باستخدام أنواع البيانات المكرّرة الخالية من النزاعات.
- تحليل البيانات لتقييم نموذج الذكاء الاصطناعي (AI).
- البحث في المحتوى وفهرسة المستندات
- تجربة تمرير مخصّصة وصور متحركة
- عرض تنفيذ النموذج لجميع طبقات واجهة المستخدم.
سيكون تنفيذها كلها أسهل بكثير للأنظمة الأساسية الأخرى إذا تمكن الفريق الهندسي من جعل قاعدة رموز iOS تعمل بالفعل لتطبيقات iOS وiPad وتنفيذها كجزء من مشروع يمكن أن تشحنه Goodnotes مثل تطبيقات Windows أو Android أو الويب.
حزمة التكنولوجيا من Goodnotes
لحسن الحظ، كانت هناك طريقة لإعادة استخدام رمز Swift الحالي على الويب—WebAssembly (Wasm). أنشأ تطبيق Goodnotes نموذجًا أوليًا باستخدام Wasm من خلال مشروع SwiftWasm المفتوح المصدر والخاضع لإدارة المجتمع. باستخدام SwiftWasm، يمكن لفريق Goodnotes إنشاء برنامج ثنائي لـ Wasm باستخدام جميع أكواد Swift التي سبق تنفيذها. يمكن تضمين هذا البرنامج الثنائي في صفحة ويب يتم شحنها بصفتها تطبيق ويب تقدّمي لنظام التشغيل Android وWindows وChromeOS وجميع أنظمة التشغيل الأخرى.
كان الهدف من ذلك هو إصدار Goodnotes كتطبيق PWA، والتمكّن من إدراجه في كلّ متجر على المنصة. بالإضافة إلى Swift، وهي لغة البرمجة المستخدمة بالفعل لنظام التشغيل iOS، وWebAssembly المستخدمة لتنفيذ رمز Swift على الويب، استخدم المشروع التقنيات التالية:
- TypeScript: لغة البرمجة الأكثر استخدامًا لتقنيات الويب.
- التفاعل وحزمة الويب: إطار العمل وحزمة الويب الأكثر شيوعًا على الويب.
- تطبيق الويب التقدّمي (PWA) ومشغّلو الخدمات: أدوات تفعيل ضخمة لهذا المشروع لأنّ الفريق يمكنه شحن تطبيقنا كتطبيق غير متّصل بالإنترنت يعمل مثل أيّ تطبيق iOS آخر ويمكنك تثبيته من المتجر أو المتصفّح نفسه.
- PWABuilder: المشروع الرئيسي الذي تستخدمه Goodnotes من أجل تحويل تطبيق الويب التقدّمي (PWA) إلى برنامج ثنائي أصلي لنظام Windows كي يتمكّن الفريق من توزيع تطبيقنا من Microsoft Store.
- الأنشطة الموثوق بها على الويب: هي أهم تكنولوجيات Android التي تستخدمها الشركة لتوزيع تطبيق الويب التقدّمي الخاص بنا كتطبيق محلي داخلي.
يوضح الشكل التالي ما تم تنفيذه باستخدام TypeScript وReact الكلاسيكي، وما تم تنفيذه باستخدام SwiftWasm وvanilla JavaScript وSwift وWebAssembly. يستخدم هذا الجزء من المشروع JSKit، وهي مكتبة JavaScript لإمكانية التشغيل التفاعلي لاستخدام Swift وWebAssembly في الفريق من أجل معالجة DOM في شاشة المحرّر من رمز Swift عند الحاجة أو حتى يتم استخدام بعض واجهات برمجة التطبيقات الخاصة بكل متصفّح.
لماذا يُنصح باستخدام Wasm والويب؟
على الرغم من أن خدمة Wasm غير معتمدة رسميًا من قِبل Apple، فإن الأسباب التالية هي سبب شعور فريق هندسة Goodnotes بأن هذا النهج كان أفضل القرار:
- إعادة استخدام أكثر من 100 ألف سطر من الرموز
- القدرة على مواصلة التطوير على المنتج الأساسي مع المساهمة أيضًا في التطبيقات عبر الأنظمة الأساسية.
- يشير ذلك المصطلح إلى القدرة على الوصول إلى كل منصة في أقرب وقت ممكن باستخدام عملية تطوير تكرارية.
- لدينا القدرة على التحكّم في عرض المستند نفسه بدون تكرار جميع منطق الأعمال، وإحداث اختلافات في عمليات التنفيذ التي نجريها.
- يشير ذلك إلى الاستفادة من جميع تحسينات الأداء التي يتم إجراؤها على كل منصة في الوقت نفسه (وجميع إصلاحات الأخطاء التي تم تنفيذها على كل منصة).
كان من الضروري إعادة استخدام أكثر من 100 ألف سطر من الرموز البرمجية ومنطق الأعمال الذي يستخدم مسار العرض لدينا. في الوقت نفسه، يتيح لهم جعل رمز Swift متوافقًا مع سلاسل الأدوات الأخرى إعادة استخدام هذا الرمز في منصات مختلفة في المستقبل إذا لزم الأمر.
تطوير المنتج التكراري
اتخذ الفريق نهجًا تكراريًا من أجل تقديم شيء ما للمستخدمين في أسرع وقت ممكن. بدأت Goodnotes بإصدار للقراءة فقط من المنتج حيث يمكن للمستخدمين الحصول على أي مستند مشترك وقراءته من أي نظام أساسي. ففقط برابط، سيكون بإمكانهم الوصول إلى الملاحظات نفسها التي كتبوها من جهاز iPad وقراءتها. تمت إضافة المرحلة التالية إلى ميزات التعديل لجعل الإصدارات من عدّة منصات مماثلة لإصدار iOS.
استغرق تطوير الإصدار الأول من المنتج للقراءة فقط ستة أشهر، وتم تخصيص الأشهر التسعة التالية لأول مجموعة من ميزات التعديل وشاشة واجهة المستخدم حيث يمكنك التحقق من جميع المستندات التي أنشأتها أو شاركها شخص ما معك. بالإضافة إلى ذلك، كان من السهل نقل الميزات الجديدة لنظام iOS إلى المشروع عبر الأنظمة الأساسية بفضل SwiftWasm Toolchain. كمثال، تم إنشاء نوع جديد من القلم وتنفيذه بسهولة عبر الأنظمة الأساسية من خلال إعادة استخدام آلاف سطور الرموز.
كان بناء هذا المشروع تجربة لا تصدق، وتعلمت Goodnotes الكثير منها. ولهذا السبب ستركز الأقسام التالية على النقاط الفنية المثيرة للاهتمام حول تطوير الويب واستخدام WebAssembly ولغات مثل Swift.
العقبات الأولية
كان العمل على هذا المشروع تحديًا كبيرًا من وجهات نظر مختلفة. كانت العقبة الأولى التي وجدها الفريق ذات صلة بسلسلة أدوات SwiftWasm. كانت سلسلة الأدوات أداة تمكين ضخمة للفريق، ولكن لم تكن كل الرموز البرمجية لنظام التشغيل iOS متوافقة مع Wasm. على سبيل المثال، لم تكن التعليمات البرمجية المتعلقة بطلب الإدراج أو واجهة المستخدم - مثل تنفيذ طرق العرض أو برامج واجهة برمجة التطبيقات أو الوصول إلى قاعدة البيانات قابلة لإعادة الاستخدام، لذلك كان على الفريق بدء إعادة هيكل أجزاء معينة من التطبيق ليتمكن من إعادة استخدامها من حل عبر المنصات. معظم العلاقات العامة التي أنشأها الفريق عبارة عن إعادة هيكلة لسحب التبعيات حتى يتمكن الفريق من استبدالها لاحقًا باستخدام حقن التبعية أو استراتيجيات أخرى مماثلة. مزج رمز iOS في الأصل بين منطق الأعمال الأولي الذي يمكن تنفيذه في Wasm باستخدام التعليمات البرمجية المسؤولة عن الإدخال/الإخراج وواجهة المستخدم التي لا يمكن تنفيذها في Wasm لأن Wasm لا تدعمها أيضًا. لذا، كان يلزم إعادة تنفيذ رموز IO وواجهة المستخدم في TypeScript بمجرد أن يصبح منطق عمل Swift جاهزًا لإعادة استخدامه بين المنصات.
مشكلات الأداء التي تم حلها
بعد أن بدأت Goodnotes العمل في المحرر، حدد الفريق بعض المشاكل المتعلقة بتجربة التعديل، وظهرت قيود تقنية صعبة ضمن خارطة الطريق. كانت المشكلة الأولى متعلقة بالأداء. لغة JavaScript هي لغة متسلسلة واحدة. وهذا يعني أنّه يتضمّن حزمة مكالمات وكومة ذاكرة واحدة. يقوم بتنفيذ التعليمات البرمجية بالترتيب ويجب أن ينتهي من تنفيذ جزء من التعليمات البرمجية قبل الانتقال إلى الخطوة التالية. إنه متزامن، لكنه قد يكون ضارًا في بعض الأحيان. على سبيل المثال، إذا استغرق تنفيذ دالة ما بعض الوقت أو اضطررت إلى الانتظار، فإنها تجمِّد كل شيء في غضون ذلك. وهذا بالضبط ما كان على المهندسين حلها. كان تقييم بعض المسارات المحددة في قاعدة التعليمات البرمجية المتعلقة بطبقة العرض أو غيرها من الخوارزميات المعقدة مشكلةً للفريق، لأن هذه الخوارزميات كانت متزامنة، وأن تنفيذها كان يعيق سلسلة التعليمات الرئيسية. أعاد فريق Goodnotes صياغتها بهدف تسريعها، كما أعاد تنظيم بعضها لجعلها غير متزامنة. وقدّموا أيضًا استراتيجية عائدات حتى يمكن للتطبيق إيقاف تنفيذ الخوارزمية ومتابعتها لاحقًا، ما يسمح للمتصفح بتحديث واجهة المستخدم وتجنب إغفال الإطارات. لم تكن هذه مشكلة في تطبيق iOS لأنه يمكنه استخدام سلاسل التعليمات وتقييم هذه الخوارزميات في الخلفية بينما تعمل سلسلة iOS الرئيسية على تحديث واجهة المستخدم.
كان هناك حل آخر كان على الفريق الهندسي حله وهو نقل واجهة مستخدم استنادًا إلى عناصر HTML المرفقة بنموذج العناصر في المستند (DOM)، إلى واجهة مستخدم مستند بناءً على لوحة رسم بملء الشاشة. بدأ المشروع في عرض جميع الملاحظات والمحتوى المتعلق بمستند كجزء من بنية DOM باستخدام عناصر HTML كما تفعل أي صفحة ويب أخرى، ولكن في مرحلة ما، تم النقل إلى لوحة بملء الشاشة لتحسين الأداء على الأجهزة المنخفضة المواصفات عن طريق تقليل وقت عمل المتصفح على تحديثات DOM.
حدد الفريق الهندسي التغييرات التالية على أنها أشياء كان من الممكن أن تقلل بعض المشكلات التي تمت مواجهتها، هل فعلوها في بداية المشروع.
- تفريغ سلسلة التعليمات الرئيسية باستخدام عمال الويب بشكل متكرر لإجراء الخوارزميات المكثفة.
- استخدِم الدوال المُصدَّرة والمستوردة بدلاً من مكتبة التشغيل التفاعلي لـ JS-Swift منذ البداية حتى تتمكّن من تقليل تأثير الأداء الناتج عن الخروج من سياق Wasm. تُعد مكتبة إمكانية التشغيل التفاعلي لـ JavaScript هذه مفيدة في الوصول إلى نموذج العناصر في المستند (DOM) أو المتصفّح ولكنه أبطأ من دوال Wasm الأصلية التي تم تصديرها.
- تأكَّد من أنّ الرمز البرمجي يسمح باستخدام
OffscreenCanvas
بشكل غير مرئي حتى يتمكّن التطبيق من تفريغ سلسلة التعليمات الرئيسية ونقل جميع استخدامات واجهة برمجة التطبيقات Canvas إلى عامل ويب يعمل على تحسين أداء التطبيقات إلى أقصى حدّ عند كتابة الملاحظات. - انقل جميع عمليات التنفيذ ذات الصلة بأداة Wasm إلى أحد مشغّلي الويب أو حتى مجموعة من العاملين على الويب، حتى يتمكّن التطبيق من تقليل عبء عمل سلسلة التعليمات الرئيسية.
محرِّر النصوص
وكانت هناك مشكلة أخرى شيقة تتعلق بأداة واحدة محددة، وهي محرر النصوص.
يستند تنفيذ هذه الأداة في نظام التشغيل iOS إلى
NSAttributedString
،
وهي مجموعة أدوات صغيرة تستخدم
RTF تلقائيًا. ومع ذلك، لا يتوافق هذا التنفيذ مع SwiftWasm، لذا اضطر الفريق عبر الأنظمة الأساسية إلى إنشاء محلّل مخصّص يعتمد على قواعد RTF أولاً، ثم تنفيذ تجربة التعديل لاحقًا من خلال تحويل RTF إلى تنسيق HTML والعكس صحيح. وفي الوقت نفسه، بدأ فريق iOS العمل على طريقة التنفيذ الجديدة لهذه الأداة بدلاً من استخدام RTF بنموذج مخصّص كي يتمكّن التطبيق من تمثيل نص مصمّم بطريقة ودية على جميع الأنظمة الأساسية التي تستخدم رمز Swift نفسه.
كان هذا التحدي أحد أهم النقاط في خارطة طريق المشروع لأنه تم حله بشكل متكرر بناءً على احتياجات المستخدم. لقد كانت مشكلة هندسية تم حلها باستخدام نهج يركز على المستخدم حيث احتاج الفريق إلى إعادة كتابة جزء من التعليمة البرمجية ليتمكن من عرض النص، لذلك تمكنوا من تحرير النص في إصدار ثانٍ.
إصدارات متكررة
لقد كان التطور الذي طرأ على المشروع على مدار العامين الماضيين أمرًا لا يصدق. بدأ الفريق العمل على نسخة للقراءة فقط من المشروع، وبعد أشهر شحن نسخة جديدة تمامًا بها الكثير من إمكانات التعديل. لإصدار تغييرات التعليمات البرمجية للإنتاج بشكل متكرر، قرر الفريق استخدام علامات الميزات على نطاق واسع. بالنسبة إلى كل إصدار، يمكن للفريق تفعيل ميزات جديدة وأيضًا إصدار تغييرات على التعليمات البرمجية لتنفيذ ميزات جديدة سيراها المستخدم بعد أسابيع. ومع ذلك، هناك شيء يعتقد الفريق أنه كان من الممكن تحسينه! ويعتقدون أن تقديم نظام ديناميكي للإبلاغ عن الميزات كان من شأنه أن يساعد في تسريع الأمور، لأنه قد يلغي الحاجة إلى إعادة النشر لتغيير قيم العلامات. يتيح ذلك لتطبيق Goodnotes مزيدًا من المرونة وكذلك تسريع عملية نشر الميزة الجديدة لأنّ تطبيق Goodnotes لن يحتاج إلى ربط عملية نشر المشروع بإصدار المنتج.
العمل بلا إنترنت
إحدى الميزات الرئيسية التي عمل عليها الفريق هي الدعم بلا اتصال بالإنترنت. القدرة على تحرير المستندات وتعديلها هي إحدى الميزات التي تتوقعها في أي تطبيق مثل هذا. ومع ذلك، فهذه ليست ميزة بسيطة لأن Goodnotes يدعم التعاون. وهذا يعني أن جميع التغييرات التي يجريها المستخدمون المختلفون على الأجهزة المختلفة يجب أن تنتهي على كل جهاز بدون مطالبة المستخدمين بحل أي تعارضات. حلت Goodnotes هذه المشكلة منذ فترة طويلة من خلال استخدام CRDT غير مسبوقة. بفضل أنواع البيانات المكررة الخالية من النزاعات هذه، يمكن لتطبيق Goodnotes دمج كل التغييرات التي يتم إجراؤها على أي مستند من قِبل أي مستخدم ودمج التغييرات بدون أي تعارض في الدمج. كان استخدام IndexedDB وسعة التخزين المتاحة لمتصفحات الويب أداة تفعيل كبيرة للتجربة التعاونية بلا اتصال بالإنترنت على الويب.
بالإضافة إلى ذلك، يؤدي فتح تطبيق الويب Goodnotes إلى إتاحة تكلفة تنزيل مبدئية مسبقة تبلغ حوالي 40 ميغابايت بسبب حجم برنامج Wasm الثنائي. في البداية، اعتمد فريق Goodnotes في ما يتعلّق بذاكرة التخزين المؤقت العادية للمتصفح لحِزمة التطبيق نفسها ومعظم نقاط نهاية واجهة برمجة التطبيقات التي يستخدمونها، ولكن بعد ذلك، كان بإمكانهم الاستفادة في وقت سابق من واجهة برمجة التطبيقات Cache API ومشغّلي الخدمات الأكثر موثوقية. تجنّب الفريق في الأصل هذه المهمة بسبب تعقيدها المفترض، ولكن في النهاية، أدرك أنّ Workbox جعلها أقل رعبًا.
الحصول على اقتراحات عند استخدام Swift على الويب
إذا كان لديك تطبيق iOS يحتوي على الكثير من التعليمات البرمجية التي تريد إعادة استخدامها، فاستعد لأنك على وشك البدء في رحلة مذهلة. هناك بعض النصائح التي قد تجدها مثيرة للاهتمام قبل أن تبدأ.
- تحقَّق من الرمز الذي تريد إعادة استخدامه. إذا تم تنفيذ منطق الأعمال لتطبيقك على الخادم، فمن المحتمل أن ترغب في إعادة استخدام رمز واجهة المستخدم الخاص بك، ولن يساعدك Wasm في ذلك. ونظر الفريق بإيجاز في Tokamak، وهو إطار عمل متوافق مع SwiftUI لتصميم تطبيقات للمتصفّح باستخدام WebAssembly، ولكنه لم يكن جاهزًا بما يكفي لاحتياجات التطبيق. ومع ذلك، إذا كان تطبيقك يحتوي على منطق عمل قوي أو خوارزميات منفَّذة كجزء من رمز العميل، سيكون Wasm أفضل صديق لك.
- تأكَّد من أنّ قاعدة رموز Swift جاهزة. أنماط تصميم البرامج لطبقة واجهة المستخدم أو الهياكل المحددة التي تنشئ فصلاً قويًا بين منطق واجهة المستخدم ومنطق عملك ستكون مفيدة حقًا لأنك لن تكون قادرًا على إعادة استخدام تنفيذ طبقة واجهة المستخدم. ستكون البنية النظيفة أو مبادئ العمارة السداسية أساسية أيضًا، لأنه سيتعين عليك إدخال التبعيات لجميع التعليمات البرمجية المتعلقة بإدخال البيانات وسيكون من الأسهل تنفيذها إذا تتبعت هذه البنى التي يتم فيها تحديد تفاصيل التنفيذ على أنها تجريد وتُستخدم مبدأ قلب التبعية بشكل كبير.
- لا يوفر Wasm رمز واجهة المستخدم. لذلك، حدد إطار عمل واجهة المستخدم الذي تريد استخدامه للويب.
- ستساعدك JSKit في دمج رمز Swift مع JavaScript ولكن ضع في اعتبارك أنه إذا كان لديك مسار سريع، قد يكون عبور جسر JS-Swift مكلفًا وستحتاج إلى استبداله بالدوال المُصدَّرة. يمكنك الاطّلاع على المزيد من المعلومات حول آلية عمل JSKit في ما يتعلّق بالمحتوى غير الرسمي في المستندات الرسمية ومشاركة البحث الديناميكي عن الأعضاء في Swift،.
- وستعتمد إمكانية إعادة استخدام بنيتك على البنية التي يتبعها تطبيقك ومكتبة آلية تنفيذ التعليمات البرمجية غير المتزامنة التي تستخدمها. ستساعدك الأنماط، مثل MVVP، أو البنية القابلة للإنشاء، على إعادة استخدام نماذج العرض وجزء من منطق واجهة المستخدم دون إقران عملية التنفيذ بتبعيات UIKit التي لا يمكنك استخدامها مع Wasm. قد لا تكون RXSwift والمكتبات الأخرى متوافقة مع Wasm، لذا ضعها في اعتبارك لأنك ستحتاج إلى استخدام OpenCombine، وغير متزامن/انتظار، وأحداث البث باستخدام رمز Swift في Goodnote.
- اضغط على برنامج Wasm الثنائي باستخدام gzip أو botli. ضع في اعتبارك أن حجم البرنامج الثنائي سيكون كبيرًا جدًا بالنسبة إلى تطبيقات الويب الكلاسيكية.
- حتى إذا كان بإمكانك استخدام Wasm بدون تطبيق الويب التقدّمي (PWA)، تأكَّد من تضمين مشغّل خدمات على الأقل، حتى إذا لم يتضمّن تطبيق الويب أي بيان أو إذا كنت لا تريد من المستخدم تثبيته. سيقوم مشغّل الخدمات بحفظ برنامج Wasm الثنائي وعرضه مجانًا وجميع موارد التطبيق حتى لا يحتاج المستخدم إلى تنزيلها في كل مرة يفتح فيها مشروعك.
- ضع في اعتبارك أن التوظيف قد يكون أصعب من المتوقع. قد تحتاج إلى توظيف مطوري ويب أقوياء لديهم بعض الخبرة في التعامل مع مطوري Swift أو مطوّري Swift ذوي الخبرة في مجال الويب. إذا تمكنت من العثور على مهندسين عامين لديهم بعض المعرفة على كلا المنصتين، فسيكون ذلك رائعًا
الاستنتاجات
يعد إنشاء مشروع ويب باستخدام حزمة تقنية معقدة أثناء العمل على منتج مليء بالتحديات تجربة لا تصدق. سيكون الأمر صعبًا، ولكنه يستحق كل ذلك تمامًا. لم يكن من الممكن أن تصدر Goodnotes أي إصدار لأنظمة التشغيل Windows وAndroid وChromeOS والويب أثناء العمل على ميزات جديدة لتطبيق iOS بدون استخدام هذا الأسلوب. بفضل حزمة التكنولوجيا هذه وفريق المهندسين في Goodnotes، أصبح Goodnotes الآن في كل مكان، وبات الفريق على استعداد لمواصلة العمل على التحديات التالية. لمعرفة المزيد عن هذا المشروع، يمكنك مشاهدة المحاضرة التي قدمها فريق Goodnotes في مؤتمر NSإسبانيا لعام 2023. احرص على تجربة الملاحظات الجيدة للويب.