一、知识点
1.1编译器和解释器
iOS编写的代码是使用编译器将代码编译成机器码,直接在CPU上运行机器码。像Java是先使用编译器将代码编译成字节码,再通过解释器将字节码解释为不同平台的机器码。
- 编译器优点是执行效率高,缺点是调试周期长。
- 解释器优点是方便调试,缺点是执行效率低。
1.2 iOS的编译器
iOS现在的编译器集合叫做LLVM,其内置编译器为lld。编译器将每个文件都编译成Mach-O(可执行文件),链接器将多个Mach-O合并成一个。
编译的过程:
- LLVM预处理代码,比如将宏嵌入到对应的位置。
- LLVM对代码进行词法分析和语法分析,生成AST(抽象语法树)。AST结构更简单,遍历更快,而且可以快速生成IR。
- 最后AST生成IR(一种更接近于机器码的语言),IR可以生成多份适合不同平台的机器码。对于iOS来说,IR生成的机器码是Mach-O。
1.3 链接器的功能
- 将变量、函数符号和其地址绑定起来
- 将多个Mach-O文件合成一个
1.4 动态库链接
静态库是编译时链接的库,会链接到Mach-O文件中。动态库是运行时链接的库,使用dyld实现动态加载。
dyld做了哪些事:
- 先执行 Mach-O 文件,根据 Mach-O 文件里 undefined符号加载对应的动态库,系统会设置一个共享缓存来解决递归依赖问题。
- 加载后,将 undefined 的符号绑定到动态库里对应的地址上。
- 最后再处理 +load 方法,main 函数返回后运行 static terminator。
二、课后作业
在 App 运行时通过 dlopen 和 dlsym 链接加载 bundle 里的动态库。
实现思路
1.制作一个简单的包含动态库的bundle文件
2.在运行时通过dlopen函数打开对应的动态库可执行文件,通过dlsym函数找到对应符号的函数进行调用。
2.1 如何制作动态库和bundle文件
这一步就不做过多解释了,网上很多对应的文章。这里我还是以戴铭老师对应专题里的例子文件来做。Boy.m文件代码如下:
#import "Boy.h"
@implementation Boy
void mytest(int a) {
NSLog(@"%s --- parama = %d",__func__,a);
}
- (void)say {
NSLog(@"%s",__func__);
}
@end
示例工程结构如下图(其中Test.framework即是Boy文件编译的动态库):
2.2 运行时加载动态库
1. 先找到动态库中可执行文件的路径
// 包含动态库的bundle路径
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"dyld" ofType:@"bundle"];
// framework的路径
NSString *frameworkPath = [[NSBundle bundleWithPath:bundlePath] pathForResource:@"Test" ofType:@"framework"];
// 动态库中可执行文件的真实路径
NSString *dyldFilePath = [frameworkPath stringByAppendingString:@"/Test"];
如下图所示,framework中的Test才是动态库的可执行文件
2.打开动态库
// 以指定模式打开指定的动态链接库文件,并返回一个句柄
// 不同的模式的详细介绍见dlopen百度百科
void *handle = dlopen([dyldFilePath UTF8String], RTLD_LAZY);
// handle == null 表示打开动态库失败,dlerror能获取到失败的信息
if (!handle) {
NSLog(@"dlopen error == %s",dlerror());
}
NSLog(@"dlopen = %s",handle);
3.通过符号找到函数并执行
// 定义函数
void(*pMytest)(int);
// 通过"mytest"符号找到其对应的函数
pMytest = dlsym(handle, "mytest");
// pMytest == null 表示没有找到符号对应的地址,dlerror能获取到失败的信息
if (!pMytest) {
NSLog(@"dlsym error == %s",dlerror());
}else {
// 调用函数
pMytest(5);
}
4.通过runtime调用OC方法
// runtime调用oc方法
[self runtimeCallMethod];
// 可以试试把该方法的调用放到dlopen之前,看看有什么区别
- (void)runtimeCallMethod {
Class Boy = NSClassFromString(@"Boy");
id boy = [[Boy alloc] init];
SEL boySaySel = NSSelectorFromString(@"say");
[boy performSelector:boySaySel withObject:nil afterDelay:0];
}
5.控制台打印结果
2019-05-05 22:20:28.939416+0800 05-加载bundle中的动态库[95027:7738314] dlopen = \M-`\M-I=\^O\^A
2019-05-05 22:20:28.939622+0800 05-加载bundle中的动态库[95027:7738314] dlsym = UH\M^I\M-eH\M^C\M-l\^PH\M^M\^E\M-y\^A
2019-05-05 22:20:28.939726+0800 05-加载bundle中的动态库[95027:7738314] mytest --- parama = 5
2019-05-05 22:20:28.956245+0800 05-加载bundle中的动态库[95027:7738314] -[Boy say]
已经能成功调用私有动态库中的C函数和OC方法了。
最后附上完整代码
更多详细内容,请移步至戴铭老师的专栏