限於iOS AppStore的審核機制,一些新的功能的添加或者bug的修復,想做些節日專屬的活動等,幾乎都是不太可能的.從已有的經驗來看,也是有了一些比較常用的解決方案.本文先是會簡單說明對比大部分方案,然后會注重闡述基於JSPatch的在線更新機制的設計和實現.對於任何一家有一定用戶基礎的iOS應用來說,在線更新技術所產生的直接和間接價值都將遠遠超過100W.理解,並掌握它;實在沒有時間,就記住它,因為這篇文章不僅僅是討論.
實例地址:https://github.com/ios122/ios122
幾種在線更新方案的對比: 為什么是JSPatch?
方案一: 申請"加急審核"
- 方法: 提交應用時,選擇"加急審核".
- 優點: 操作簡單,只需要重新上傳應用即可;
- 缺點:"加急審核",肯定是不能經常使用的.
- 簡評: 我想,這可能是大多數公司遇到緊急問題時,最常使用的方案.一個應用,每年是有若干次機會申請"加急審核",來縮短應用新版本的審核周期.通常審核周期是7天左右;"加急審核",通常只需要3天左右.
方案二: 使用 webview + Html5 頁面
- 方法: 特定的可能需要經常換的頁面使用WebView來顯示,內部使用Html5的內容來填充.當需要改變頁面時,只需要改變下服務器接口返回的內容即可.
- 優點: 對於內容的更新,足夠靈活和迅速.
- 缺點: 無法修復非HTML5頁面的Bug;Html5 交互和UI通常遜色於原生頁面.
- 簡評: 混合應用常用的方式,如PhoneGap等;對於大多數原生應用來說,此方案基本無適用性.
方案三: 編寫基於ReactNative的應用
- 方法: 使用 ReactNative 來編寫應用或應用的部分頁面,更多介紹參見: React Native 官方文檔中文版
- 優點: 原生UI,原生交互,支持服務器方式在線更新應用.
- 缺點: 對於非ReactNative編寫的頁面無能為力.
- 簡評: 個人主觀是很看好 ReactNative的,也在慢慢踩坑;但現實是大部分公司的已有項目是基於Objetive-C的,所以基於ReactNative的在線更新策略,目前對於大多說公司來說也並不具有可行性.
方案四: 基於JSPatch實現在線補丁式更新
- 方法: 在自己的項目中引入JSPatch庫,然后參見下文繼續討論的方案細節實施即可.JSPatch的入門使用,參見: http://www.ios122.com/2015/11/jspatch/
- 優點: 支持操作所有工程中引入的CocoaTouch庫與各種第三方庫.可完全自由定義與重寫已有代碼的邏輯.
- 缺點: JS語法操作API,語法轉換有一定成本.
- 簡評: 大多數時候,我們需要的只是重寫下某個方法,甚至某個判斷,某個默認值,就可以很好地修復某個線上的Bug.所以,JSPatch,已經夠用了.當然,如果是對於復雜的新功能的添加的話,建議還是提交審核吧.另外,不得不說一句,JSPatch + ReactNatvie 將來或許會成為一個很強力的組合,前者側重於Bug的修復,后者側重於復雜新需求的添加.本文接下來的篇幅將注重討論基於JSPatch的線上Bug的即時修復方案.
關於使用JSPatch幾個技術點的分析與實現.
基本實現原理
安裝本地所有補丁 --> 聯網更新補丁信息,並安裝有更新或新增加的補丁.注意此處的安裝,指的是執行以下JS文件中的代碼.此段代碼會替換某個類的默認實現.當App運行到需要某個類的某個被JSPatch替換的方法時,會走JS定義的邏輯,而不再是源代碼中默認的邏輯.可以看下DEMO.另外,我們的應用和示例中都使用了Objection這個依賴注入的庫,你可能也要先溫習下: [Objection,一個輕量級的Objective-C依賴注入框架
](http://www.ios122.com/2015/11/objection/)
文件 md5 值的獲取與校驗
mac上,獲取某個文件的md5值,直接在終端輸入命令:
md5 文件完整路徑.
關於校驗md5的代碼,其實最核心的是如何在oc中使用代碼獲取某個文件的md5值,然后進行比對.網上的示例很多,但可能不太靠譜,下面貼一段確實可行的,注意要引入系統庫 #include <CommonCrypto/CommonDigest.h>
:
/**
* 獲取文件的md5信息.
*
* @param path 文件路徑.
*
* @return 文件的md5值.
*/
-(NSString *)mcMd5HashOfPath:(NSString *)path
{
NSFileManager *fileManager = [NSFileManager defaultManager];
// 確保文件存在.
if( [fileManager fileExistsAtPath:path isDirectory:nil] )
{
NSData *data = [NSData dataWithContentsOfFile:path];
unsigned char digest[CC_MD5_DIGEST_LENGTH];
CC_MD5( data.bytes, (CC_LONG)data.length, digest );
NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for( int i = 0; i < CC_MD5_DIGEST_LENGTH; i++ )
{
[output appendFormat:@"%02x", digest[i]];
}
return output;
}
else
{
return @"";
}
}
補丁狀態的管理
可以毫不誇張的說,正確理解並定義補丁狀態,是整個在線更新機制最核心的一步,其他的真的只是輔助:
/**
* 補丁狀態.
*/
typedef enum : NSUInteger {
YFPatchModelStatusUnKnownError, //!< 未知錯誤.
YFPatchModelStatusUnInstall, //!< 尚未開始安裝.應用初始時,所有本地補丁狀態均為此;補丁更新或新增的補丁;在下載完成后,狀態也會設置為此.
YFPatchModelStatusSuccess, //!< 安裝成功.
YFPatchModelStatusFileNotExit, //!< 本地補丁文件不存在.
YFPatchModelStatusFileNotMatch, //!< 本地補丁MD5與給定的MD5值不匹配.
YFPatchModelStatusUpdate, //!< 此補丁有更新.即服務器最新返回的補丁列表中包含此補丁,但補丁的md5或url已改變.
YFPatchModelStatusAdd //!< 此補丁為新增的.即服務器最新返回的補丁列表中新添加的補丁.
} YFPatchModelStatus;
補丁狀態的具體管理策略,參見 https://github.com/ios122/ios122/blob/master/iOS122/iOS122/samples/JSPatchOnline/patch/YFPatchViewModel.m
如何在本地測試JS可用性
這個是必然要考慮的問題,一種方式是可以在工程中放一個demo.js供Debug模式下調試;另一種方式是本地返回固定的假數據,但是假數據本身的 JS文件地址,md5,版本號等都是真實的.
/**
* 測試模式下,會執行此方法,以驗證某個JS文件的作用.默認使用本地demo.js.
*/
- (void)mcDebug
{
#ifdef DEBUG
NSString * path = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
[self mcEvaluateScriptFile: path];
#endif
}
補丁的增刪改查.
- 增:服務器返回的補丁,本地不存在時,會默認下載存儲,並執行.
- 刪: 服務器返回的補丁集中,不包含本地的某個補丁,則此補丁下次不會再被執行.
- 改: 服務器返回的補丁,本地包含,但md5值變化,此時會重新下載此補丁.
- 查: 會默認在應用啟動時,執行所有存在,且md5值匹配的補丁.補丁集的信息,會在每次聯網更新時更新.此處使用的是一個緩存庫https://github.com/pinterest/PINCache
另外,整個邏輯的實現,還使用了ReactCocoa來簡化邏輯代碼,如果不是很熟悉,可以先看下:ReactiveCocoa,最受歡迎的iOS函數響應式編程庫(2.5版),沒有之一!
關於安全性
這個要根據自己App的情況,實際考慮下.我們的App網絡接口是基於HTTPS的,所以不存在中間人攻擊的情況.所以可以保證md5和文件路徑是我們自己可控的,所以只做了最基本的md5校驗.具體大家可以參考下官方的基於JSPatch的在線更新補丁實踐http://jspatch.com/Docs/security:
JSPatch腳本的執行權限很高,若在傳輸過程中被中間人篡改,會帶來很大的安全問題,為了防止這種情況出現,我們在傳輸過程中對JS文件進行了RSA簽名加密,流程如下:
服務端:
計算 JS 文件 MD5 值。
用 RSA 私鑰對 MD5 值進行加密,與JS文件一起下發給客戶端。
客戶端:
拿到加密數據,用 RSA 公鑰解密出 MD5 值。
本地計算返回的 JS 文件 MD5 值。
對比上述的兩個 MD5 值,若相等則校驗通過,取 JS 文件保存到本地。
官方有個內測的平台http://jspatch.com,來支持在線更新,但是我做的時候,是不知道的,有點重復造輪子的感覺.但是,也就兩天左右就實現了,只要能捋順補丁狀態控制的時機,代碼本身其實並沒有真正的技術難點.另外,官方的內測平台,好像是閉源的,我不太敢用.
關於JS文件的編寫.
可以先參考下文檔https://github.com/bang590/JSPatch/wiki/defineClass使用文檔,其實都是一一對應的語法轉義.如果代碼很多,官方還提供了轉換工具:https://github.com/bang590/JSPatchConvertor,但是結果僅供參考,可能還需要二次修改.
關於 APPstore 審核
我們的App,嵌入了JSPatch來進行Bug修復,已經通過審核,並且剛好修復了一個很緊急的Bug.這里不做過多的口水式的討論.
小結
如果還在為每次的APP更新而提心吊膽,請細細閱讀這篇文章;在線更新的,不僅僅可以用來修復Bug呦~