前言:
查看某一個命令作用有兩種方式
// 第一種方式
1. $ man nm
// 第二種方式,可以輸出簡短的命令參數(shù)信息
2. $ nm --help
// -p 表示不排序 -a表示全部符號 這些命令作用都可以在終端查看
// 比如查看 nm 作用,終端輸入 $ man nm 然后輸入 /-p , n向下查找,N向上查找 , q 退出查找
3. $ nm -pa test
庫(Library)就是一段編譯好的二進制代碼,加上頭文件提供給別人使用。
.a:常見的靜態(tài)庫文件格式
.dylib:傳統(tǒng)意義上的動態(tài)庫文件格式
.framework:可以是靜態(tài)庫也可以是動態(tài)庫。
.xcframework:剛推出,不同架構(gòu)庫放到一塊。
一. 鏈接靜態(tài)庫
1.1 file命令查看一個文件具體格式,.a是一個文檔格式
$ file libAFNetworking.a
//libAFNetworking.a: current ar archive
1.2 查看.a文件的內(nèi)容
$ ar -t libAFNetworking.a
__.SYMDEF
AFAutoPurgingImageCache.o
AFHTTPSessionManager.o
AFImageDownloader.o
AFNetworkActivityIndicatorManager.o
AFNetworking-dummy.o
AFNetworkReachabilityManager.o
AFSecurityPolicy.o
AFURLRequestSerialization.o
AFURLResponseSerialization.o
AFURLSessionManager.o
UIActivityIndicatorView+AFNetworking.o
UIButton+AFNetworking.o
UIImageView+AFNetworking.o
UIProgressView+AFNetworking.o
UIRefreshControl+AFNetworking.o
WKWebView+AFNetworking.o
說明.a文件是.o文件的一個合集
1.3 創(chuàng)建test文件去調(diào)用我們的庫
#import <Foundation/Foundation.h>
#import <AFNetworking.h>
int main(){
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSLog(@"testApp----%@", manager);
return 0;
}
接下來我們把test.m文件編譯成目標文件test.o文件,使用如下命令
$ clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-I./AFNetWorking \
-c test.m -o test.o
\ 是一個轉(zhuǎn)義字符,本來敲回車是要執(zhí)行命令,現(xiàn)在加上轉(zhuǎn)義字符\,變成了換行
-target: 指定架構(gòu)平臺,生成的是X86_64_macOS架構(gòu)的代碼
-fobjc-arc: 編譯arc環(huán)境
-isysroot: 指定系統(tǒng)Foundation庫路徑,因為在Mac上編譯,所以指定了MacOSX11.0.sdk,也可以換成手機或者模擬器,指定好對應(yīng)的Foundation路徑即可
-I: 用到的其他庫的頭文件地址在./Frameworks,./表示AFNetWorking與test.m是同一級目錄,如果是多個引用庫,可以寫多個 -I/.AFNetWorking -I/.SDWebImage
1.4 生成可執(zhí)行文件
$ clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-L./AFNetworking \
-lAFNetworking \
test.o -o test
test.o鏈接libTestExample.a生成test可執(zhí)行文件
-L./StaticLibrary 在當前目錄的子目錄StaticLibrary查找需要的庫文件
-lTestExample 鏈接的名稱為libTestExample/TestExample的動態(tài)庫或者靜態(tài)庫
查找規(guī)則:先找lib+<library_name>的動態(tài)庫,找不到,再去找lib+<library_name>的靜態(tài)庫,還找不到,就報錯
小結(jié):
clang命令參數(shù):
-x: 指定編譯文件語言類型
-g: 生成調(diào)試信息
-c: 生成目標文件,只運行preprocess,compile,assemble,不鏈接
-o: 輸出文件
-isysroot: 使用的SDK路徑
鏈接一個庫文件的三要素:
1. -I<directory> 在指定目錄尋找頭文件 header search path
2. -L<dir> 指定庫文件路徑(.a.dylib庫文件) library search path
3. -l<library_name> 指定鏈接的庫文件名稱(.a.dylib庫文件)other link flags -lAFNetworking
-F<directory> 在指定目錄尋找framework framework search path
-framework <framework_name> 指定鏈接的framework名稱 other link flags -framework AFNetworking
二. 靜態(tài)庫原理
2.1 首先創(chuàng)建這樣的文件,如下圖所示

