Catatan bagus di mana saja

Gambar pemasaran Goodnotes yang menampilkan seorang wanita menggunakan produk di iPad.

Selama dua tahun terakhir, tim engineering Goodnotes telah mengerjakan project untuk menghadirkan aplikasi pencatat iPad yang sukses ke platform lain. Studi kasus ini membahas cara aplikasi iPad terbaik tahun 2022 diakses di web, ChromeOS, Android, dan Windows yang didukung oleh teknologi web dan WebAssembly yang menggunakan kembali kode Swift yang sama yang telah dikerjakan tim selama lebih dari sepuluh tahun.

Logo Goodnotes.

Alasan Goodnotes hadir di web, Android, dan Windows

Pada tahun 2021, Goodnotes hanya tersedia sebagai aplikasi untuk iOS dan iPad. Tim engineering di Goodnotes menerima tantangan teknis yang besar: membuat Goodnotes versi baru, tetapi untuk sistem operasi dan platform tambahan. Produk harus sepenuhnya kompatibel dengan, dan merender catatan yang sama seperti aplikasi iOS. Setiap catatan yang dibuat di atas PDF, atau gambar yang dilampirkan harus setara dan menampilkan goresan yang sama dengan yang ditampilkan aplikasi iOS. Goresan yang ditambahkan harus setara dengan yang dapat dibuat pengguna iOS, terlepas dari alat yang digunakan pengguna—misalnya, pena, stabilo, pena stilus, bentuk, atau penghapus.

Pratinjau aplikasi Goodnotes dengan catatan dan sketsa tulisan tangan.

Berdasarkan persyaratan dan pengalaman tim engineer, tim dengan cepat menyimpulkan bahwa menggunakan kembali codebase Swift akan menjadi tindakan terbaik, mengingat codebase tersebut telah ditulis dan diuji dengan baik selama bertahun-tahun. Namun, mengapa tidak langsung mem-porting aplikasi iOS/iPad yang sudah ada ke platform atau teknologi lain seperti Flutter atau Compose Multiplatform? Untuk beralih ke platform baru, Goodnotes harus ditulis ulang. Tindakan ini dapat memulai perlombaan pengembangan antara aplikasi iOS yang telah diimplementasikan dan aplikasi baru yang akan di-build dari awal, atau melibatkan penghentian pengembangan baru pada aplikasi yang ada saat codebase baru menyusul. Jika Goodnotes dapat menggunakan kembali kode Swift, tim dapat memanfaatkan fitur baru yang diterapkan oleh tim iOS saat tim lintas platform mengerjakan dasar-dasar aplikasi dan mencapai paritas fitur.

Produk ini telah menyelesaikan sejumlah tantangan menarik bagi iOS untuk menambahkan fitur seperti:

  • Rendering catatan.
  • Sinkronisasi dokumen dan catatan.
  • Penyelesaian konflik untuk catatan menggunakan Jenis Data Replika Bebas Konflik.
  • Analisis data untuk evaluasi model AI.
  • Penelusuran konten dan pengindeksan dokumen.
  • Pengalaman dan animasi scroll kustom.
  • Lihat implementasi model untuk semua lapisan UI.

Semuanya akan jauh lebih mudah diterapkan untuk platform lain jika tim engineer dapat membuat codebase iOS yang sudah berfungsi untuk aplikasi iOS dan iPad dan menjalankannya sebagai bagian dari project yang dapat dikirimkan Goodnotes sebagai aplikasi Windows, Android, atau web.

Tech stack Goodnotes

Untungnya, ada cara untuk menggunakan kembali kode Swift yang ada di web—WebAssembly (Wasm). Goodnotes membuat prototipe menggunakan Wasm dengan project SwiftWasm open source dan dikelola komunitas. Dengan SwiftWasm, tim Goodnotes dapat membuat biner Wasm menggunakan semua kode Swift yang telah diimplementasikan. Biner ini dapat disertakan dalam halaman web yang dikirim sebagai Progressive Web Application untuk Android, Windows, ChromeOS, dan setiap sistem operasi lainnya.

Urutan peluncuran Goodnotes dimulai dengan Chrome, lalu Windows, diikuti dengan Android, dan platform lain seperti Linux di akhir, semuanya didasarkan pada PWA.

