解決引入SDK后無法運行模擬器問題

問題描述

iOS開發中經常要用到模擬器,甚至比真機被用得更頻繁。模擬器相對真機有下面幾種優勢:

* 模擬器一般不卡,性能往往比在真機上跑更穩定,因為電腦有更大的內存,更穩定的網絡。
* 可以模擬系統、設備、地理位置等。
* 調IM時,加一個模擬器,就可以互發消息了。 
* 導Sandbox數據方便。
* 抓包比真機方便。
* 調試比真機方便,真機需要裝證書。
* ...

然而,有時候第三方SDK集成時,第三方SDK可能不提供模擬器的x86架構,那么在鏈接時,就會提示無法找到符號。

tinyLibTool項目中的demo,鏈接時,會報沒有找到x86_64架構對應的符號:

如果用lipo -info 命令查看libMyLib.a這個庫,就會發現它只提供了 arm7arm64兩種架構,而沒有x86_64架構。

# lipo -info libMyLib.a
Architectures in the fat file: libMyLib.a are: armv7 arm64

如果碰到這種庫,引入它之后,項目就不再能在模擬器上運行了,因為它鏈接都不會過。而我們往往希望引入庫之前的其他功能仍能在模擬器上調試。

解決思路

你可以要求SDK廠商提供模擬器的版本,他們頂多改幾行腳本,多產生一個x86架構,再把兩個.a合并就行。但是如果碰上比較老沒有維護的SDK,或者廠商認為SDK不需要考慮模擬器上運行的場景,那就比較麻煩了。

你可以把所有用到SDK的代碼通過TARGET_OS_SIMULATOR宏來判斷。但是這樣可能工作量比較大,而且容易出問題。

這里另外給出一種思路,我們可以根據庫中的頭文件,自己空實現這些接口,最后編譯產生一個x86架構的庫,并把它加到工程里面,這樣工程鏈接時就不會出錯了。

空實現,指的是函數里什么都不做,直接返回。如:

+ (instancetype)footerWithRefreshingTarget:(id)target refreshingAction:(SEL)action {
    return 0;
}

我們知道,objc里面,如果調用空對象的方法,程序并不會有問題,只是什么都不做。如下面代碼,雖然footernil,仍不會崩潰。

MyRefreshFooter *footer = [MyRefreshFooter footerWithRefreshingTarget:nil refreshingAction:nil];
[footer resetNoMoreData];

所以在模擬器上除了SDK的功能不能用,其他模塊的功能并不會受影響。

這種思路除了能解決編譯問題,還有種好處是,不用改任何原來工程中的代碼,只是附加了一個x86的lib,不影響應用在真機上的功能。

確定了這種思路后,還可以把這種邏輯泛化應用到任意的庫中,通過使用適當的工具,可以自動解析objc或cpp的頭文件,產生相應空實現的代碼,并編譯產生需要的x86架構的庫。

工具

下面介紹,我寫的工具tinyLibTool。使用它,基本可以自動產生空實現的函數。工程目錄說明:


使用步驟:

1. 把要解析的庫的頭文件,放入input目錄。
2. 運行 ``python tinyLibTool.py`` 腳本,產生``output``。
3. 運行 ``ruby proj_tool.rb``,自動往工程中加入文件,并打開工程。
4. 編譯工程,選擇模擬器,生產libSim.a。
5. 把libSim.a放到原來的工程,原來的工程就可以鏈接通過。

1. objc頭文件解析

objc類的語法還算比較簡單的,可以通過正則來抓取函數。

獲取類名和類的body:

re.compile(r'''(?i)(@interface\s+(\w+)\s*?(?:.*?)$.*?^@end)''', re.S|re.M)

獲取類中方法:

re.compile(r'''(?i)(^\s*[-|+]\s*?\((.*?)\)(\w*?)(.*?)\s*?);''', re.S|re.M)

具體可以查看python腳本中的 dealObjcHead這個函數。

2. cpp頭文件解析

一般SDK提供給iOS用時,會通過objc來暴露接口。如果SDK直接提供cpp接口,我們還是要空實現cpp接口。

