一、知識點
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方法了。
最后附上完整代碼
更多詳細內容,請移步至戴銘老師的專欄