Tujuannya adalah merilis Goodnotes sebagai PWA, dan dapat mencantumkannya di setiap toko platform. Selain Swift, bahasa pemrograman yang sudah digunakan untuk iOS, dan WebAssembly yang digunakan untuk mengeksekusi kode Swift di web, project ini menggunakan teknologi berikut:

  • TypeScript: Bahasa pemrograman yang paling sering digunakan untuk teknologi web.
  • React dan webpack: Framework dan bundler yang paling populer untuk web.
  • PWA dan pekerja layanan: Pendorong besar untuk project ini karena tim dapat mengirimkan aplikasi kami sebagai aplikasi offline yang berfungsi seperti aplikasi iOS lainnya dan Anda dapat menginstalnya dari app store atau browser itu sendiri.
  • PWABuilder: Project utama yang digunakan Goodnotes untuk menggabungkan PWA ke dalam biner Windows native sehingga tim dapat mendistribusikan aplikasi kami dari Microsoft Store.
  • Trusted Web Activities: Teknologi Android terpenting yang digunakan perusahaan untuk mendistribusikan PWA sebagai aplikasi native di balik layar.

Stack teknologi Goodnotes yang terdiri dari Swift, Wasm, React, dan PWA.

Gambar berikut menunjukkan apa yang diimplementasikan menggunakan TypeScript klasik dan React, serta apa yang diimplementasikan menggunakan SwiftWasm dan JavaScript vanilla, Swift, dan WebAssembly. Bagian project ini menggunakan JSKit, library interoperabilitas JavaScript untuk Swift dan WebAssembly yang digunakan tim untuk menangani DOM di layar editor dari kode Swift saat diperlukan atau bahkan menggunakan beberapa API khusus browser.

Screenshot aplikasi di perangkat seluler dan desktop yang menampilkan area gambar tertentu yang didorong oleh Wasm, dan area UI yang didorong oleh React.

Mengapa menggunakan Wasm dan web?

Meskipun Wasm tidak didukung secara resmi oleh Apple, alasan berikut adalah alasan tim engineer Goodnotes merasa pendekatan ini adalah keputusan terbaik:

  • Penggunaan kembali lebih dari 100 ribu baris kode.
  • Kemampuan untuk melanjutkan pengembangan pada produk inti sekaligus berkontribusi pada aplikasi lintas platform.
  • Kemampuan untuk menjangkau setiap platform sesegera mungkin menggunakan proses pengembangan iteratif.
  • Memiliki kontrol untuk merender dokumen yang sama tanpa menduplikasi semua logika bisnis, dan memperkenalkan perbedaan dalam implementasi kami.
  • Manfaatkan semua peningkatan performa yang dilakukan di setiap platform secara bersamaan (dan semua perbaikan bug yang diterapkan di setiap platform).

Penggunaan kembali lebih dari 100 ribu baris kode, dan logika bisnis yang menerapkan pipeline rendering kami adalah hal mendasar. Pada saat yang sama, membuat kode Swift kompatibel dengan toolchain lain memungkinkan mereka menggunakan kembali kode ini di platform yang berbeda pada masa mendatang jika diperlukan.

Pengembangan produk iteratif

Tim ini menggunakan pendekatan iteratif untuk memberikan sesuatu kepada pengguna sesegera mungkin. Goodnotes dimulai dengan versi produk hanya baca tempat pengguna bisa mendapatkan dokumen bersama dan membacanya dari platform mana pun. Hanya dengan link, mereka akan dapat mengakses dan membaca catatan yang sama yang mereka tulis dari iPad. Fase berikutnya ditambahkan dalam fitur pengeditan, untuk membuat versi lintas platform setara dengan versi iOS.

Dua screenshot aplikasi yang melambangkan perubahan dari produk hanya baca menjadi produk yang memiliki fitur lengkap.

Versi pertama produk hanya baca memerlukan waktu enam bulan untuk dikembangkan, sembilan bulan berikutnya dikhususkan untuk sekumpulan fitur pengeditan pertama dan layar UI tempat Anda dapat memeriksa semua dokumen yang Anda buat atau yang dibagikan seseorang kepada Anda. Selain itu, fitur baru platform iOS mudah di-porting ke project lintas platform berkat SwiftWasm Toolchain. Sebagai contoh, jenis pena baru dibuat dan mudah diterapkan secara lintas platform dengan menggunakan kembali ribuan baris kode.

Membangun project ini adalah pengalaman yang luar biasa, dan Goodnotes telah banyak mempelajarinya. Itulah sebabnya bagian berikut akan berfokus pada poin teknis menarik tentang pengembangan web dan penggunaan WebAssembly serta bahasa seperti Swift.

Hambatan awal

