721
技術社區[雲棲]
C++、Java、Objective-C、Swift 二進製兼容測試
鑒於目前動態庫在iOS App中使用越來越廣泛,二進製的兼容問題可能會成為一個令人頭疼的問題。本文主要對比一下C++、Java、Objecive-C和Swift的二進製兼容問題。
iOS端動態庫使用情況
- iOS 8開始支持App使用動態庫。
- 蘋果對提交的App的
__TEXT__
段大小是有限製的,很多巨無霸App容易超出這個限製。iOS9之前每個架構的__TEXT__
段比較小,iOS9放大到了500MB。詳細情況請看:To submit an app for review。 - 開源庫隻能通過Podfile做源碼引入,源碼依賴,編譯非常慢。
- 可持續構建也需要基於蘋果的環境,比如使用Mac Pro/Mac Mini構建。Mac Pro比較昂貴,Mac mini性能不行,構建一次需要花費大量時間。
- 大型App為了加快編譯速度,可以維護自己的私有倉庫,把依賴的庫盡量編譯成Framework,加快編譯速度。
- Swift目前必須基於動態庫開發。
- 基於動態庫構建App,升級一個動態庫需要將整個依賴樹編譯一遍。尤其是一些頻繁變動的基礎組件,比如視覺組件的改動,牽一發而動全身。
測試環境
C++、Java、OC和Swift分別實現Foo這個基類,然後再實現Bar這個子類,main則使用Bar類打印成員變量的信息。給Foo類添加成員變量member0
,重新編譯Foo(make foo && ./main),Bar和main不變,然後觀察執行結果。
代碼地址:binary_compatibility_test。
LLDB一點有用的調試技巧。更多的調試功能,請參看:The LLDB Debugger。
br set -f main.cpp -l 17 //在main.m:17打斷點
br set -f main.cpp -n main //在main.m:main函數打斷點
x/10a *(void**)bar //將bar對象的虛函數打印出來
測試結果
- C++會出現錯位,但是沒有崩潰。二進製也是比較脆弱的。
- Java能正常工作。
- OC能正常工作。OC非常適合基於動態庫的組件方式。
- Swift構造Bar對象就會崩潰。現狀讓我們非常頭疼。
warning: (x86_64) libBar.dylib unable to load swift module 'Bar' (failed to get module 'Bar' from AST context:
error: missing required module 'Foo'
)
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x1000a7a98)
* frame #0: 0x00007fff90419fd1 libobjc.A.dylib`realizeClass(objc_class*) + 1155
frame #1: 0x00007fff9041d26d libobjc.A.dylib`_class_getNonMetaClass + 127
frame #2: 0x00007fff9041d053 libobjc.A.dylib`lookUpImpOrForward + 232
frame #3: 0x00007fff9041cad4 libobjc.A.dylib`_objc_msgSend_uncached + 68
frame #4: 0x00000001000a4df5 libBar.dylib`type metadata accessor for Bar at Bar.swift:0
frame #5: 0x0000000100001584 main`main at main.swift:7
frame #6: 0x00007fff90d0f235 libdyld.dylib`start + 1
frame #7: 0x00007fff90d0f235 libdyld.dylib`start + 1
結果分析
- C++的設計沒有考慮到二進製兼容的問題,所以兼容很一般。
- Java的二進製兼容非常完美,對象成員改變,方法增刪,都不會輕易導致二進製兼容問題。詳細情況請參看:Chapter 13. Binary Compatibility。
- OC使用方法和屬性都使用消息派發,增加和刪除方法,移動方法的順序,都不會導致問題;另外對成員變量的改變做了支持,所以二進製兼容完美。
作為一種嶄新的語言,Swift的二進製兼容最差,匪夷所思啊。
另外大家討論的時候也提到C++虛函數改變順序會不會出問題。針對這個問題我驗證了一下,確認C++虛函數表裏麵函數的順序完全取決於函數在頭文件中聲明的順序。
比如Foo有func1和func2兩個虛函數,調換func1和func2的順序,不重新編譯main。在main裏麵調用func2,實際上會調用到func1。
#include "Foo.h"
using namespace std;
int main()
{
Foo *foo = new Foo();
foo->func2();
delete foo;
return 0;
}
$ lldb main
(lldb) target create "main"
Current executable set to 'main' (x86_64).
(lldb) br set -f main.cpp -l 17
Breakpoint 1: where = main`main + 65 at main.cpp:17, address = 0x0000000100000f11
(lldb) run
Process 11179 launched: '/Users/henshao/binary_compatibility_test/C++/main' (x86_64)
Process 11179 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100000f11 main`main at main.cpp:17
14 delete bar;*/
15
16 Foo *foo = new Foo();
-> 17 foo->func2();
18 delete foo;
19
20 return 0;
(lldb) x/10a *(void**)foo
0x1000900f0: 0x000000010008eff0 libfoo.dylib`Foo::func2() at Foo.cpp:10
0x1000900f8: 0x000000010008ee40 libfoo.dylib`Foo::func1() at Foo.cpp:6
0x100090100: 0x000000010008f050 libfoo.dylib`Foo::~Foo() at Foo.cpp:15
0x100090108: 0x000000010008f070 libfoo.dylib`Foo::~Foo() at Foo.cpp:15
0x100090110: 0x00007fff999b9bb8 libc++abi.dylib`vtable for __cxxabiv1::__class_type_info + 16
0x100090118: 0x000000010008ff00 libfoo.dylib`typeinfo name for Foo
0x100090120: 0x0000000000000000
0x100090128: 0x0000000000000000
0x100090130: 0x0000000000000000
0x100090138: 0x0000000000000000
參考文章
最後的最後
Golang也是一門讚新的語言,我非常好奇它對二進製兼容這塊是怎麼考慮的,所以歡迎廣大有為青年補充一個Golang版本。
最後更新:2017-10-23 15:34:05