解决引入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阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容