Mengerjakan project ini sangat menantang dari berbagai sudut pandang. Hambatan pertama yang ditemukan tim terkait dengan toolchain SwiftWasm. Toolchain adalah enabler besar bagi tim, tetapi tidak semua kode iOS kompatibel dengan Wasm. Misalnya, kode yang terkait dengan IO atau UI—seperti implementasi tampilan, klien API, atau akses ke database tidak dapat digunakan kembali, sehingga tim perlu mulai memfaktorkan ulang bagian aplikasi tertentu agar dapat menggunakannya kembali dari solusi lintas platform. Sebagian besar PR yang dibuat tim adalah pemfaktoran ulang untuk dependensi abstrak sehingga tim nantinya dapat menggantinya menggunakan injeksi dependensi atau strategi serupa lainnya. Kode iOS awalnya mencampur logika bisnis mentah yang dapat diterapkan di Wasm dengan kode yang bertanggung jawab atas input/output dan antarmuka pengguna yang tidak dapat diterapkan di Wasm karena Wasm juga tidak mendukungnya. Jadi, kode IO dan UI perlu diimplementasikan ulang di TypeScript setelah logika bisnis Swift siap digunakan kembali di antara platform.

Masalah performa teratasi

Setelah Goodnotes mulai mengerjakan editor, tim mengidentifikasi beberapa masalah dengan pengalaman pengeditan, dan kendala teknologi yang menantang masuk ke dalam roadmap kami. Masalah pertama terkait dengan performa. JavaScript adalah bahasa dengan thread tunggal. Artinya, aplikasi memiliki satu stack panggilan dan satu heap memori. Thread ini mengeksekusi kode secara berurutan dan harus menyelesaikan eksekusi bagian kode sebelum melanjutkan ke bagian berikutnya. Proses ini bersifat sinkron, tetapi terkadang dapat berbahaya. Misalnya, jika fungsi memerlukan waktu beberapa saat untuk dieksekusi atau harus menunggu sesuatu, fungsi tersebut akan membekukan semuanya untuk sementara. Dan inilah yang harus dipecahkan oleh para engineer. Mengevaluasi beberapa jalur tertentu di codebase kami yang terkait dengan lapisan rendering atau algoritma kompleks lainnya adalah masalah bagi tim, karena algoritma ini bersifat sinkron, dan menjalankannya akan memblokir thread utama. Tim Goodnotes menulis ulang kode tersebut untuk membuatnya lebih cepat, dan memfaktorkan ulang beberapa kode untuk membuatnya asinkron. Mereka juga memperkenalkan strategi hasil sehingga aplikasi dapat menghentikan eksekusi algoritma dan melanjutkannya nanti, sehingga browser dapat mengupdate UI dan menghindari frame yang terjatuh. Hal ini tidak menjadi masalah bagi aplikasi iOS karena dapat menggunakan thread dan mengevaluasi algoritma ini di latar belakang saat thread iOS utama mengupdate antarmuka pengguna.

Solusi lain yang harus dipecahkan oleh tim engineer adalah memigrasikan UI berdasarkan elemen HTML yang dilampirkan ke DOM, ke UI dokumen berdasarkan kanvas layar penuh. Project ini mulai menampilkan semua catatan dan konten yang terkait dengan dokumen sebagai bagian dari struktur DOM menggunakan elemen HTML seperti yang dilakukan halaman web lainnya, tetapi pada suatu saat dimigrasikan ke kanvas layar penuh untuk meningkatkan performa di perangkat kelas bawah dengan mengurangi waktu yang diperlukan browser untuk mengerjakan update DOM.

Perubahan berikut diidentifikasi oleh tim engineer sebagai hal yang dapat mengurangi beberapa masalah yang dihadapi, jika mereka melakukannya di awal project.

  • Kurangi beban thread utama dengan sering menggunakan pekerja web untuk algoritma berat.
  • Gunakan fungsi yang diekspor dan fungsi yang diimpor, bukan library interop JS-Swift sejak awal sehingga dapat mengurangi dampak performa saat keluar dari konteks Wasm. Library interop JavaScript ini berguna untuk mendapatkan akses ke DOM atau browser, tetapi lebih lambat daripada fungsi yang diekspor Wasm native.
  • Pastikan kode memungkinkan penggunaan OffscreenCanvas di balik layar sehingga aplikasi dapat men-offload thread utama dan memindahkan semua penggunaan Canvas API ke pekerja web yang memaksimalkan performa aplikasi saat menulis catatan.
  • Pindahkan semua eksekusi terkait Wasm ke pekerja web atau bahkan kumpulan pekerja web sehingga aplikasi dapat mengurangi beban kerja thread utama.

