问题描述
iOS开发中经常要用到模拟器,甚至比真机被用得更频繁。模拟器相对真机有下面几种优势:
* 模拟器一般不卡,性能往往比在真机上跑更稳定,因为电脑有更大的内存,更稳定的网络。
* 可以模拟系统、设备、地理位置等。
* 调IM时,加一个模拟器,就可以互发消息了。
* 导Sandbox数据方便。
* 抓包比真机方便。
* 调试比真机方便,真机需要装证书。
* ...
然而,有时候第三方SDK集成时,第三方SDK可能不提供模拟器的x86架构,那么在链接时,就会提示无法找到符号。
如tinyLibTool项目中的demo,链接时,会报没有找到x86_64架构对应的符号:
如果用lipo -info
命令查看libMyLib.a
这个库,就会发现它只提供了 arm7
和 arm64
两种架构,而没有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
里面,如果调用空对象的方法,程序并不会有问题,只是什么都不做。如下面代码,虽然footer
为nil
,仍不会崩溃。
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开始。
项目中使用到的libTooling 和 AST 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
网友评论
LICENSE demo input.zip output tiny-lib-tool tinyLibTool.py
README.md input libSim.xcodeproj proj_tool.rb tiny-lib-tool-src tmp
➜ tinyLibTool git:(master) ✗ python tinyLibTool.py
input: /Users/xxxx/Workspace/tinyLibTool/input,output: /Users/xxxx/Workspace/tinyLibTool/output
Traceback (most recent call last):
File "tinyLibTool.py", line 235, in <module>
configEnv()
File "tinyLibTool.py", line 228, in configEnv
raise "No clang environment..."
TypeError: exceptions must be old-style classes or derived from BaseException, not str
➜ clang -v
Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin17.2.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
之前可以的,今天再用的时候,报错了