//TestExample的h文件
#import <Foundation/Foundation.h>
@interface TestExample : NSObject
- (void)lg_test:(_Nullable id)e;
@end
//TestExample的m文件
@implementation TestExample
- (void)lg_test:(_Nullable id)e {
NSLog(@"TestExample----");
}
@end
//test文件的內(nèi)容
#import <Foundation/Foundation.h>
#import "TestExample.h"
int main(){
NSLog(@"testApp----");
TestExample *manager = [TestExample new];
[manager lg_test: nil];
return 0;
}
2.2 TestExample文件編譯成目標文件
$ clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-c TestExample.m -o TestExample.o
2.3 修改目標文件為庫文件
把TestExample.o文件修改成libTestExample.a。如果系統(tǒng)比較新可以把o文件改成后綴dylib的文件,改成可執(zhí)行文件之后,可以選擇把.dylib后綴刪除
2.4 編譯test.m文件為目標文件
$ clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-I./StaticLibrary \
-c test.m -o test.o
2.5 鏈接生成可執(zhí)行文件
$ clang \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-L./StaticLibrary \
-lTestExample \
test.o -o test
2.6 使用上述命令在終端一行行敲比較繁瑣,我們可以把上述命令寫入腳本文件build.sh,直接執(zhí)行腳本文件

// build.sh文件內(nèi)容
// 變量定義,注意!!!不能有空格
SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk
FILE_NAME=test
HEADER_SEARCH_PATH=./StaticLibrary
echo "-----開始編譯test.m"
clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot $SYSROOT \
-I${HEADER_SEARCH_PATH} \
-c ${FILE_NAME}.m -o ${FILE_NAME}.o
echo "-----開始進入StaticLibrary"
// Mac文件目錄也類似于入棧,出棧,使用cd進入文件夾會修改系統(tǒng)默認文件目錄,所以這里使用pushd
pushd ./StaticLibrary
clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot $SYSROOT \
-c TestExample.m -o TestExample.o
ar -rc libTestExample.a TestExample.o
echo "-----開始退出StaticLibrary"
popd
echo "-----開始test.o to test EXEC"
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot $SYSROOT \
-L./StaticLibrary \
-lTestExample \
${FILE_NAME}.o -o ${FILE_NAME}
//執(zhí)行腳本,./build.sh 后面也可以指定一些參數(shù)
$ ./build.sh
//報無權(quán)限zsh: permission denied: ./build.sh
// 通過下面命令授權(quán)
$ chmod +x ./build.sh
//再次執(zhí)行腳本
$ ./build.sh
2.7 執(zhí)行
$ lldb
file test
Current executable set to '/Users/wangning/Documents/資料/1:20/靜態(tài)庫/上課代碼/靜態(tài)庫原理/test' (x86_64).
r
Process 21101 launched: '/Users/wangning/Documents/資料/1:20/靜態(tài)庫/上課代碼/靜態(tài)庫原理/test' (x86_64)
2021-02-09 21:51:29.118705+0800 test[21101:2114310] testApp----
2021-02-09 21:51:29.119020+0800 test[21101:2114310] TestExample----
Process 21101 exited with status = 0 (0x00000000)
// q命令退出終端
q
小結(jié):
shell是一個解釋型語言,一行一行解釋執(zhí)行

TestExample 文件是把 TestExample.m 打包.a文件,同時刪除lib前綴與.a后綴
創(chuàng)建腳本文件build.sh