Editor teks

Masalah menarik lainnya terkait dengan satu alat tertentu, editor teks. Implementasi iOS untuk alat ini didasarkan pada NSAttributedString, serangkaian alat kecil yang menggunakan RTF di balik layar. Namun, implementasi ini tidak kompatibel dengan SwiftWasm sehingga tim lintas platform terpaksa membuat parser kustom berdasarkan tata bahasa RTF terlebih dahulu, lalu menerapkan pengalaman pengeditan dengan mengubah RTF menjadi HTML dan sebaliknya. Sementara itu, tim iOS mulai mengerjakan implementasi baru untuk alat ini yang menggantikan penggunaan RTF dengan model kustom sehingga aplikasi dapat mewakili teks bergaya dengan cara yang mudah bagi semua platform yang menggunakan kode Swift yang sama.

Editor teks Goodnotes.

Tantangan ini adalah salah satu poin paling menarik dalam roadmap project karena diselesaikan secara iteratif berdasarkan kebutuhan pengguna. Ini adalah masalah engineering yang dipecahkan menggunakan pendekatan yang berfokus pada pengguna, dengan tim perlu menulis ulang sebagian kode agar dapat merender teks sehingga mereka mengaktifkan pengeditan teks dalam rilis kedua.

Rilis iteratif

Evolusi project ini selama dua tahun terakhir sangat luar biasa. Tim mulai mengerjakan versi project hanya baca dan beberapa bulan kemudian mengeluarkan versi baru dengan banyak kemampuan pengeditan. Untuk sering merilis perubahan kode ke produksi, tim memutuskan untuk menggunakan flag fitur secara ekstensif. Untuk setiap rilis, tim dapat mengaktifkan fitur baru dan juga merilis perubahan kode yang menerapkan fitur baru yang akan dilihat pengguna beberapa minggu kemudian. Namun, ada sesuatu yang menurut tim bisa ditingkatkan. Mereka berpikir bahwa memperkenalkan sistem flag fitur dinamis akan membantu mempercepat proses, karena tidak perlu melakukan deployment ulang untuk mengubah nilai flag. Hal ini akan memberikan fleksibilitas yang lebih besar kepada Goodnotes dan juga mempercepat deployment fitur baru karena Goodnotes tidak perlu menautkan deployment project ke rilis produk.

Pekerjaan offline

Salah satu fitur utama yang dikerjakan tim adalah dukungan offline. Kemampuan untuk mengedit dokumen dan mengubahnya adalah salah satu fitur yang Anda harapkan dari aplikasi seperti ini. Namun, ini bukan fitur sederhana karena Goodnotes mendukung kolaborasi. Artinya, semua perubahan yang dilakukan oleh pengguna yang berbeda di perangkat yang berbeda akan berakhir di setiap perangkat tanpa meminta pengguna untuk menyelesaikan konflik apa pun. Goodnotes telah lama memecahkan masalah ini dengan menggunakan CRDT di balik layar. Berkat Jenis Data Replika tanpa Konflik ini, Goodnotes dapat menggabungkan semua perubahan yang dilakukan pada dokumen oleh pengguna mana pun dan menggabungkan perubahan tanpa konflik penggabungan. Penggunaan IndexedDB dan penyimpanan yang tersedia untuk browser web adalah enabler besar untuk pengalaman offline kolaboratif di web.

Aplikasi Goodnotes berfungsi secara offline.

Selain itu, membuka aplikasi web Goodnotes akan menghasilkan biaya download awal sekitar 40 MB karena ukuran biner Wasm. Awalnya, tim Goodnotes hanya mengandalkan cache browser reguler untuk app bundle itu sendiri dan sebagian besar endpoint API yang mereka gunakan, tetapi setelah dipikirkan kembali, mereka seharusnya mendapatkan keuntungan dari Cache API dan pekerja layanan yang lebih andal sebelumnya. Tim ini awalnya menghindari tugas ini karena dianggap rumit, tetapi pada akhirnya, menyadari bahwa Workbox membuatnya jauh lebih mudah.

Rekomendasi saat menggunakan Swift di web

