靜態(tài)庫

前言:

查看某一個命令作用有兩種方式

// 第一種方式
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)建這樣的文件,如下圖所示

image.png

//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í)行腳本文件

image.png

// 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í)行

先制作一個靜態(tài)framework,如下圖所示
image.png
TestExample.h文件內(nèi)容同上
TestExample 文件是把 TestExample.m 打包.a文件,同時刪除lib前綴與.a后綴

創(chuàng)建腳本文件build.sh
image.png
test.m文件內(nèi)容同上
靜態(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.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容