در دو سال گذشته، تیم مهندسی Goodnotes روی پروژهای کار میکردند تا اپلیکیشن موفق یادداشتبرداری آیپد را به پلتفرمهای دیگر بیاورند. این مطالعه موردی نشان میدهد که چگونه برنامه سال ۲۰۲۲ آیپد به وب، ChromeOS، Android و Windows با استفاده از فناوریهای وب و WebAssembly با استفاده مجدد از همان کد سوئیفت که تیم برای بیش از ده سال روی آن کار میکرده است، رسید.
چرا Goodnotes به وب، اندروید و ویندوز آمد؟
در سال 2021 Goodnotes فقط به عنوان یک برنامه برای iOS و iPad در دسترس بود. تیم مهندسی Goodnotes یک چالش فنی بزرگ را پذیرفت: ایجاد نسخه جدیدی از Goodnotes اما برای سیستم عامل ها و پلتفرم های اضافی. محصول باید کاملاً با برنامه iOS سازگار باشد و همان یادداشتها را ارائه کند. هر یادداشتی که در بالای یک PDF گرفته میشود، یا هر تصویری که پیوست میشود باید معادل باشد و همان حرکاتی را که برنامه iOS نشان میدهد نشان دهد. هر ضربه ای که اضافه می شود باید معادل همان چیزی باشد که کاربران iOS می توانند ایجاد کنند، مستقل از ابزاری که کاربر استفاده می کند - به عنوان مثال، خودکار، هایلایت، خودکار، اشکال یا پاک کن.
بر اساس الزامات و تجربه تیم مهندسی، تیم به سرعت به این نتیجه رسیدند که استفاده مجدد از پایگاه کد سوئیفت بهترین اقدام خواهد بود، با توجه به اینکه قبلاً نوشته شده بود و طی سالها به خوبی آزمایش شده بود. اما چرا برنامه iOS/iPad از قبل موجود را به پلتفرم یا فناوری دیگری مانند Flutter یا Compose Multiplatform پورت نکنید؟ انتقال به یک پلتفرم جدید مستلزم بازنویسی Goodnotes است. انجام این کار ممکن است یک مسابقه توسعه بین برنامه iOS که قبلاً اجرا شده و یک برنامه جدید ساخته شده از صفر آغاز شود، یا شامل توقف توسعه جدید در برنامه موجود در حالی که پایگاه کد جدید فرا می رسد. اگر Goodnotes بتواند دوباره از کد سوئیفت استفاده کند، تیم میتواند از ویژگیهای جدیدی که توسط تیم iOS پیادهسازی شده است، بهرهمند شود، در حالی که تیم چند پلتفرمی روی اصول برنامه کار میکرد و به برابری ویژگیها دست یافت.
این محصول قبلاً تعدادی از چالش های جالب را برای iOS حل کرده بود تا ویژگی هایی مانند:
- ارائه یادداشت ها
- همگام سازی اسناد و یادداشت ها
- حل تضاد برای یادداشتها با استفاده از انواع دادههای تکراری بدون تضاد .
- تجزیه و تحلیل داده ها برای ارزیابی مدل هوش مصنوعی.
- جستجوی محتوا و نمایه سازی اسناد.
- تجربه پیمایش سفارشی و انیمیشن ها.
- اجرای مدل را برای تمام لایه های UI مشاهده کنید.
اگر تیم مهندسی بتواند پایگاه کدهای iOS را که قبلاً برای برنامههای iOS و iPad کار میکند و به عنوان بخشی از پروژهای که Goodnotes میتواند به عنوان برنامههای کاربردی ویندوز، اندروید یا وب ارسال کند، اجرا کند، اجرای همه آنها برای سایر پلتفرمها بسیار آسانتر خواهد بود.
پشته فناوری Goodnotes
خوشبختانه، راهی برای استفاده مجدد از کد سوئیفت موجود در وب وجود داشت - WebAssembly (Wasm). Goodnotes یک نمونه اولیه با استفاده از Wasm با منبع باز و پروژه SwiftWasm ایجاد کرد. با SwiftWasm، تیم Goodnotes میتواند با استفاده از تمام کدهای Swift که قبلاً پیادهسازی شدهاند، یک باینری Wasm تولید کند. این باینری را می توان در یک صفحه وب که به عنوان یک برنامه وب پیشرفته برای Android، Windows، ChromeOS و هر سیستم عامل دیگری ارسال می شود، گنجاند.
هدف این بود که Goodnotes را به عنوان یک PWA منتشر کنیم و بتوانیم آن را در فروشگاه هر پلتفرمی فهرست کنیم. علاوه بر Swift، زبان برنامه نویسی که قبلاً برای iOS استفاده می شد، و WebAssembly که برای اجرای کد سوئیفت در وب استفاده می شد، این پروژه از فناوری های زیر استفاده کرد:
- TypeScript: پرکاربردترین زبان برنامه نویسی برای فناوری های وب.
- React و webpack: محبوب ترین فریم ورک و باندلر برای وب.
- PWA و کارکنان خدمات: توانمندسازهای بزرگ برای این پروژه زیرا تیم می تواند برنامه ما را به عنوان یک برنامه آفلاین ارسال کند که مانند هر برنامه دیگر iOS کار می کند و شما می توانید آن را از فروشگاه یا خود مرورگر نصب کنید.
- PWABuilder: پروژه اصلی Goodnotes برای قرار دادن PWA در یک باینری بومی ویندوز استفاده میکند تا تیم بتواند برنامه ما را از فروشگاه مایکروسافت توزیع کند.
- فعالیتهای وب مورد اعتماد: مهمترین فناوری اندرویدی که شرکت برای توزیع PWA ما بهعنوان یک برنامه بومی تحت پوشش استفاده میکند.
شکل زیر نشان می دهد که چه چیزی با استفاده از TypeScript و React کلاسیک پیاده سازی شده است و چه چیزی با استفاده از SwiftWasm و vanilla JavaScript، Swift و WebAssembly پیاده سازی شده است. این بخش از پروژه از JSKit استفاده میکند، یک کتابخانه قابلیت همکاری جاوا اسکریپت برای Swift و WebAssembly که تیم از آن استفاده میکند تا در صورت نیاز، DOM را در صفحه ویرایشگر ما از کد Swift مدیریت کند یا حتی از برخی APIهای خاص مرورگر استفاده کند.
چرا از Wasm و وب استفاده کنیم؟
اگرچه Wasm به طور رسمی توسط اپل پشتیبانی نمی شود، دلایل زیر نشان می دهد که چرا تیم مهندسی Goodnotes این رویکرد را بهترین تصمیم می داند:
- استفاده مجدد از بیش از 100 هزار خط کد.
- توانایی ادامه توسعه در محصول اصلی و در عین حال کمک به برنامههای چند پلتفرمی.
- قدرت رسیدن به هر پلتفرم در اسرع وقت با استفاده از فرآیند توسعه تکراری.
- داشتن کنترل برای ارائه یک سند بدون تکرار همه منطق تجاری، و ایجاد تفاوت در پیاده سازی های ما.
- بهره مندی از تمام بهبودهای عملکرد انجام شده در هر پلتفرم به طور همزمان (و تمام رفع اشکالات اجرا شده در هر پلتفرم).
استفاده مجدد از بیش از 100 هزار خط کد و منطق تجاری اجرای خط لوله رندر ما اساسی بود. در عین حال، سازگار کردن کد سوئیفت با دیگر زنجیرههای ابزار به آنها اجازه میدهد در صورت نیاز در آینده از این کد در پلتفرمهای مختلف استفاده کنند.
توسعه محصول تکراری
این تیم رویکردی تکراری را در پیش گرفت تا بتواند هر چه سریعتر چیزی را به کاربران برساند. Goodnotes با یک نسخه فقط خواندنی محصول شروع شد که در آن کاربران میتوانستند هر سند مشترکی را دریافت کنند و آن را از هر پلتفرمی بخوانند. فقط با یک پیوند، آنها می توانند به همان یادداشت هایی که از iPad خود نوشته اند دسترسی داشته باشند و آنها را بخوانند. فاز بعدی در ویژگیهای ویرایش اضافه شد، تا نسخههای کراس پلتفرم معادل نسخه iOS شود.
اولین نسخه از محصول فقط خواندنی شش ماه طول کشید تا توسعه یابد، نه ماه بعدی به اولین دسته از ویژگی های ویرایش و صفحه رابط کاربری اختصاص داده شد که در آن می توانید تمام اسنادی را که ایجاد کرده اید یا شخصی که با شما به اشتراک گذاشته است بررسی کنید. علاوه بر این، ویژگیهای جدید پلتفرم iOS به لطف SwiftWasm Toolchain به راحتی به پروژه چند پلتفرمی منتقل میشوند. به عنوان مثال، نوع جدیدی از قلم ایجاد شد و با استفاده مجدد از هزاران خط کد، به راحتی در چند پلتفرم پیادهسازی شد.
ساخت این پروژه یک تجربه باورنکردنی بود و Goodnotes چیزهای زیادی از آن آموخته است. به همین دلیل است که بخشهای بعدی بر روی نکات فنی جالب در مورد توسعه وب و استفاده از WebAssembly و زبانهایی مانند Swift تمرکز خواهند کرد.
موانع اولیه
کار بر روی این پروژه از دیدگاه های مختلف بسیار چالش برانگیز بود. اولین مانعی که تیم پیدا کرد مربوط به زنجیره ابزار SwiftWasm بود. زنجیره ابزار یک توانمندساز بزرگ برای تیم بود، اما همه کدهای iOS با Wasm سازگار نبودند. به عنوان مثال، کدهای مربوط به IO یا UI - مانند اجرای نماها، کلاینتهای API یا دسترسی به پایگاه داده قابل استفاده مجدد نبود، بنابراین تیم باید شروع به بازسازی بخشهای خاصی از برنامه کند تا بتواند از آنها مجدداً از آنها استفاده کند. راه حل پلت فرم اکثر روابط عمومیهایی که تیم ایجاد کردند، بازساز وابستگیهای انتزاعی بودند، بنابراین تیم میتوانست بعداً آنها را با استفاده از تزریق وابستگی یا سایر استراتژیهای مشابه جایگزین کند. کد iOS در ابتدا منطق تجاری خام را که میتوانست در Wasm پیادهسازی شود، با کدهایی که مسئول ورودی/خروجی و رابط کاربری هستند ترکیب میکردند که نمیتوانستند در Wasm پیادهسازی شوند، زیرا Wasm از هیچکدام پشتیبانی نمیکند. بنابراین، زمانی که منطق تجاری سوئیفت برای استفاده مجدد بین پلتفرمها آماده شد، باید کد IO و UI در TypeScript دوباره پیادهسازی میشد.
مشکلات عملکرد حل شد
هنگامی که Goodnotes شروع به کار بر روی ویرایشگر کرد، تیم مشکلاتی را در تجربه ویرایش شناسایی کرد و محدودیتهای چالش برانگیز فناوری وارد نقشه راه ما شد. اولین مشکل مربوط به عملکرد بود. جاوا اسکریپت یک زبان تک رشته ای است. این بدان معناست که یک پشته تماس و یک پشته حافظه دارد. کد را به ترتیب اجرا می کند و باید قبل از رفتن به کد بعدی، اجرای یک قطعه کد را به پایان برساند. همزمان است، اما در مواقعی ممکن است مضر باشد. برای مثال، اگر اجرای یک تابع کمی طول بکشد یا مجبور باشد روی چیزی منتظر بماند، در این فاصله همه چیز را ثابت میکند. و این دقیقاً همان چیزی است که مهندسان باید حل می کردند. ارزیابی برخی از مسیرهای خاص در پایگاه کد ما مربوط به لایه رندر یا سایر الگوریتمهای پیچیده برای تیم یک مشکل بود، زیرا این الگوریتمها همزمان بودند و اجرای آنها موضوع اصلی را مسدود میکرد. تیم Goodnotes آنها را بازنویسی کرد تا سریعتر شوند و برخی از آنها را مجدداً تغییر دادند تا ناهمزمان شوند. آنها همچنین یک استراتژی بازدهی را معرفی کردند تا برنامه بتواند اجرای الگوریتم را متوقف کند و بعداً آن را ادامه دهد و به مرورگر اجازه دهد رابط کاربری را بهروزرسانی کند و از انداختن فریمها جلوگیری کند. این مشکلی برای برنامه iOS نبود زیرا میتواند از رشتهها استفاده کند و این الگوریتمها را در پسزمینه ارزیابی کند، در حالی که موضوع اصلی iOS رابط کاربری را بهروزرسانی میکند.
راه حل دیگری که تیم مهندسی باید حل می کرد، انتقال یک رابط کاربری مبتنی بر عناصر HTML متصل به DOM، به یک رابط کاربری سند بر اساس یک بوم تمام صفحه بود. پروژه شروع به نشان دادن تمام یادداشت ها و محتوای مربوط به یک سند به عنوان بخشی از ساختار DOM با استفاده از عناصر HTML مانند هر صفحه وب دیگری کرد، اما در مقطعی به یک بوم تمام صفحه برای بهبود عملکرد در دستگاه های پایین رده منتقل شد. کاهش زمان کار مرورگر روی بهروزرسانیهای DOM.
تغییرات زیر توسط تیم مهندسی بهعنوان مواردی شناسایی شد که میتوانست برخی از مشکلات پیشآمده را کاهش دهد، اگر آنها در ابتدای پروژه این کار را انجام میدادند.
- با استفاده مکرر از webworkers برای الگوریتم های سنگین، موضوع اصلی را بیشتر بارگذاری کنید.
- از ابتدا از توابع صادر شده و وارد شده به جای کتابخانه interop JS-Swift استفاده کنید تا بتوانند تأثیر عملکرد خروج از زمینه Wasm را کاهش دهند. این کتابخانه interop جاوا اسکریپت برای دسترسی به DOM یا مرورگر مفید است، اما کندتر از توابع صادر شده Wasm بومی است.
- اطمینان حاصل کنید که کد اجازه استفاده از
OffscreenCanvas
را در زیر کاپوت می دهد تا برنامه بتواند رشته اصلی را بارگیری کند و تمام استفاده از Canvas API را به یک وب کارگر منتقل کند تا عملکرد برنامه ها را هنگام نوشتن یادداشت به حداکثر برساند. - تمام اجرای مربوط به Wasm را به یک وب کارگر یا حتی مجموعه ای از کارگران وب منتقل کنید تا برنامه بتواند بار کاری رشته اصلی را کاهش دهد.
ویرایشگر متن
مشکل جالب دیگر مربوط به یک ابزار خاص، ویرایشگر متن بود. پیاده سازی iOS برای این ابزار بر اساس NSAttributedString
است، یک مجموعه ابزار کوچک که از RTF در زیر هود استفاده می کند. با این حال، این پیادهسازی با SwiftWasm سازگار نیست، بنابراین تیم cross-platform مجبور شد ابتدا یک تجزیهکننده سفارشی بر اساس گرامر RTF ایجاد کند و بعداً با تبدیل RTF به HTML و بالعکس، تجربه ویرایش را پیادهسازی کند. در همین حال، تیم iOS شروع به کار بر روی پیادهسازی جدید این ابزار کرد که استفاده از RTF را با یک مدل سفارشی جایگزین کرد تا برنامه بتواند متن استایلشده را به روشی دوستانه برای همه پلتفرمهایی که کد Swift یکسان را به اشتراک میگذارند، نمایش دهد.
این چالش یکی از جالب ترین نکات در نقشه راه پروژه بود زیرا بر اساس نیاز کاربر به صورت تکراری حل شد. این یک مشکل مهندسی بود که با استفاده از یک رویکرد متمرکز بر کاربر حل شد، جایی که تیم باید بخشی از کد را بازنویسی میکرد تا بتواند متن را ارائه کند تا ویرایش متن را در نسخه دوم فعال کنند.
نسخه های تکراری
تکامل این پروژه در دو سال گذشته باورنکردنی بوده است. تیم شروع به کار بر روی یک نسخه فقط خواندنی از پروژه کرد و ماهها بعد یک نسخه کاملاً جدید را با قابلیتهای ویرایش زیادی ارسال کرد. برای انتشار مکرر تغییرات کد در تولید، تیم تصمیم گرفت به طور گسترده از پرچمهای ویژگی استفاده کند. برای هر نسخه، تیم میتواند ویژگیهای جدید را فعال کند و همچنین تغییرات کد را با اجرای ویژگیهای جدیدی که کاربر هفتهها بعد میبیند، منتشر کند. با این حال، چیزی وجود دارد که تیم فکر می کند آنها می توانستند پیشرفت کنند! آنها فکر می کنند که معرفی یک سیستم پرچم ویژگی پویا می تواند به سرعت بخشیدن به کارها کمک کند، زیرا نیاز به استقرار مجدد برای تغییر مقادیر پرچم را از بین می برد. این امر به Goodnotes انعطافپذیری بیشتری میبخشد و همچنین استقرار ویژگی جدید را سرعت میبخشد، زیرا Goodnotes نیازی به پیوند دادن استقرار پروژه به انتشار محصول ندارد.
کار آفلاین
یکی از ویژگی های مهمی که تیم روی آن کار کرده است، پشتیبانی آفلاین است. توانایی ویرایش اسناد خود و اصلاح آنها یکی از ویژگی هایی است که از هر برنامه ای مانند این انتظار دارید. با این حال، این یک ویژگی ساده نیست زیرا Goodnotes از همکاری پشتیبانی می کند. این بدان معناست که تمام تغییراتی که توسط کاربران مختلف در دستگاههای مختلف انجام میشود باید در هر دستگاه بدون درخواست از کاربران برای حل هرگونه تضاد انجام شود. Goodnotes این مشکل را مدتها پیش با استفاده از CRDT در زیر کاپوت حل کرد. به لطف این نوع دادههای تکراری بدون تداخل، Goodnotes میتواند تمام تغییرات انجام شده بر روی هر سند توسط هر کاربر را ترکیب کند و تغییرات را بدون هیچ گونه تضاد ادغام ادغام کند. استفاده از IndexedDB و فضای ذخیره سازی موجود برای مرورگرهای وب، یک عامل بزرگ برای تجربه آفلاین مشترک در وب بود.
علاوه بر این، باز کردن برنامه وب Goodnotes به دلیل اندازه باینری Wasm منجر به هزینه اولیه دانلود حدود 40 مگابایت می شود. در ابتدا، تیم Goodnotes صرفاً به حافظه پنهان مرورگر معمولی برای خود بسته برنامه و بیشتر نقاط پایانی API که استفاده میکنند متکی بود، اما در آینده نزدیک میتوانستند از API قابل اعتمادتر Cache و کارگران خدمات زودتر سود ببرند. این تیم در ابتدا به دلیل پیچیدگی فرضی آن از این کار اجتناب کردند، اما در نهایت متوجه شدند که Workbox آن را بسیار کمتر ترسناک کرده است.
توصیه هایی هنگام استفاده از Swift در وب
اگر یک برنامه iOS با کدهای زیادی دارید که می خواهید دوباره از آن استفاده کنید، آماده باشید زیرا در شرف شروع یک سفر باورنکردنی هستید. نکاتی وجود دارد که ممکن است قبل از شروع برایتان جالب باشد.
- بررسی کنید چه کدی را می خواهید دوباره استفاده کنید. اگر منطق تجاری برنامه شما در سمت سرور پیادهسازی شده باشد، احتمالاً دوست دارید از کد UI خود دوباره استفاده کنید و Wasm در اینجا به شما کمک نمیکند. این تیم به طور خلاصه به Tokamak ، یک چارچوب سازگار با SwiftUI برای ساخت برنامه های مرورگر با WebAssembly نگاه کردند، اما برای نیازهای برنامه به اندازه کافی بالغ نبود. با این حال، اگر برنامه شما دارای منطق تجاری قوی یا الگوریتم هایی است که به عنوان بخشی از کد مشتری پیاده سازی شده است، Wasm بهترین دوست شما خواهد بود.
- مطمئن شوید که پایگاه کد سوئیفت شما آماده است. الگوهای طراحی نرم افزار برای لایه UI یا معماری های خاص ایجاد جدایی قوی بین منطق UI شما و منطق کسب و کار شما واقعا مفید خواهد بود زیرا شما نمی توانید از پیاده سازی لایه UI مجددا استفاده کنید. اصول معماری پاک یا معماری شش ضلعی نیز اساسی خواهند بود، زیرا شما باید برای تمام کدهای مرتبط با IO وابستگی هایی را تزریق و ارائه کنید و اگر از این معماری ها پیروی کنید، جایی که جزئیات پیاده سازی به عنوان انتزاع و تعریف می شوند، انجام این کار بسیار ساده تر خواهد بود. اصل وارونگی وابستگی به شدت مورد استفاده قرار می گیرد.
- Wasm کد UI ارائه نمی کند. بنابراین، در مورد چارچوب UI که می خواهید برای وب استفاده کنید، تصمیم بگیرید.
- JSKit به شما کمک می کند تا کد سوئیفت خود را با جاوا اسکریپت ادغام کنید، اما به خاطر داشته باشید که اگر یک Hotpath دارید، عبور از پل JS–Swift ممکن است گران باشد و باید آن را با توابع صادر شده جایگزین کنید. در اسناد رسمی و جستجوی اعضای پویا در Swift، یک جواهر پنهان، میتوانید درباره نحوه عملکرد JSKit در زیر کاپوت بیشتر بدانید! پست.
- اینکه آیا میتوانید از معماری خود استفاده مجدد کنید، به معماری برنامه شما و کتابخانه مکانیزم اجرای کد غیرهمگامی که استفاده میکنید بستگی دارد. الگوهایی مانند MVVP یا معماری composable به شما کمک میکنند تا از مدلهای view و بخشی از منطق UI استفاده مجدد کنید، بدون اینکه پیادهسازی را با وابستگیهای UIKit که نمیتوانید با Wasm استفاده کنید، استفاده کنید. RXSwift و سایر کتابخانهها ممکن است با Wasm سازگار نباشند، بنابراین آن را در نظر داشته باشید زیرا باید از OpenCombine ، async/wait، و جریانها در کد سوئیفت Goodnotes استفاده کنید.
- باینری Wasm را با استفاده از gzip یا brotli فشرده کنید. به خاطر داشته باشید که اندازه باینری برای برنامه های کاربردی وب کلاسیک بسیار بزرگ خواهد بود.
- حتی زمانی که میتوانید از Wasm بدون PWA استفاده کنید، مطمئن شوید که حداقل یک سرویسدهنده را شامل میشوید، حتی اگر برنامه وب شما مانیفست نداشته باشد یا نمیخواهید کاربر آن را نصب کند. سرویسکار باینری Wasm و تمام منابع برنامه را بهصورت رایگان ذخیره و ارائه میکند تا کاربر هر بار که پروژه شما را باز میکند نیازی به دانلود آنها نداشته باشد.
- به خاطر داشته باشید استخدام ممکن است سخت تر از حد انتظار باشد. ممکن است لازم باشد توسعه دهندگان وب قوی با تجربه در Swift یا توسعه دهندگان قوی Swift با کمی تجربه در وب استخدام کنید. اگر بتوانید مهندسان عمومی با دانشی در هر دو پلتفرم پیدا کنید، عالی خواهد بود
نتیجه گیری
ساختن یک پروژه وب با استفاده از یک پشته فناوری پیچیده در حین کار بر روی محصولی پر از چالش، تجربه ای باورنکردنی است. سخت خواهد بود، اما کاملاً ارزشش را دارد. Goodnotes هرگز نمیتوانست نسخهای را برای Windows، Android، ChromeOS و وب منتشر کند، در حالی که روی ویژگیهای جدید برنامه iOS بدون استفاده از این رویکرد کار میکرد. به لطف این پشته فناوری و تیم مهندسی Goodnotes، Goodnotes اکنون در همه جا حضور دارد و تیم آماده ادامه کار بر روی چالش های بعدی است! اگر میخواهید درباره این پروژه بیشتر بدانید، میتوانید سخنرانی تیم Goodnotes در NSSpain 2023 را تماشا کنید. حتما Goodnotes برای وب را امتحان کنید!