Jika Anda memiliki aplikasi iOS dengan banyak kode yang ingin digunakan kembali, bersiaplah karena Anda akan memulai perjalanan yang luar biasa. Ada beberapa tips yang mungkin menarik bagi Anda sebelum memulai.

  • Periksa kode yang ingin Anda gunakan kembali. Jika logika bisnis aplikasi Anda diterapkan di sisi server, kemungkinan Anda ingin menggunakan kembali kode UI, dan Wasm tidak akan membantu Anda di sini. Tim ini melihat Tokamak secara singkat, yaitu framework yang kompatibel dengan SwiftUI untuk mem-build aplikasi browser dengan WebAssembly, tetapi belum cukup matang untuk kebutuhan aplikasi. Namun, jika aplikasi Anda memiliki logika bisnis atau algoritma yang kuat yang diterapkan sebagai bagian dari kode klien, Wasm akan menjadi teman terbaik Anda.
  • Pastikan codebase Swift Anda sudah siap. Pola desain software untuk lapisan UI atau arsitektur tertentu yang menciptakan pemisahan yang kuat antara logika UI dan logika bisnis akan sangat berguna karena Anda tidak akan dapat menggunakan kembali implementasi lapisan UI. Arsitektur bersih atau prinsip arsitektur heksagonal juga akan menjadi dasar, karena Anda harus memasukkan dan menyediakan dependensi untuk semua kode terkait IO dan akan jauh lebih mudah dilakukan jika Anda mengikuti arsitektur ini dengan detail implementasi yang ditentukan sebagai abstraksi dan prinsip inversi dependensi sangat sering digunakan.
  • Wasm tidak menyediakan kode UI. Oleh karena itu, tentukan framework UI yang ingin Anda gunakan untuk web.
  • JSKit akan membantu Anda mengintegrasikan kode Swift dengan JavaScript, tetapi perlu diingat bahwa jika Anda memiliki hotpath, melintasi jembatan JS–Swift mungkin mahal dan Anda harus menggantinya dengan fungsi yang diekspor. Anda dapat mempelajari lebih lanjut cara kerja JSKit di latar belakang di dokumentasi resmi dan postingan Dynamic Member Lookup in Swift, a hidden gem!.
  • Apakah Anda dapat menggunakan kembali arsitektur akan bergantung pada arsitektur yang diikuti aplikasi Anda dan library mekanisme eksekusi kode asinkron yang Anda gunakan. Pola seperti MVVP atau arsitektur composable akan membantu Anda menggunakan kembali model tampilan dan bagian logika UI tanpa menggabungkan penerapan ke dependensi UIKit yang tidak dapat Anda gunakan dengan Wasm. RXSwift dan library lainnya mungkin tidak kompatibel dengan Wasm, jadi perhatikan karena Anda harus menggunakan OpenCombine, asinkron/menunggu, dan streaming dalam kode Swift Goodnotes.
  • Kompresi biner Wasm menggunakan gzip atau brotli. Perhatikan bahwa ukuran biner akan cukup besar untuk aplikasi web klasik.
  • Meskipun Anda dapat menggunakan Wasm tanpa PWA, pastikan Anda setidaknya menyertakan pekerja layanan, meskipun aplikasi web Anda tidak memiliki manifes atau Anda tidak ingin pengguna menginstalnya. Pekerja layanan akan menyimpan dan menayangkan biner Wasm secara gratis dan semua resource aplikasi sehingga pengguna tidak perlu mendownloadnya setiap kali membuka project Anda.
  • Perlu diingat bahwa perekrutan mungkin lebih sulit dari yang diperkirakan. Anda mungkin perlu mempekerjakan developer web yang berpengalaman dengan Swift atau developer Swift yang berpengalaman dengan web. Jika Anda dapat menemukan engineer generalis dengan beberapa pengetahuan di kedua platform, itu akan sangat bagus

Kesimpulan

Membuat project web menggunakan stack teknologi yang kompleks sambil mengerjakan produk yang penuh tantangan adalah pengalaman yang luar biasa. Ini akan sulit, tetapi sangat sepadan. Goodnotes tidak akan pernah merilis versi untuk Windows, Android, ChromeOS, dan web sambil mengerjakan fitur baru untuk aplikasi iOS tanpa menggunakan pendekatan ini. Berkat stack teknologi ini dan tim teknik Goodnotes, Goodnotes kini tersedia di mana saja, dan tim siap untuk terus menangani tantangan berikutnya. Jika ingin mengetahui lebih lanjut tentang project ini, Anda dapat menonton presentasi yang diberikan tim Goodnotes di NSSpain 2023. Pastikan untuk mencoba Goodnotes untuk web.