靜態(tài)庫就是目標文件.o文件的合集,Framework的合成和.a差不多,唯一區(qū)別就是在鏈接的時候-L和-l換成-F 和-framework
三. 靜態(tài)庫合并
ar壓縮目標文件,并對其進行編號和索引,形成靜態(tài)庫。同時也可以解壓縮靜態(tài)庫,查看有哪些目標文件:
ar -rc a.a a.o
-r: 向a.a添加or替換文件
-c: 不輸出任何信息
-t: 列出包含的目標文件
ar -rc libTestExample.a TestExample.o
我們一般使用xcode給我提供的libtool
$ libtool \
-static \
-o libCat.a \
libSDWebImage.a \
libAFNetworking.a
靜態(tài)庫合并最要考慮的點:libSDWebImage.a與libAFNetworking.a中.o文件合并之后,頭文件.h要怎么處理?
四. module文件
4.1 下面代碼中TestExample導入之后,雖然沒有使用,但是TestExample文件會進行編譯,如果多個類都倒入,就會編譯多次。這個時候Clang 提供了module,module可以提前把.h文件編譯成二進制緩存到系統(tǒng)目錄中,這樣就解決了多次編譯的問題。
#import <Foundation/Foundation.h>
#import "TestExample.h"
int main(){
NSLog(@"testApp----");
return 0;
}
4.2 Auto-Link
鏈接器的特性,Auto-Link。啟用這個特性后,當我們import <模塊>,不需要我們再去往鏈接器去配置鏈接參數(shù)。比如import <framework>我們在代碼里使用這個是framework格式的庫文件,那么在生成目標文件時,會自動在目標文件的Mach-O中,插入一個 load command格式是LC_LINKER_OPTION,存儲這樣一個鏈接器參數(shù)-framework <framework>。
4.3 Framework
Mac OS/iOS 平臺還可以使用 Framework。Framework 實際上是一種打包 方式,將庫的二進制文件,頭文件和有關(guān)的資源文件打包到一起,方便管 理和分發(fā)。
Framework 和系統(tǒng)的 UIKit.Framework 還是有很大區(qū)別。系統(tǒng)的 Framework 不需要拷?到目標程序中,我們自己做出來的 Framework 哪怕 是動態(tài)的,最后也還是要拷?到 App 中(App 和 Extension 的 Bundle 是 共享的),因此蘋果又把這種 Framework 稱為 Embedded Framework。
五. dead code strip
我們把test.m文件修改如下
#import <Foundation/Foundation.h>
#import "TestExample.h"
int main(){
NSLog(@"testApp----");
// TestExample *manager = [TestExample new];
// [manager lg_test: nil];
return 0;
}
現(xiàn)在只引用了TestExample頭文件,但是沒有對TestExample使用,那TestExample代碼是否被鏈接到我們的test文件中的呢?
$ objdump --macho -d test
test:
(__TEXT,__text) section
_main:
100003f60: 55 pushq %rbp
100003f61: 48 89 e5 movq %rsp, %rbp
100003f64: 48 83 ec 10 subq $16, %rsp
100003f68: 48 8d 05 99 00 00 00 leaq 153(%rip), %rax ## Objc cfstring ref: @"testApp----"
100003f6f: c7 45 fc 00 00 00 00 movl $0, -4(%rbp)
100003f76: 48 89 c7 movq %rax, %rdi
100003f79: b0 00 movb $0, %al
100003f7b: e8 08 00 00 00 callq 0x100003f88 ## symbol stub for: _NSLog
100003f80: 31 c0 xorl %eax, %eax
100003f82: 48 83 c4 10 addq $16, %rsp
100003f86: 5d popq %rbp
100003f87: c3 retq
從命令中看出TestExample的代碼并沒有
現(xiàn)在把test.m文件還原如下
#import <Foundation/Foundation.h>
#import "TestExample.h"
int main(){
NSLog(@"testApp----");
TestExample *manager = [TestExample new];
[manager lg_test: nil];
return 0;
}
執(zhí)行上述腳本以及$ objdump --macho -d test命令查看
ref: TestExample
100003ee7: 48 89 cf movq %rcx, %rdi
100003eea: e8 43 00 00 00 callq 0x100003f32 ## symbol stub for: _objc_opt_new
100003eef: 31 d2 xorl %edx, %edx
100003ef1: 48 89 45 f0 movq %rax, -16(%rbp)
100003ef5: 48 8b 45 f0 movq -16(%rbp), %rax
100003ef9: 48 8b 35 c8 41 00 00 movq 16840(%rip), %rsi ## Objc selector ref: lg_test:
這樣會出現(xiàn)一個問題,因為我們的類別是在運行時動態(tài)創(chuàng)建的,但是我們dead code是在連接的時候生效的,所以我們的分類就會被strip掉,當我們運行時就會出現(xiàn)實例找不到方法的情況。我們配置other linkers flags,我們可以在config文件配置如下
OTHER_LDFLAGS=-Xlinker -all_load
-Xlinker -all_load:不dead strip,加載全部代碼
-Xlinker -ObjC:加載全部OC相關(guān)代碼,包括類別
-Xlinker -force_load: 要加載那個靜態(tài)庫的全部代碼
dead code strip和-all_load的區(qū)別
-all_load 指定對靜態(tài)庫的鏈接情況,-ObjC 是全部鏈接還是只鏈接oc符號, -force_load是指定鏈接某一個靜態(tài)庫。和-dead_strip的作用如下
-dead_strip
Remove functions and data that are unreachable by the entry
point or exported symbols.