iOS开发经验程序员iOS Developer

解决引入SDK后无法运行模拟器问题

2017-08-15  本文已影响276人  俞子将

问题描述

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

上一篇下一篇

猜你喜欢

热点阅读