Goodnotes mühendislik ekibi, son iki yıldır başarılı iPad not alma uygulamasını diğer platformlara getirmek için bir proje üzerinde çalışıyor. Bu örnek olayda, 2022 yılının iPad uygulaması, ekibin on yıldan uzun süredir üzerinde çalıştığı Swift kodunu yeniden kullanarak web teknolojileri ve WebAssembly ile web, ChromeOS, Android ve Windows'a nasıl taşındığı ele alınmaktadır.
Goodnotes'i web, Android ve Windows'a sunmamızın nedeni
2021'de Goodnotes yalnızca iOS ve iPad için uygulama olarak kullanılabiliyordu. Goodnotes'taki mühendislik ekibi, büyük bir teknik zorluğa göğüs gerdi: Goodnotes'in yeni bir sürümünü ek işletim sistemleri ve platformlar için oluşturmak. Ürün, iOS uygulamasıyla tam uyumlu olmalı ve aynı notları oluşturmalıdır. PDF'nin üzerine alınan notlar veya eklenen resimler, iOS uygulamasında gösterilenlerle eşdeğer olmalı ve aynı vuruşları göstermelidir. Eklenen tüm çizgiler, kullanıcının kullandığı araçtan (ör. kalem, fosforlu kalem, dolma kalem, şekiller veya silgi) bağımsız olarak iOS kullanıcılarının oluşturabileceği çizgilerle eşdeğer olmalıdır.
Ekip, gereksinimlere ve mühendislik ekibinin deneyimine dayanarak, Swift kod tabanının zaten yazılmış ve yıllarca iyi test edilmiş olması nedeniyle en iyi seçeneğin bu kodu yeniden kullanmak olduğuna karar verdi. Ancak mevcut iOS/iPad uygulamasını başka bir platforma veya Flutter ya da Compose Multiplatform gibi bir teknolojiye taşımak neden daha iyi bir seçenek değil? Yeni bir platforma geçmek için Goodnotes'un yeniden yazılması gerekir. Bu, halihazırda uygulanmış iOS uygulaması ile sıfırdan oluşturulacak yeni uygulama arasında bir geliştirme yarışı başlatabilir veya yeni kod tabanı yetişirken mevcut uygulamada yeni geliştirmelerin durdurulmasını gerektirebilir. Goodnotes, Swift kodunu yeniden kullanabilseydi ekip, platformlar arası ekip uygulamanın temelleri üzerinde çalışırken iOS ekibi tarafından uygulanan yeni özelliklerden yararlanabilir ve özellik dengelemesine ulaşabilirdi.
Ürün, iOS'te aşağıdaki gibi özellikler eklemek için bir dizi ilginç zorluğu çözmüştü:
- Notları oluşturma.
- Belge ve not senkronizasyonu.
- Çakışmasız Kopyalanan Veri Türleri'ni kullanan notlar için çakışma çözümü.
- Yapay zeka modeli değerlendirmesi için veri analizi.
- İçerik arama ve belge dizine ekleme.
- Özel kaydırma deneyimi ve animasyonlar.
- Tüm kullanıcı arayüzü katmanlarının model uygulamasını görüntüleyin.
Mühendislik ekibi, iOS kod tabanını iOS ve iPad uygulamaları için çalıştırıp Goodnotes'in Windows, Android veya web uygulamaları olarak kullanıma sunabileceği bir projenin parçası olarak yürütebilseydi bunların hepsinin diğer platformlarda uygulanması çok daha kolay olurdu.
Goodnotes'in teknoloji yığını
Neyse ki web'de mevcut Swift kodunu yeniden kullanmanın bir yolu vardı: WebAssembly (Wasm). Goodnotes, açık kaynak ve topluluk tarafından yönetilen SwiftWasm projesiyle Wasm kullanarak bir prototip oluşturdu. Goodnotes ekibi, SwiftWasm sayesinde halihazırda uygulanmış tüm Swift kodunu kullanarak bir Wasm ikili dosyası oluşturabildi. Bu ikili program, Android, Windows, ChromeOS ve diğer tüm işletim sistemleri için progresif web uygulaması olarak gönderilen bir web sayfasına dahil edilebilir.
Amaç, Goodnotes'i bir PWA olarak yayınlamak ve her platformun mağazasında listelemektir. Projede, iOS için halihazırda kullanılan programlama dili Swift ve Swift kodunu web'de çalıştırmak için kullanılan WebAssembly'e ek olarak aşağıdaki teknolojiler de kullanıldı:
- TypeScript: Web teknolojileri için en sık kullanılan programlama dilidir.
- React ve webpack: Web için en popüler çerçeve ve paketleyici.
- PWA ve servis çalışanları: Ekip, uygulamamızı diğer iOS uygulamaları gibi çalışan çevrimdışı bir uygulama olarak sunabildiği ve uygulamayı mağazadan veya tarayıcıdan yükleyebildiğiniz için bu projenin önemli destekleyicileridir.
- PWABuilder: Ekibin uygulamamızı Microsoft Store'dan dağıtabilmesi için PWA'yı yerel bir Windows ikilisine sarmalamak üzere Goodnotes'in kullandığı ana projedir.
- Güvenilir Web Etkinlikleri: Şirketin, PWA'mızı yerel bir uygulama olarak dağıtmak için kullandığı en önemli Android teknolojisidir.
Aşağıdaki şekilde, klasik TypeScript ve React kullanılarak nelerin, SwiftWasm ve standart JavaScript, Swift ve WebAssembly kullanılarak nelerin uygulandığı gösterilmektedir. Projenin bu bölümünde, Swift ve WebAssembly için bir JavaScript birlikte çalışabilirlik kitaplığı olan JSKit kullanılır. Ekip, gerektiğinde düzenleyici ekranımızdaki DOM'u Swift kodumuzdan yönetmek veya hatta tarayıcıya özgü bazı API'leri kullanmak için bu kitaplığı kullanır.
Wasm ve web'i neden kullanmalısınız?
Wasm, Apple tarafından resmi olarak desteklenmese de Goodnotes mühendislik ekibi bu yaklaşımın en iyi karar olduğunu şu nedenlerle düşünüyor:
- 100.000'den fazla kod satırının yeniden kullanılması.
- Platformlar arası uygulamalara katkıda bulunurken temel üründe geliştirmeye devam etme olanağı.
- Tekrara dayalı bir geliştirme süreci kullanarak her platforma en kısa sürede ulaşma gücü.
- Tüm iş mantığını kopyalamadan ve uygulamalarımızda farklılıklar oluşturmadan aynı dokümanı oluşturma kontrolüne sahip olmak.
- Her platformda aynı anda yapılan tüm performans iyileştirmelerinden (ve her platformda uygulanan tüm hata düzeltmelerinden) yararlanabilirsiniz.
100 bin satırdan fazla kodun ve oluşturma ardışık düzenimizi uygulayan iş mantığının yeniden kullanılması çok önemliydi. Aynı zamanda Swift kodunu diğer araç zincirleriyle uyumlu hale getirmek, gerektiğinde bu kodu gelecekte farklı platformlarda yeniden kullanmalarına olanak tanır.
Yinelemeli ürün geliştirme
Ekip, kullanıcılara mümkün olduğunca hızlı bir şekilde bir çözüm sunmak için yinelemeli bir yaklaşım benimsedi. Goodnotes, kullanıcıların paylaşılan dokümanları alıp herhangi bir platformdan okuyabileceği salt okuma sürümüyle başladı. Bağlantıyı kullanarak iPad'lerinden yazdıkları notlara erişip okuyabilirler. Bir sonraki aşamada, platformlar arası sürümleri iOS sürümüne eşdeğer hale getirmek için düzenleme özellikleri eklendi.
Salt okunur ürünün ilk sürümünün geliştirilmesi altı ay sürdü. Sonraki dokuz ay ise ilk düzenleme özelliklerine ve oluşturduğunuz veya başkalarının sizinle paylaştığı tüm dokümanları kontrol edebileceğiniz kullanıcı arayüzü ekranına ayrıldı. Ayrıca, SwiftWasm araç seti sayesinde iOS platformunun yeni özelliklerinin platformlar arası projeye aktarılması kolaydı. Örneğin, binlerce satır kod yeniden kullanılarak yeni bir kalem türü oluşturuldu ve platformlar arası kolayca uygulandı.
Bu projeyi oluşturmak inanılmaz bir deneyimdi ve Goodnotes bu süreçten çok şey öğrendi. Bu nedenle, aşağıdaki bölümlerde web geliştirme ve WebAssembly ile Swift gibi dillerin kullanımıyla ilgili ilginç teknik noktalara odaklanacağız.
İlk engeller
Bu projede çalışmak birçok açıdan son derece zordu. Ekibin karşılaştığı ilk engel, SwiftWasm araç zinciriyle ilgiliydi. Araç zinciri ekip için büyük bir destek oldu ancak tüm iOS kodları Wasm ile uyumlu değildi. Örneğin, G/Ç veya kullanıcı arayüzüyle ilgili kodlar (ör. görünümlerin uygulanması, API istemcileri veya veritabanına erişim) yeniden kullanılamadığından ekibin, platformlar arası çözümde yeniden kullanabilmek için uygulamanın belirli bölümlerini yeniden tasarlamaya başlaması gerekiyordu. Ekibin oluşturduğu PR'lerin çoğu, bağımlılıkların soyut hale getirilmesi için yeniden yapılandırıldı. Böylece ekip, daha sonra bağımlılık ekleme veya benzer stratejiler kullanarak bunları değiştirebildi. iOS kodu başlangıçta Wasm'de uygulanabilen ham iş mantığını, Wasm'de uygulanamayan giriş/çıkış ve kullanıcı arayüzünden sorumlu kodla karıştırıyordu. Bu nedenle, Swift iş mantığı platformlar arasında yeniden kullanılmaya hazır olduğunda G/Ç ve kullanıcı arayüzü kodunun TypeScript'te yeniden uygulanması gerekiyordu.
Çözülen performans sorunları
Goodnotes, düzenleyici üzerinde çalışmaya başladıktan sonra ekip, düzenleme deneyimiyle ilgili bazı sorunlar tespit etti ve yol haritamıza zorlu teknolojik kısıtlamalar eklendi. İlk sorun performansla ilgiliydi. JavaScript tek iş parçacıklı bir dildir. Bu, bir çağrı yığınına ve bir bellek yığınına sahip olduğu anlamına gelir. Kodu sırayla yürütür ve bir kod parçasının yürütülmesini tamamlamadan diğerine geçemez. Senkronizedir ancak bazen bu durum zararlı olabilir. Örneğin, bir işlevin yürütülmesi biraz zaman alıyorsa veya bir şeyi beklemesi gerekiyorsa bu süre zarfında her şey donar. Mühendislerin çözmesi gereken sorun da tam olarak buydu. Kod tabanımızdaki oluşturma katmanı veya diğer karmaşık algoritmalarla ilgili belirli yolları değerlendirmek ekip için bir sorundu. Bunun nedeni, bu algoritmaların senkronize olması ve yürütülmesinin ana iş parçacısını engellemesiydi. Goodnotes ekibi, bu işlevleri daha hızlı hale getirmek için yeniden yazdı ve bazılarını asenkron hale getirmek için yeniden yapılandırdı. Ayrıca, uygulamanın algoritma yürütmeyi durdurup daha sonra devam edebilmesi için bir verim stratejisi de sundular. Bu strateji, tarayıcının kullanıcı arayüzünü güncellemesine ve kare atlamasını önlemesine olanak tanır. Ana iOS iş parçacığı kullanıcı arayüzünü güncellerken iOS uygulaması, iş parçacıklarını kullanabildiği ve bu algoritmaları arka planda değerlendirebildiği için bu bir sorun oluşturmadı.
Mühendislik ekibinin çözmesi gereken bir diğer çözüm de DOM'a eklenmiş HTML öğelerine dayalı bir kullanıcı arayüzünü tam ekran kanvas tabanlı bir doküman kullanıcı arayüzüne taşımaktı. Proje, diğer web sayfalarında olduğu gibi HTML öğelerini kullanarak bir dokümanla ilgili tüm notları ve içeriği DOM yapısının bir parçası olarak göstermeye başladı. Ancak bir noktada, tarayıcının DOM güncellemeleriyle çalıştığı süreyi kısaltarak düşük kaliteli cihazlardaki performansı artırmak için tam ekran bir kanvas kullanmaya başladı.
Mühendislik ekibi, aşağıdaki değişikliklerin projenin başında yapılmış olsaydı karşılaşılan sorunların bir kısmını azaltabileceğini tespit etti.
- Ağır algoritmalar için web çalışanlarını sık sık kullanarak ana iş parçacığının yükünü daha fazla azaltın.
- Wasm bağlamından çıkmanın performans üzerindeki etkisini azaltmak için başlangıçtan itibaren JS-Swift birlikte çalışabilirlik kitaplığı yerine dışa aktarılan ve içe aktarılan işlevleri kullanın. Bu JavaScript birlikte çalışabilirlik kitaplığı, DOM'a veya tarayıcıya erişmek için faydalıdır ancak yerel Wasm dışa aktarılan işlevlerden daha yavaştır.
- Uygulamanın ana iş parçacığının yükünü hafifletmesi ve not yazarken uygulamaların performansını en üst düzeye çıkarmak için Canvas API'nin tüm kullanımını bir web işleyicisine taşıyabilmesi amacıyla kodun,
OffscreenCanvas
kullanımına izin verdiğinden emin olun. - Uygulamanın ana iş parçacığı iş yükünü azaltabilmesi için Wasm ile ilgili tüm yürütme işlemlerini bir web çalışanına veya hatta bir web çalışanı havuzuna taşıyın.
Metin düzenleyici
İlginç bir diğer sorun da metin düzenleyiciyle ilgiliydi.
Bu aracın iOS uygulaması, temelinde RTF kullanan küçük bir araç seti olan NSAttributedString
'i kullanır. Ancak bu uygulama SwiftWasm ile uyumlu olmadığından platformlar arası ekip önce RTF dil bilgisine dayalı özel bir ayrıştırıcı oluşturmak, ardından RTF'yi HTML'ye ve HTML'yi RTF'ye dönüştürerek düzenleme deneyimini uygulamak zorunda kaldı. Bu sırada iOS ekibi, bu aracın yeni uygulaması üzerinde çalışmaya başladı. Uygulamanın, stillendirilmiş metni aynı Swift kodunu paylaşan tüm platformlar için kullanıcı dostu bir şekilde gösterebilmesi amacıyla RTF kullanımı özel bir modelle değiştirildi.
Bu zorluk, kullanıcının ihtiyaçlarına göre iteratif olarak çözüldüğü için proje yol haritasındaki en ilginç noktalardan biriydi. Kullanıcı odaklı bir yaklaşımla çözülen bu mühendislik sorununda ekibin, metni oluşturabilmek için kodun bir kısmını yeniden yazması gerekiyordu. Böylece ikinci sürümde metin düzenleme özelliğini etkinleştirdiler.
Yinelemeli sürümler
Projenin son iki yıldaki gelişimi inanılmazdı. Ekip, projenin salt okunur bir sürümünde çalışmaya başladı ve birkaç ay sonra çok sayıda düzenleme özelliğine sahip yepyeni bir sürüm kullanıma sundu. Ekip, kod değişikliklerini üretime sık sık yayınlamak için özellik işaretlerini yaygın olarak kullanmaya karar verdi. Ekip her sürümde yeni özellikleri etkinleştirebilir ve kullanıcının haftalar sonra göreceği yeni özellikleri uygulayan kod değişiklikleri yayınlayabilir. Ancak ekibin iyileştirilmesi gerektiğini düşündüğü bir konu var. İşaret değerlerini değiştirmek için yeniden dağıtım yapma ihtiyacını ortadan kaldıracağından dinamik bir özellik işareti sisteminin kullanıma sunulması sürecin hızlandırılmasına yardımcı olurdu. Bu, Goodnotes'a daha fazla esneklik sağlar ve proje dağıtımını ürün sürümüne bağlaması gerekmediği için yeni özelliğin dağıtımını hızlandırır.
Çevrimdışı çalışma
Ekibin üzerinde çalıştığı önemli özelliklerden biri de çevrimdışı destektir. Dokümanları düzenleyebilme ve değiştirebilme, bu tür uygulamalardan beklenen özelliklerden biridir. Ancak Goodnotes ortak çalışmayı desteklediği için bu basit bir özellik değildir. Bu, farklı cihazlarda farklı kullanıcılar tarafından yapılan tüm değişikliklerin, kullanıcılardan herhangi bir çakışma çözmelerini istemeden her cihaza yansıtılması gerektiği anlamına gelir. Goodnotes, arka planda CRDT'leri kullanarak bu sorunu çok uzun zaman önce çözdü. Çakışmaz Kopyalanan Veri Türleri sayesinde Goodnotes, herhangi bir kullanıcının herhangi bir belgede yaptığı tüm değişiklikleri birleştirebilir ve herhangi bir birleştirme çakışması olmadan değişiklikleri birleştirebilir. IndexedDB'in kullanımı ve web tarayıcılarına sunulan depolama alanı, web'de ortak çalışma için çevrimdışı deneyimin önemli bir destekçisi oldu.
Ayrıca, Goodnotes web uygulamasının açılması, Wasm ikili dosyası boyutu nedeniyle yaklaşık 40 MB'lık ilk ön indirme maliyetine neden olur. Goodnotes ekibi başlangıçta uygulama paketi ve kullandıkları API uç noktalarının çoğu için yalnızca normal tarayıcı önbelleğini kullanıyordu. Ancak şimdi geriye dönüp bakıldığında, daha güvenilir Cache API ve hizmet çalışanlarından daha önce yararlanmış olabileceklerini düşünüyorlar. Ekip, başlangıçta karmaşık olduğu varsayılan bu görevden uzak duruyordu ancak sonunda Workbox'un bu görevi çok daha az korkutucu hale getirdiğini fark etti.
Swift'i web'de kullanmayla ilgili öneriler
Yeniden kullanmak istediğiniz çok sayıda kodun bulunduğu bir iOS uygulamanız varsa inanılmaz bir yolculuğa çıkmaya hazır olun. Başlamadan önce faydalı olabilecek bazı ipuçları verilmektedir.
- Yeniden kullanmak istediğiniz kodu kontrol edin. Uygulamanızın iş mantığı sunucu tarafında uygulanıyorsa kullanıcı arayüzü kodunuzu yeniden kullanmak istersiniz. Bu durumda Wasm size yardımcı olmaz. Ekip, WebAssembly ile tarayıcı uygulamaları oluşturmak için SwiftUI uyumlu bir çerçeve olan Tokamak'ı kısaca inceledi ancak bu çerçeve, uygulama ihtiyaçları için yeterince gelişmiş değildi. Ancak uygulamanızda istemci kodu kapsamında uygulanmış güçlü bir iş mantığı veya algoritma varsa Wasm en iyi arkadaşınız olacaktır.
- Swift kod tabanınızın hazır olduğundan emin olun. Kullanıcı arayüzü katmanı için yazılım tasarımı kalıpları veya kullanıcı arayüzü mantığınız ile iş mantığınız arasında güçlü bir ayrım oluşturan belirli mimariler, kullanıcı arayüzü katmanı uygulamasını yeniden kullanamayacağınız için gerçekten kullanışlı olacaktır. IO ile ilgili tüm kodlar için bağımlılıkları eklemeniz ve sağlamanız gerektiğinden temiz mimari veya altıgen mimari ilkeleri de temel olacaktır. Uygulama ayrıntılarının soyutlamalar olarak tanımlandığı ve bağımlılık inversiyonu ilkesinin yoğun olarak kullanıldığı bu mimarileri uygularsanız bu işlemi yapmak çok daha kolay olacaktır.
- Wasm, kullanıcı arayüzü kodu sağlamaz. Bu nedenle, web için kullanmak istediğiniz kullanıcı arayüzü çerçevesine karar verin.
- JSKit, Swift kodunuzu JavaScript ile entegre etmenize yardımcı olur ancak hızlı yolunuz varsa JS-Swift köprüsünü geçmenin pahalı olabileceğini ve bu yolu dışa aktarılan işlevlerle değiştirmeniz gerektiğini unutmayın. JSKit'in işleyiş şekli hakkında daha fazla bilgiyi resmi dokümanda ve Swift'te gizli bir mücevher: Dinamik Üye Arama başlıklı makalede bulabilirsiniz.
- Mimarinizin yeniden kullanılabilir olup olmadığı, uygulamanızın izlediği mimariye ve kullandığınız eşzamansız kod yürütme mekanizması kitaplığına bağlıdır. MVVP veya birleştirilebilir mimari gibi modeller, uygulamayı Wasm ile kullanamadığınız UIKit bağımlılıkları ile ilişkilendirmeden görüntüleme modellerinizi ve kullanıcı arayüzü mantığının bir kısmını yeniden kullanmanıza yardımcı olur. RXSwift ve diğer kitaplıklar Wasm ile uyumlu olmayabilir. Bu nedenle, GoodNotes'in Swift kodunda OpenCombine, async/await ve akışları kullanmanız gerekeceğinden bunu göz önünde bulundurun.
- Wasm ikilisini gzip veya brotli kullanarak sıkıştırın. Klasik web uygulamaları için ikili dosyanın boyutunun oldukça büyük olacağını unutmayın.
- Wasm'i PWA olmadan kullanabilseniz bile, web uygulamanızda manifest yoksa veya kullanıcının uygulamayı yüklemesini istemiyorsanız en azından bir hizmet çalışanı eklediğinizden emin olun. Hizmet çalışanı, Wasm ikilisini ve tüm uygulama kaynaklarını ücretsiz olarak kaydedip sunar. Böylece kullanıcının projenizi her açtığında bunları indirmesi gerekmez.
- İşe alma sürecinin beklenenden daha zor olabileceğini unutmayın. Swift konusunda biraz deneyimi olan güçlü web geliştiricileri veya web konusunda biraz deneyimi olan güçlü Swift geliştiricileri tutmanız gerekebilir. Her iki platform hakkında da bilgi sahibi olan genel mühendisler bulabilirseniz çok iyi olur.
Sonuçlar
Zorluklarla dolu bir ürün üzerinde çalışırken karmaşık bir teknoloji grubu kullanarak web projesi oluşturmak inanılmaz bir deneyim. Zor olacak ancak buna değer. Goodnotes, bu yaklaşımı kullanmadan iOS uygulaması için yeni özellikler üzerinde çalışırken Windows, Android, ChromeOS ve web için bir sürüm yayınlayamazdı. Bu teknoloji grubu ve Goodnotes'in mühendislik ekibi sayesinde Goodnotes artık her yerde. Ekip, yeni zorluklarla çalışmaya devam etmeye hazır. Bu proje hakkında daha fazla bilgi edinmek isterseniz Goodnotes ekibinin NSSpain 2023'te verdiği bir konuşmayı izleyebilirsiniz. Web için Goodnotes'i denemeyi unutmayın.