今天聊聊 iOS 動態化的故事。
問題
在開發模式上,web 的方式是比較先進的,有各種優點,包括跨平臺/UI開發效率高,最重要的是可以時刻保證使用者看到的程式是最新的,沒有版本概念,整個系統時刻保持在掌握之中,而客戶端開發模式相對 web 開發是一種倒退,客戶端做不到這樣的動態化,無法隨時更新,目前一個客戶端程式要更新成本是很高的,需要釋出版本,也無法保證所有人都能更新到這個版本,這是最大的弱點,也是非常大的一塊需求。
原因
為什麼會有這種倒退,最主要原因是:蘋果引領的體驗優先規則。
在 iPhone 出現之前大家並不太在意一個軟體的動畫體驗,一個 web 應用是很少有動畫的,點一個按鈕,一整塊內容直接重新整理,再點個連結整個頁面變白重新整理,PC上網頁滾動都是一格格滾動的,而不是現在手機上那種順滑流暢的滾動,PC客戶端軟體也一樣,大家都覺得沒什麼問題,用得挺好,但蘋果改變了這種情況,iPhone 剛推出時頁面間切換的動畫,60fps 的絲滑滾動,點選的即時響應,微軟的人都驚呼是黑魔法,讓人用了就上癮,再也回不去,而 web 的方式還不足以像原生客戶端那樣支援這樣的流暢性,做不到好的體驗,無法被人接受,開發上優勢再多也無法幹過客戶端,參考 facebook 初期用 web 技術構建 app 的慘狀,沒辦法,服了蘋果,大家只能按照蘋果的方式幹,做原生客戶端。
當然這裡還有手機環境網路不穩定,流量費貴的原因,但這些都可以在技術上通過快取解決,最主要還是體驗問題。
那發展到今天,這個體驗問題解決了沒有呢?沒有,即使發展到今天手機效能已上天,但 web 做出來的東西體驗仍然跟客戶端有差別,大家也已經習慣了 APP 的方式,也被流暢的 APP 慣壞。現在 APP 裡也有不少功能是 web實現,但大家都知道這是犧牲了一些效能體驗去換取開發和釋出效率,只是一種權衡。
解決方案
那這個問題怎麼解決呢,現在業界有兩種方案。
1.優化 web 效能
既然用 web 方式開發的劣勢只是效能體驗跟不上,那就優化效能吧。web 效能瓶頸在哪裡?在那個有悠久歷史的 webkit 引擎,它有各種歷史問題,要改進它並不容易。我們要的是 web 的開發和釋出方式,不需要 web 的全部,那能不能重新實現一個渲染引擎呢,這個引擎可以針對原生客戶端優化,不需要相容繁雜的 web 標準,不跟 web 那些歷史問題扯上關係,於是就有了 React Native 和 Weex 這種方案,web 的方式開發,原生的方式渲染,擁有 web 優秀的開發和釋出方式,又有不錯的效能體驗,看起來很完美,很有前途的方案。
一個方案能不能推廣開讓大家都使用,主要看成本和收益。目前 React Native 和 Weex 等這些方案的接入成本是很高的,一是它們本身就是大型框架,學習成本高,後期維護成本以及團隊學習成本同樣高。二是它們還不夠成熟,還在繼續填坑中,使用的過程可能要一起去填坑。收益上也不夠理想,就目前狀況它們能代替的是那些本來用 H5 實現的模組,換成這種方式實現後效能體驗會更好,但也不能保證像原生那樣好,很多場景需要深入框架進行優化。整個 APP 都使用這種方案構建還不靠譜,部分使用又無法使整個 APP 保持動態化,總體上來說收益也沒有達到有絕對優勢的程度,成本高收益低,推廣起來會比較困難,還需期待其繼續發展。
2.原生動態化
另一種是方案是,我可以放棄用 web 的開發方式,放棄 web 跨平臺/UI開發簡單的優越性,我想繼續用原生的方式開發這個APP,但又希望這個 APP 隨時可以更新,讓程式時刻在自己掌握中,出了問題可以快速修復,還想要可以隨時更新版本快速迭代,可以不?完全可以,用動態庫就行了。
動態庫
技術上要在 iOS 上做到原生動態化比 Android 更容易,iOS 開發語言 Objective-C 天生動態,執行時都能隨意替換方法,執行時載入動態庫又是項很老的技術,只要我把增量的程式碼和資源打包到一個 framework 裡,動態下發執行時載入,修 bug,加功能都不在話下,效能完全無損,這件事就結束了。
但是呢。蘋果把載入動態庫的功能給封了,動態庫必須跟隨安裝包一起簽名才能被載入,無法通過別的途徑簽名後再下發。
為什麼這麼做呢,這又涉及到蘋果另一個創舉:稽核模式(蘋果的創舉之多令人髮指)。一個軟體,要在一個平臺上釋出,需要先通過這個平臺的人工稽核,這個好像在蘋果之前沒見過有別人這樣做過,windows 不用,mac 不用,各種 unix 不用,web 也不用,蘋果為了對自己的平臺有絕對控制權,搞了這樣一個東西,稽核模式就跟動態化衝突了,如果我一個 APP 可以不經過稽核不斷下發 framework 新增修改功能, 還需要蘋果稽核做什麼?
因為這種限制,沒法用最方便的方式進行動態更新了,整個 APP 發出去後就不受控制,有什麼嚴重 bug,需要新增什麼功能,都乖乖打個包提交給蘋果稽核,再等使用者慢慢更新,對於急性子的中國人來說,這種事難以容忍。
繞道
蘋果把動態庫這扇門關了,我們可以繞個道從另一個門進,動態加功能可以緩緩,大家需求還不那麼迫切,但修 bug 的需求就很急,很多公司 APP 的 crash 率是 KPI 來著,看著線上 crash 不斷增多又毫無辦法是很不爽的事,於是有了 waxPatch 和 JSPatch 這種方案,曲線救國。
JSPatch 把 OC 手動翻譯成 JS,在執行時通過 OC 的動態特性去呼叫和替換 OC 方法,實時修復 bug。修 bug 這個需求基本是滿足了,雖然小繞了下道,但成本還是很低的,引擎本身也很小很輕量,接入對 APP 不會有任何負面影響,在關鍵時刻又可以幫大忙,成本低收益高,於是很容易推廣開。
人慾望是無窮的,技術宅的折騰是無止境的,JSPatch 滿足了修 bug 的需求,但還是無法滿足動態化的全部需求,最大的缺點在於需要手寫 JS,雖然已經有轉換器輔助,但還沒做到100%準確,用來修 bug 還好,用來新增功能的話學習成本和開發效率還不夠。
於是有了滴滴的 DynamicCocoa 這種方案,繞了一個更大的道,從編譯階段入手,通過 clang 把 OC 程式碼編譯成自己定製的 JS 格式,再動態下發去執行,做到原生開發,動態執行,主打動態新增功能,當然順便把修 bug 也給支援了。手機 QQ 內部也有一個類似的方案,不過更進一步,他們通過 clang 把 OC 程式碼編譯成自己定製的位元組碼動態下發,然後開發一個虛擬機器去執行(驚呆了),同樣實現了原生開發,動態執行,都是 NB 得很的方案。只要底層處理做得足夠好,也是個成本低收益高的方案,不過目前都還沒開源,還沒能看到實際效果和 NB 的原始碼,挺期待。
稽核
這種方案有沒有什麼問題呢,問題在對於蘋果稽核比較尷尬。這種方案做到極致後(所有OC/C語法都支援),實際上是繞道實現了動態載入 framework 的全部功能,開發體驗還是一致的,如果蘋果同意這種方案,那相當於允許載入動態庫,那還不如直接把門開了,讓大家直接用動態庫去做這個事情,用動態庫還能在簽名時禁止使用私有 API,用這種編譯成指令碼/位元組碼下發的方案可就禁止不了了。
這跟 JSPatch 還不太一樣,JSPatch 雖然我也想推廣動態新增功能的用法,但因為開發體驗問題大部分還是用於修 bug,蘋果稽核對 JSPatch 開始也是有一些拒絕案例,後來估計看到大家只是用它來修 bug 和 crash,提升 APP 的質量,就默許了。但 DynamicCocoa 和手Q的方案一開始目標和效果就是跟載入動態庫對齊,大規模推廣後蘋果會怎麼看就不知道了。
我覺得蘋果現在的稽核方式挺有問題的,有多少APP是稽核時用一套,稽核通過後又通過後臺一些開關把不符合規則的一些功能開放出來?只要能連上網,就有N種方式修改 APP 裡的功能,蘋果完全攔不住。個人認為稽核方式應該改為只在釋出新 APP 時稽核,釋出後允許動態下發程式碼更新,版本更新也不需要重新稽核,而是通過舉報和抽查的方式去稽核已上線的 APP,這樣既能顧及開發效率,方便開發者快速迭代做出更好的 APP,也更能確保稽核效果,只是實施起來沒有現在簡單粗暴。
最後
故事講到這裡已經差不多了,再多說一點,有個讓我覺得很奇怪的問題,就是國外開發者只熱衷於使用第一種方案解決問題,也就是使用web技術,用 React Native / NativeScript 的方式去做這個事情,對第二種方案很冷淡,包括 iOS Android 都是,原生熱更新只在國內火,國外根本不感冒,國外有個 rollout 熱更新平臺也不溫不火,為什麼呢?是國外使用者更守規則,或是使用者對線上 bug 容忍度高,開發者對線上 bug 並不那麼著急?還是 Android 被 Google Play 卡死斷了這念想,iOS JSPatch 之類的方案推廣不利?缺少國外一線開發者的支援,讓系統原生支援動態化就比較困難了。
個人認為由系統支援動態化(允許載入動態庫)在當前環境下是最好的,兼顧開發效率和 APP 體驗,雖然不能跨平臺,但也還能接受,可惜這個主動權掌握在蘋果手上,開發者無能為力,才會出現這麼多強行繞道突破的方案。web 的方式可能是未來,但目前適用範圍有限,接下來怎麼發展,拭目以待吧。