בשנתיים האחרונות, צוות המהנדסים של Goodnotes עבד על פרויקט להעברת אפליקציית הכתיבה המצליחה ל-iPad לפלטפורמות אחרות. בניתוח המקרה הזה נסביר איך אפליקציית השנה ל-iPad לשנת 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: שפת התכנות הנפוצה ביותר בטכנולוגיות אינטרנט.
- React ו-Webpack: המסגרת והכלי לאריזה הפופולריים ביותר באינטרנט.
- אפליקציות PWA ושירותי עובדים: הן כלי עזר חשובים בפרויקט הזה, כי הצוות יכול היה לשלוח את האפליקציה שלנו כאפליקציה אופליין שפועלת כמו כל אפליקציית iOS אחרת, ואפשר להתקין אותה מהחנות או מהדפדפן עצמו.
- PWABuilder: הפרויקט הראשי שבו נעשה שימוש ב-Goodnotes כדי לעטוף את ה-PWA בקובץ בינארי מקורי ל-Windows, כדי שהצוות יוכל להפיץ את האפליקציה שלנו מ-Microsoft Store.
- פעילויות אינטרנט מהימנות: הטכנולוגיה החשובה ביותר של Android שבה החברה משתמשת כדי להפיץ את אפליקציית ה-PWA שלנו כאפליקציה מקורית ברקע.
בתרשים הבא מוצג מה מיושם באמצעות TypeScript ו-React קלאסיים, ומה מיושם באמצעות SwiftWasm ו-JavaScript, Swift ו-WebAssembly רגילים. בחלק הזה של הפרויקט נעשה שימוש ב-JSKit, ספרייה של יכולת פעולה הדדית ב-JavaScript ל-Swift ול-WebAssembly. הצוות משתמש בספרייה הזו כדי לטפל ב-DOM במסך העריכה מקוד ה-Swift שלנו במקרה הצורך, או אפילו להשתמש בממשקי API מסוימים שספציפיים לדפדפנים.
למה כדאי להשתמש ב-Wasm ובאינטרנט?
אמנם Apple לא תומכת באופן רשמי ב-Wasm, אבל צוות המהנדסים של Goodnotes החליט שהגישה הזו היא ההחלטה הטובה ביותר מהסיבות הבאות:
- שימוש חוזר ביותר מ-100 אלף שורות קוד.
- היכולת להמשיך בפיתוח המוצר העיקרי תוך תרומה גם לאפליקציות בפלטפורמות שונות.
- היכולת להגיע לכל פלטפורמה בהקדם האפשרי באמצעות תהליך פיתוח איטרטיבי.
- שליטה על היכולת להציג את אותו מסמך בלי להכפיל את כל הלוגיקה העסקית ולהציג הבדלים בהטמעות שלנו.
- ליהנות מכל שיפורי הביצועים שבוצעו בכל הפלטפורמות בו-זמנית (ומכל תיקוני הבאגים שהוחלו בכל הפלטפורמות).
שימוש חוזר ביותר מ-100 אלף שורות קוד, וגם בלוגיקה העסקית שמטמיעה את צינור העיבוד שלנו לעיבוד תמונות, היה חיוני. במקביל, הפיכת הקוד ב-Swift לתואמת לערכות כלים אחרות מאפשרת להם לעשות שימוש חוזר בקוד הזה בפלטפורמות שונות בעתיד, אם יהיה צורך.
פיתוח מוצרים איטרטיבי
הצוות נקט גישה איטרטיבית כדי לספק למשתמשים משהו מהר ככל האפשר. Goodnotes התחיל בגרסה לקריאה בלבד של המוצר, שבה המשתמשים יכלו לקבל כל מסמך ששותף ולקרוא אותו מכל פלטפורמה. בעזרת קישור, הם יוכלו לגשת לאותו הערות שכתבו ב-iPad ולקרוא אותן. בשלב הבא הוספנו תכונות עריכה, כדי שהגרסאות בפלטפורמות השונות יהיו זהות לגרסה ל-iOS.
פיתחנו את הגרסה הראשונה של המוצר לקריאה בלבד במשך שישה חודשים, והתשעה החודשים הבאים הוקדשו לקבוצה הראשונה של תכונות העריכה ולמסך הממשק המשתמש שבו אפשר לבדוק את כל המסמכים שיצרתם או ששותפו איתכם. בנוסף, תכונות חדשות של פלטפורמת iOS הועברו בקלות לפרויקט בפלטפורמות שונות, הודות ל-SwiftWasm Toolchain. לדוגמה, נוצר סוג חדש של עט והוטמע בקלות בפלטפורמות שונות באמצעות שימוש חוזר באלפי שורות קוד.
פיתוח הפרויקט הזה היה חוויה מדהימה, ואנחנו ב-Goodnotes למדנו ממנו הרבה. לכן, בקטעים הבאים נתמקד בנושאים טכניים מעניינים לגבי פיתוח אינטרנט ושימוש ב-WebAssembly ובשפות כמו Swift.
מכשולים ראשוניים
העבודה על הפרויקט הזה הייתה מאתגרת מאוד מנקודות מבט שונות. המכשול הראשון שהצוות נתקל בו היה קשור ל-toolchain של SwiftWasm. כלי הפיתוח עזרו מאוד לצוות, אבל לא כל הקוד ל-iOS תואם ל-Wasm. לדוגמה, לא ניתן היה לעשות שימוש חוזר בקוד שקשור ל-IO או לממשק המשתמש, כמו הטמעת תצוגות, לקוחות API או גישה למסד הנתונים. לכן הצוות נאלץ להתחיל לבצע רפאקציה של חלקים ספציפיים באפליקציה כדי שיהיה אפשר לעשות בהם שימוש חוזר מהפתרון בפלטפורמות שונות. רוב בקשות העריכה שהצוות יצר היו שינויים מבניים של יחסי התלות כדי שהצוות יוכל להחליף אותם מאוחר יותר באמצעות הזרקת יחסי תלות או שיטות דומות אחרות. במקור, הקוד ל-iOS כלל שילוב של לוגיקה עסקית גולמית שאפשר להטמיע ב-Wasm, עם קוד שאחראי על קלט/פלט ועל ממשק המשתמש, ולא ניתן להטמיע ב-Wasm כי הוא לא תומך בשניהם. לכן, אחרי שהלוגיקה העסקית ב-Swift הייתה מוכנה לשימוש חוזר בפלטפורמות שונות, היה צריך להטמיע מחדש את הקוד של הקלט/פלט וממשק המשתמש ב-TypeScript.
בעיות בביצועים נפתרו
כשהתחלנו לעבוד על העורך ב-Goodnotes, הצוות זיהה כמה בעיות בחוויית העריכה, והתמודדנו עם מגבלות טכנולוגיות מאתגרות. הבעיה הראשונה הייתה קשורה לביצועים. JavaScript היא שפה עם ליבה יחידה. כלומר, יש לו סטאק קריאות אחד וערימה אחת של זיכרון. הוא מבצע קוד לפי הסדר, וצריך לסיים את ביצוע קטע הקוד לפני שהוא עובר לקטע הבא. הוא סינכרוני, אבל לפעמים זה יכול להזיק. לדוגמה, אם הפעלת פונקציה נמשכת זמן מה או שהיא צריכה להמתין למשהו, כל הפעולות האחרות מושהות בינתיים. וזה בדיוק מה שהמהנדסים נאלצו לפתור. הערכת נתיבים ספציפיים בקוד שלנו שקשורים לשכבת הרינדור או לאלגוריתמים מורכבים אחרים הייתה בעיה עבור הצוות, כי האלגוריתםים האלה היו סינכרוניים והפעלתם חסמה את הליבה הראשית. צוות Goodnotes כתב אותם מחדש כדי שהם יפעלו מהר יותר, וביצע עיבוד מחדש של חלק מהם כדי שהם יפעלו באופן אסינכרוני. הם גם הוסיפו אסטרטגיית תשואה כדי שהאפליקציה תוכל להפסיק את ביצוע האלגוריתם ולהמשיך אותו מאוחר יותר, וכך לאפשר לדפדפן לעדכן את ממשק המשתמש ולהימנע מאיבוד פריימים. לא הייתה בעיה באפליקציה ל-iOS כי היא יכולה להשתמש בשרשור ולחשב את האלגוריתמים האלה ברקע, בזמן ששרשור iOS הראשי מעדכן את ממשק המשתמש.
פתרון נוסף שצוות המהנדסים נאלץ למצוא היה העברה של ממשק משתמש שמבוסס על רכיבי HTML שמצורפים ל-DOM, לממשק משתמש של מסמך שמבוסס על לוח קנבס במסך מלא. בהתחלה, כל ההערות והתוכן שקשורים למסמך הוצגו כחלק ממבנה ה-DOM באמצעות רכיבי HTML, כמו בכל דף אינטרנט אחר. בשלב מסוים, העברנו את הפרויקט ללוח קנבס במסך מלא כדי לשפר את הביצועים במכשירים ברמה נמוכה, על ידי צמצום הזמן שבו הדפדפן עובד על עדכוני DOM.
צוות המהנדסים זיהה את השינויים הבאים, שיכולים היו לצמצם חלק מהבעיות שנתקלו בהן, אם היו מבצעים אותם בתחילת הפרויקט.
- כדי להפחית את העומס על הליבה, כדאי להשתמש בתדירות גבוהה ב-web workers לאלגוריתמים כבדים.
- כדאי להשתמש בפונקציות שיוצאות ובפונקציות שיובאות כבר מההתחלה במקום בספריית ה-interop של JS-Swift, כדי לצמצם את ההשפעה על הביצועים של היציאה מההקשר של Wasm. ספריית JavaScript לפעולות הדדיות מועילה לקבלת גישה ל-DOM או לדפדפן, אבל היא איטית יותר מפונקציות Wasm מקוריות שיוצאו.
- חשוב לוודא שהקוד מאפשר שימוש ב-
OffscreenCanvas
ברקע, כדי שהאפליקציה תוכל להעביר את כל השימוש ב-Canvas API ל-web worker, וכך למקסם את ביצועי האפליקציות בזמן כתיבת הערות. - העברת כל הביצועים הקשורים ל-Wasm ל-web worker או אפילו למאגר של web workers, כדי שהאפליקציה תוכל להפחית את עומס העבודה של השרשור הראשי.
עורך הטקסט
בעיה מעניינת נוספת הייתה קשורה לכלי ספציפי אחד, עורך הטקסט.
ההטמעה של הכלי הזה ב-iOS מבוססת על NSAttributedString
, ערכת כלים קטנה שמשתמשת ב-RTF מתחת לפני השטח. עם זאת, ההטמעה הזו לא תואמת ל-SwiftWasm, ולכן הצוות בפלטפורמות השונות נאלץ קודם ליצור מנתח מותאם אישית שמבוסס על תחביר ה-RTF, ולאחר מכן להטמיע את חוויית העריכה על ידי המרת RTF ל-HTML ולהפך. בינתיים, צוות iOS התחיל לעבוד על ההטמעה החדשה של הכלי הזה, שבה נעשה שימוש במודל מותאם אישית במקום ב-RTF, כדי שהאפליקציה תוכל לייצג טקסט עם עיצוב בצורה ידידותית לכל הפלטפורמות שמשתמשות באותו קוד Swift.
האתגר הזה היה אחד מהנקודות המעניינות ביותר בתוכנית העבודה של הפרויקט, כי הוא נפתר באופן איטרטיבי על סמך הצרכים של המשתמשים. זו הייתה בעיה מהנדסית שהצוות פתר באמצעות גישה שממוקדת במשתמש. כדי לאפשר עיבוד טקסט, הצוות נאלץ לכתוב מחדש חלק מהקוד, ולכן האפשרות לערוך טקסט נוספה במהדורה השנייה.
גרסאות חוזרות
ההתפתחות של הפרויקט בשנתיים האחרונות הייתה מדהימה. הצוות התחיל לעבוד על גרסה לקריאה בלבד של הפרויקט, וכמה חודשים לאחר מכן השיק גרסה חדשה לגמרי עם הרבה יכולות עריכה. כדי לפרסם שינויים בקוד בתדירות גבוהה בסביבת הייצור, הצוות החליט להשתמש בהרחבות (flags) של תכונות. בכל גרסה, הצוות יכול להפעיל תכונות חדשות וגם לפרסם שינויים בקוד שמטמיעים תכונות חדשות שהמשתמשים יראו רק כמה שבועות לאחר מכן. עם זאת, לדעת הצוות יש משהו שהם יכלו לשפר. לדעתם, הכנסת מערכת דינמית של דגלים של תכונות הייתה יכולה להאיץ את התהליך, כי היא הייתה מסירה את הצורך בפריסה מחדש כדי לשנות את ערכי הדגלים. כך תהיה ל-Goodnotes גמישות רבה יותר, והפריסה של התכונה החדשה תהיה מהירה יותר כי לא תצטרכו לקשר את פריסת הפרויקט להשקת המוצר.
עבודה במצב אופליין
אחת מהתכונות העיקריות שהצוות עבד עליהן היא תמיכה במצב אופליין. יכולת העריכה והשינוי של המסמכים היא אחת מהתכונות שאפשר לצפות להן מכל אפליקציה כזו. עם זאת, זו לא תכונה פשוטה כי Goodnotes תומכת בשיתוף פעולה. כלומר, כל השינויים שבוצעו על ידי משתמשים שונים במכשירים שונים אמורים להופיע בכל המכשירים, בלי לבקש מהמשתמשים לפתור התנגשויות. ב-Goodnotes פתרו את הבעיה הזו מזמן באמצעות CRDT ברקע. בזכות סוגי הנתונים המשוכפלים ללא התנגשויות, אפליקציית Goodnotes יכולה לשלב את כל השינויים שבוצעו בכל מסמך על ידי כל משתמש ולמזג את השינויים ללא התנגשויות במיזוג. השימוש ב-IndexedDB והאחסון שזמין לדפדפני אינטרנט היו גורמים משמעותיים שאפשרו את חוויית העבודה המשותפת אופליין באינטרנט.
בנוסף, פתיחת אפליקציית האינטרנט של Goodnotes גורמת לעלות הורדה ראשונית בסך כ-40MB בגלל הגודל הבינארי של Wasm. בהתחלה, צוות Goodnotes הסתמך רק על המטמון הרגיל של הדפדפן לחבילת האפליקציה עצמה ולרוב נקודות הקצה של ה-API שבהן הם משתמשים, אבל בדיעבד הם יכלו להפיק תועלת מ-Cache API ומ-service workers מהימנים יותר מוקדם יותר. בהתחלה הצוות נמנע מהמשימה הזו בגלל המורכבות המשוערת שלה, אבל בסופו של דבר הבין ש-Workbox הפחית את המורכבות שלה באופן משמעותי.
המלצות לשימוש ב-Swift באינטרנט
אם יש לכם אפליקציה ל-iOS עם הרבה קוד שאתם רוצים לעשות בו שימוש חוזר, תכינו את עצמכם כי אתם עומדים להתחיל מסע מדהים. לפני שמתחילים, כדאי לקרוא את הטיפים הבאים.
- בודקים איזה קוד רוצים לעשות בו שימוש חוזר. אם הלוגיקה העסקית של האפליקציה שלכם מוטמעת בצד השרת, סביר להניח שתרצו לעשות שימוש חוזר בקוד של ממשק המשתמש, ו-Wasm לא יעזור לכם בכך. הצוות בדק בקצרה את Tokamak, מסגרת תואמת ל-SwiftUI ליצירת אפליקציות דפדפן באמצעות WebAssembly, אבל היא לא הייתה מספיק מפותחת לצרכים של האפליקציה. עם זאת, אם באפליקציה שלכם יש לוגיקה עסקית חזקה או אלגוריתמים שמוטמעים כחלק מקוד הלקוח, Wasm הוא החבר הכי טוב שלכם.
- מוודאים שקוד ה-Swift מוכן. דפוסי תכנון תוכנה לשכבת ממשק המשתמש או ארכיטקטורות ספציפיות שיוצרות הפרדה חזקה בין הלוגיקה של ממשק המשתמש לבין הלוגיקה העסקית יהיו שימושיים מאוד, כי לא תוכלו לעשות שימוש חוזר בהטמעה של שכבת ממשק המשתמש. ארכיטקטורה נקייה או עקרונות של ארכיטקטורה משושה יהיו גם הם חיוניים, כי תצטרכו להחדיר ולספק יחסי תלות לכל הקוד שקשור ל-IO, וקל יותר לעשות זאת אם פועלים לפי הארכיטקטורות האלה שבהן פרטי ההטמעה מוגדרים כאבסוקציות, והשימוש בהיפוך יחסי התלות הוא נרחב.
- ב-Wasm אין קוד של ממשק משתמש. לכן, כדאי להחליט איזו מסגרת של ממשק משתמש תרצו להשתמש בה באינטרנט.
- JSKit יעזור לכם לשלב את קוד ה-Swift עם JavaScript, אבל חשוב לזכור שאם יש לכם נתיב חם, מעבר מ-JS ל-Swift עשוי להיות יקר ותצטרכו להחליף אותו בפונקציות שיוצאו. מידע נוסף על אופן הפעולה של JSKit מופיע במסמכי העזרה הרשמיים ובפוסט Dynamic Member Lookup in Swift, a hidden gem!.
- האפשרות לעשות שימוש חוזר בארכיטקטורה תלויה בארכיטקטורה של האפליקציה ובספריית מנגנון ביצוע הקוד האסינכרוני שבה אתם משתמשים. דפוסים כמו MVVP או ארכיטקטורה ניתנת-להרכבה יעזרו לכם לעשות שימוש חוזר במודלים של התצוגה ובחלק מהלוגיקה של ממשק המשתמש, בלי לקשר את ההטמעה ליחסי התלות של UIKit שלא ניתן להשתמש בהם עם Wasm. יכול להיות ש-RXSwift וספריות אחרות לא יהיו תואמות ל-Wasm, לכן חשוב לזכור שצריך להשתמש ב-OpenCombine, ב-async/await ובסטרימינג בקוד ה-Swift של Goodnotes.
- דחיסת הקובץ הבינארי של Wasm באמצעות gzip או brotli. חשוב לזכור שהקובץ הבינארי יהיה גדול למדי באפליקציות אינטרנט רגילות.
- גם אם אפשר להשתמש ב-Wasm בלי ה-PWA, חשוב לכלול לפחות שירות עובד (service worker), גם אם לאפליקציית האינטרנט אין מניפסט או שאתם לא רוצים שהמשתמש יתקין אותה. ה-service worker ישמור ויגיש בחינם את קובץ ה-Wasm הבינארי ואת כל משאבי האפליקציה, כדי שהמשתמשים לא יצטרכו להוריד אותם בכל פעם שהם פותחים את הפרויקט.
- חשוב לזכור שיכול להיות שתהליך הגיוס יהיה קשה יותר מהצפוי. יכול להיות שתצטרכו להעסיק מפתחי אינטרנט מוכשרים עם ניסיון מסוים ב-Swift, או מפתחי Swift מוכשרים עם ניסיון מסוים באינטרנט. אם יש לך אפשרות למצוא מהנדסים כלליים עם ידע מסוים בשתי הפלטפורמות, זה יהיה נהדר
מסקנות
פיתוח פרויקט אינטרנט באמצעות סטאק טכני מורכב תוך כדי עבודה על מוצר מלא באתגרים היא חוויה מדהימה. זה יהיה קשה, אבל זה שווה את זה. ללא הגישה הזו, לא היינו יכולים להשיק גרסה ל-Windows, ל-Android, ל-ChromeOS ולאינטרנט בזמן שאנחנו עובדים על תכונות חדשות לאפליקציה ל-iOS. בזכות סטאק הטכנולוגי הזה וצוות המהנדסים של Goodnotes, Goodnotes זמין עכשיו בכל מקום, והצוות מוכן להמשיך לעבוד על האתגרים הבאים. למידע נוסף על הפרויקט, תוכלו לצפות בשיחה של צוות Goodnotes בכנס NSSpain 2023. כדאי לנסות את Goodnotes לאינטרנט.