解決引入SDK后無法運(yùn)行模擬器問題

問題描述

iOS開發(fā)中經(jīng)常要用到模擬器,甚至比真機(jī)被用得更頻繁。模擬器相對真機(jī)有下面幾種優(yōu)勢:

* 模擬器一般不卡,性能往往比在真機(jī)上跑更穩(wěn)定,因?yàn)殡娔X有更大的內(nèi)存,更穩(wěn)定的網(wǎng)絡(luò)。
* 可以模擬系統(tǒng)、設(shè)備、地理位置等。
* 調(diào)IM時,加一個模擬器,就可以互發(fā)消息了。 
* 導(dǎo)Sandbox數(shù)據(jù)方便。
* 抓包比真機(jī)方便。
* 調(diào)試比真機(jī)方便,真機(jī)需要裝證書。
* ...

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

tinyLibTool項(xiàng)目中的demo,鏈接時,會報沒有找到x86_64架構(gòu)對應(yīng)的符號:

如果用lipo -info 命令查看libMyLib.a這個庫,就會發(fā)現(xiàn)它只提供了 arm7arm64兩種架構(gòu),而沒有x86_64架構(gòu)。

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

如果碰到這種庫,引入它之后,項(xiàng)目就不再能在模擬器上運(yùn)行了,因?yàn)樗溄佣疾粫^。而我們往往希望引入庫之前的其他功能仍能在模擬器上調(diào)試。

解決思路

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

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

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

空實(shí)現(xiàn),指的是函數(shù)里什么都不做,直接返回。如:

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

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

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

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

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

確定了這種思路后,還可以把這種邏輯泛化應(yīng)用到任意的庫中,通過使用適當(dāng)?shù)墓ぞ?,可以自動解析objc或cpp的頭文件,產(chǎn)生相應(yīng)空實(shí)現(xiàn)的代碼,并編譯產(chǎn)生需要的x86架構(gòu)的庫。

工具

下面介紹,我寫的工具tinyLibTool。使用它,基本可以自動產(chǎn)生空實(shí)現(xiàn)的函數(shù)。工程目錄說明:


使用步驟:

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

1. objc頭文件解析

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

獲取類名和類的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這個函數(shù)。

2. cpp頭文件解析

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

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

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

使用clang解析的部分較復(fù)雜,你可以直接用項(xiàng)目中生成的tiny-lib-tool?;蛘哒f工程中沒有涉及cpp接口,你可以跳過這個章節(jié)。

clang環(huán)境

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

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

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

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

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

c++編譯工具和llvm都需要寫到環(huán)境變量中(最好放到.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工具

環(huán)境準(zhǔn)備好后,就可以進(jìn)入 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基礎(chǔ)

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

項(xiàng)目中使用到的libToolingAST Matcher 也可以看下。

項(xiàng)目中主要代碼,添加Matcher:

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

Matcher的回調(diào)

virtual void run(const MatchFinder::MatchResult &Result){
    if (const FunctionDecl *cmd1 = Result.Nodes.getNodeAs<FunctionDecl>("staticFuncDecl")) {
        // 只處理當(dāng)前文件,不處理被包含頭文件中的類
        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. 產(chǎn)生lib工程

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

ruby proj_tool.rb

該腳本會把實(shí)現(xiàn)文件加入工程中,并打開工程。當(dāng)然,你也可以手動創(chuàng)建lib工程,手動添加實(shí)現(xiàn)文件。

注: 選用ruby腳本,是因?yàn)樗鼘code工程文件的支持比較好,有一個專門的xcodeproj庫。

TODO&Bug

1. 解析objc也用clang來解析。
2. 解析時,動態(tài)處理未知的符號定義。參考cling。
3. std::string等標(biāo)準(zhǔn)庫類型會被解析成內(nèi)部實(shí)現(xiàn)。

引用&參考

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容