解析cpp接口比較麻煩,特別是可能引入了c++11之類的特性,還有類中可以嵌套定義內部類,正則對嵌套的處理比較麻煩。好在我們可以借助編譯器前端clang來實現cpp的解析[libTooliing]。相關的工具源碼在tiny-lib-tool-src下,腳本調用邏輯在dealCppHeader函數中。

注:我們只需簡單地提取出函數名,手動簡單解析應該也可以,比較繁瑣而已。

使用clang解析的部分較復雜,你可以直接用項目中生成的tiny-lib-tool。或者說工程中沒有涉及cpp接口,你可以跳過這個章節。

clang環境

如果你要手動編譯tiny-lib-tool-src,需要安裝c++編譯工具鏈、llvm庫、cmake。

c++編譯工具鏈一般隨Xcode或command-line-tool提供。

llvm庫,你可以下載源碼自己編譯,或選擇下載預編譯好的庫(LLVM Download Page)。

注:推薦下載現成的,源碼編譯太費時。用brew安裝llvm可能也可以。下載現成的,最好選擇llvm 4.0.0,最新的4.0.1的libclang在mac 10.12上有問題。

cmake是目前最流行的一套c++跨平臺編譯工具,自帶Makefile、Xcode工程、VS工程、Ninja等生成器。可以通過 brew install cmake 來安裝。

c++編譯工具和llvm都需要寫到環境變量中(最好放到.zshrc或.bashrc中),如:

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
export LLVM_DIR="/work/compiler/llvm400"
export PATH="$PATH:$LLVM_DIR/bin"

你可以通過以下命令查看是否安裝成功,如果有這兩條命令,則安裝成功。

# clang -v
# llvm-config --libs

編譯clang工具

環境準備好后,就可以進入 tiny-lib-tool-src源碼目錄,然后通過cmake工具編譯了。

# cd tiny-lib-tool-src
# mkdir build
# cd build
# cmake ..
# make & make install

注:如果想用自己編譯的tiny-lib-tool,可能需要修改腳本中的
self.tool_cmd = r'''./tiny-lib-tool'''

clang libTooling基礎

如果你想了解clang,可以從 Clang documentation開始。

項目中使用到的libToolingAST Matcher 也可以看下。

項目中主要代碼,添加Matcher:

DeclarationMatcher classMatcher = functionDecl().bind("staticFuncDecl");
Matcher.addMatcher(classMatcher, &HandlerForClassMatcher);

Matcher的回調

virtual void run(const MatchFinder::MatchResult &Result){
    if (const FunctionDecl *cmd1 = Result.Nodes.getNodeAs<FunctionDecl>("staticFuncDecl")) {
        // 只處理當前文件,不處理被包含頭文件中的類
        SourceManager &srcMgr = Result.Context->getSourceManager();
        string fileName = srcMgr.getFilename(cmd1->getLocation()).str();
        if (fileName.rfind(InputFile)==string::npos) {
            return;
        }

        // 判斷是否是類中的方法
        if (const CXXMethodDecl *cmd = dyn_cast<CXXMethodDecl>(cmd1)) {
            // 類中方法聲明的處理
            //...
        else {
            // 不在類中的方法聲明的處理
            //...
        }
    }
}

3. 產生lib工程

解析了objc和cpp的header并產生相應的空實現后,我們需要創建一個lib工程,并把這些代碼加入工程中。項目根目錄中放了一個libSim的模板工程,它會被拷入output中,然后你可以調用如下腳本:

ruby proj_tool.rb

該腳本會把實現文件加入工程中,并打開工程。當然,你也可以手動創建lib工程,手動添加實現文件。

注: 選用ruby腳本,是因為它對Xcode工程文件的支持比較好,有一個專門的xcodeproj庫。

TODO&Bug

1. 解析objc也用clang來解析。
2. 解析時,動態處理未知的符號定義。參考cling。
3. std::string等標準庫類型會被解析成內部實現。

引用&參考

1. Generate C interface from C++ source code using Clang libtooling

2. Reading C type declarations

3. Clang Driver FAQ

4. LibTooling

5. LLVM Download Page

6. AST Matcher Reference

7. Building LLVM with CMake

8. CMake Cross Compiling

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容