前言:
伴隨著企業的快速發展,承載著移動互聯網業務的App的更新迭代要求越來越高。尤其是在中國,App的迭代速度很快,有時App需要緊急發版來處理線上業務和技術等問題。由于Apple審核制度的限制,在Apple提速了審核速度的情況下,大多數App審核往往需要1到2天。但是這仍舊無法滿足App快速更新的需求。所以,想不通過發版,解決線上問題的方案一直為iOS端技術研發人員所青睞。
但是隨著公開版本的JSPatch被Apple拒絕。熱更新這個概念似乎冷了下來。但是App更新與審核速度的矛盾從沒有解決。分析、構思,探索和找尋解決這個問題的合理方案。
理論可行性分析:
查閱了幾種熱更新的方案,JSPatch、Aspects、Stringer、TTPatch。分別對其實現原理、上架審核現狀以及文檔和資料完備性等方面進行分析。
拒絕JSPatch:
關于Apple拒絕JSPath事件的分析。
Apple對于App的開發傾向于Native。但是拒絕JSPatch并不是因為其使用了JS動態下發代碼,而是因為這種方式存在著安全漏洞。可能被第三方篡改造成隱患。正式因為這樣,所以在市面上滴滴出行App仍舊存在熱更新。從一些資料上看到,滴滴的方案是從技術層面上對于下發的JS代碼做了安全性校驗。使得滴滴的App能夠識別這些JS是由滴滴下發的。從而避免了Apple拒絕JSPatch熱更新的安全漏洞。所以,重要的是安全性。而不是熱更新的手段。
關于Aspects方案:
Aspects的原理與消息轉發機制密切相關。Aspects主要是利用了forwardInvocation進行轉發,Aspects其實利用和kvo類似的原理,通過動態創建子類的方式,把對應的對象isa指針指向創建的子類,然后把子類的forwardInvocation的IMP替成__ASPECTS_ARE_BEING_CALLED__,假設要hook的方法名XX,在子類中添加一個Aspects_XX的方法,然后將Aspects_XX的IMP指向原來的XX方法的IMP,這樣方便后面調用原始的方法,再把要hook的方法XX的IMP指向_objc_msgForward,這樣就進入了消息轉發流程,而forwardInvocation的IMP被替換成了__ASPECTS_ARE_BEING_CALLED__,這樣就會進入__ASPECTS_ARE_BEING_CALLED__進行攔截處理,這樣整個流程大概結束。
關于Stringer方案:
雖然Stringer聲稱速度快又好,但是怎奈查看github庫,當前開發版本號是0.3.0,可以認為是還不夠成熟。并且其公開文檔、資料極少。Demo也不完備。這給實際應用帶來了麻煩。
綜合分析:
于是有了滴滴的 DynamicCocoa 這種方案,繞了一個更大的道,從編譯階段入手,通過 clang 把 OC 代碼編譯成自己定制的 JS 格式,再動態下發去執行,做到原生開發,動態運行,主打動態添加功能,當然順便把修 bug 也給支持了。手機 QQ 內部也有一個類似的方案,不過更進一步,他們通過 clang 把 OC 代碼編譯成自己定制的字節碼動態下發,然后開發一個虛擬機去執行(驚呆了),同樣實現了原生開發,動態運行,都是 NB 得很的方案。只要底層處理做得足夠好,也是個成本低收益高的方案,不過目前都還沒開源,還沒能看到實際效果和可靠的源碼。
綜合查閱以上幾個熱更新方案,遴選出Aspects方案。原因是,此方案可以實現需要的代碼修復功能,而且Aspects庫與iOS庫相關。這可以作為通過審核的有力依據。
This is stable and used in hundreds of apps since it’s part of PSPDFKit,
an iOS PDF framework that ships with apps like Dropbox or Evernote.
綜上如果你是企業賬號那么方案就是JSPatch 1.7.2版本加自己管理補丁,因為他這個平臺超過1萬會收費.但是人家js轉OC的代碼轉換器都有了開發成本很低的。
如果是需要上架的App,并且團隊的后臺安全性沒有得到充分保證的情況下,Aspects是穩妥的方案。
Aspects深入分析:
理論分析--面向切面(AOP)編程:
通過預編譯和運行期動態代理實現給程序動態統一添加功能的一種技術。可以實現不修改原始類的實現無入侵式改變應用行為,相對來講,實現簡單,易于維護。可以在需要的某一個類或實例中添加一些我們自己的實現,只針對某個切面進行Hook操作,這個就是面向切面的概念(AOP,aspect-oriented programming),框架Aspects是 AOP 編程的框架。
AOP是采用使用運行時hook方法實現的原理(iOS的消息轉發、swzziling)。
Aspects可以做什么:
AOP在開發中是一個非常重要的思想,我們希望將需求分離到非業務邏輯的方法中,盡可能的不影響業務邏輯的代碼。主要的應用場景大概有以下幾種:
參數校驗:網絡請求前的參數校驗,返回數據的格式校驗等等;
無痕埋點:統一處理埋點,降低代碼耦合度;
頁面統計:幫助統計頁面訪問量;
事務處理:攔截指定事件,添加觸發事件;
異常處理:發生異常時使用面向切面的方式進行處理;
熱修復:AOP可以讓我們在某方法執行前后或者直接替換為另一段代碼,我們可以根據這個思路,實現bug修復.
Aspects使用注意事項:
會有額外的系統開銷,要避免高頻調用。hook方法不能修改 @"retain", @"release", @"autorelease", @"forwardInvocation:"。hook類中的dealloc方法只能AspectPositionBefore。Aspects會產生額外開銷,性能不高,不建議高頻調用。
關于封裝庫的使用經驗:
JS替換實例方法會覆蓋原類和其分類的方法實現。注意有返回值和無返回值JS方法的靈活運用。注意無參數、有參數、多參數JS方法的調用。JS方法同樣可以修改某一類中代理方法的實現。與一般方法相同。JS修改方法,可以獲得上下文,參數,方法名。可以根據具體需要來進行邏輯處理。
部分JS代碼:
fixInstanceMethodAfter('UBTestViewController', 'initViewLayout', function(instance, originInvocation, originArguments){
? ? //runVoidInstanceWithNoParamter(instance, 'showProgressForCommit');
? ? //runVoidInstanceWith1Paramter(instance, 'showText:','這是熱更新測試');//替換某個方法,為該類的已有方法
? ? //下面是用JS語法設置OC的實例對象
? ? var coffeeVo = runClassWithNoParamter('UBCoffeeModel', 'new');
? ? runVoidInstanceWith1Paramter(coffeeVo, 'setBrand:','luckin');
? ? runVoidInstanceWith1Paramter(coffeeVo, 'setPrice:','16');
? ? runVoidInstanceWith1Paramter(instance, 'setCoffee:',coffeeVo);
? ? //runVoidInstanceWith2Paramter(instance, 'resetMyCoffee::',coffeeVo);
? ? console.log('這是熱更新測試 替換方法驗證##');
});
對于block可以參考如下代碼。
//參數作為方法的參數,在JS中的調用(makeMyCoffeeComplete:的參數在原OC實現是一個block)
fixInstanceMethodReplace('UBTestViewController', 'makeMyCoffeeComplete:', function(instance, originInvocation, originArguments){
? ? if (originArguments[0]) {//取出參數,這里在對應的OC中,這個參數是block也就是function
? ? ? ? var callback = originArguments[0];
? ? ? ? callback(20);//block? 作為參數,直接調用
? ? }
? ? console.log('這是熱更新測試 替換block方法驗證##' + originArguments[0]);
});
部分對Aspects的封裝代碼(借鑒了網上搜到的信息):
+ (void)_runVoidInstanceWithInstance:(id)instance selector:(NSString *)selector obj1:(id)obj1 obj2:(id)obj2 {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
? ? [instance performSelector:NSSelectorFromString(selector) withObject:obj1 withObject:obj2];
#pragma clang diagnostic pop
}
+ (void)_runVoidInstanceWithInstance:(id)instance selector:(NSString *)selector withObjects:(NSArray *)objects {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
? ? [[UBFreeFixManger sharedInstance] performSelector:NSSelectorFromString(selector) target:instance? withObjects:objects];
#pragma clang diagnostic pop
}
+ (void)_runVoidInstanceWithInstance:(id)instance selector:(NSString *)selector {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
? ? [instance performSelector:NSSelectorFromString(selector)];
#pragma clang diagnostic pop
}
特別的,基于Aspects的熱修復,其目的是“熱修復”,也僅僅是熱修復。不建議通過這種方式隨意修改一般功能邏輯。我想這也是基于Aspects的熱修復能得到Apple認可的原因。
以上可行性分析很多資料借鑒和來源于網絡,Aspects方案經過筆者仔細實踐。Demo源碼可以留言索取。
參考資料:
1、TTPatch【類JSPatch】<有成功案例、JS代碼下發,轉成原生代碼>
《TTPatch使用》
http://www.lxweimin.com/p/470a9e49b4f2
2、iOS中OC轉Javascript的工具
https://blog.csdn.net/u013602835/article/details/52777461
3、Aspects<數百成功案例、JS代碼下發,轉成原生代碼>
http://www.lxweimin.com/p/2c93446d86bd
4、《iOS AOP框架Aspects實現原理》
http://www.lxweimin.com/p/0d43db446c5b
5、《輕量級低風險 iOS 熱更新方案》
https://mp.weixin.qq.com/s/2re_s3NmOvE9RXlbGQqGDA
6、《iOSHotFixDemo》
https://github.com/3KK3/iOSHotFixDemo/tree/master/iOSHotFixDemo
7、《OS 2020 熱更新》
https://blog.csdn.net/u013712343/article/details/107932706
8、《《【iOS 教程】亮劍: Stinger到底能比Aspects快多少》
https://blog.csdn.net/weixin_47143210/article/details/105603880》
9、iOS使用Aspects做簡單熱修復原理
https://www.pianshen.com/article/9576238931/
10、動態交換方法實現
https://blog.csdn.net/WangErice/article/details/51211328
11、iOS AOP框架Aspects實現原理
http://www.lxweimin.com/p/0d43db446c5b
12、Mac啟動本地服務
http://www.lxweimin.com/p/90d5fa728861
13、熱更新方案
https://srxboys.github.io/2018/06/03/%E7%83%AD%E6%9B%B4%E6%96%B0%E6%96%B9%E6%A1%88/