iOS:靜態庫和dead code strip

1.前沿

.a:常見的靜態庫文件格式
.dylib:傳統意義上的動態庫文件格式
.framework:可以是靜態庫也可以是動態庫。
.xcframework:剛推出,不同架構庫放到一塊。

什么是庫
庫(Library)說白了就是一段編譯好的二進制代碼,加上頭文件就可以供別 人使用。
什么時候會用到庫(Library)?

  1. 某些代碼需要給別人使用,但是我們不希望別人看到源碼,就需要以
    庫的形式進行封裝,只暴露出頭文件。
  2. 對于某些不會進行大的改動的代碼,我們想減少編譯的時間,就可以 把它打包成庫,因為庫是已經編譯好的二進制了,編譯的時候只需 要 Link 一下,不會浪費編譯時間。
    什么是鏈接(Link)?
    庫在使用的時候需要鏈接(Link),
    鏈接 的方式有兩種:
  3. 靜態
  4. 動態
    什么是靜態庫?
    靜態庫即靜態鏈接庫:可以簡單的看成一組目標文件的集合。即很多目 標文件經過壓縮打包后形成的文件。Windows 下的 .lib,Linux 和 Mac 下的 .a。Mac獨有的.framework。
    缺點:
    浪費內存和磁盤空間,模塊更新困難
    什么是動態庫?
    與靜態庫相反,動態庫在編譯時并不會被拷?到目標程序中,目標程序 中只會存儲指向動態庫的引用。等到程序運行時,動態庫才會被真正加 載進來。格式有:.framework、.dylib、.tdb。
    缺點:
    會導致一些性能損失。但是可以優化,比如延遲綁定(Lazy Binding)技術
    什么是tdb格式?
    tbd全稱是text-based stub libraries,本質上就是一個YAML描述的文本文
    件。
    他的作用是用于記錄動態庫的一些信息,包括導出的符號、動態庫的架構信息、動
    態庫的依賴信息
    用于避免在真機開發過程中直接使用傳統的dylib。
    對于真機來說,由于動態庫都是在設備上,在Xcode上使用基于tbd格式的偽 framework可以大大減少Xcode的大小。
    Framework
    Mac OS/iOS 平臺還可以使用 Framework。Framework 實際上是一種打包 方式,將庫的二進制文件,頭文件和有關的資源文件打包到一起,方便管 理和分發。
    Framework 和系統的 UIKit.Framework 還是有很大區別。系統的 Framework 不需要拷?到目標程序中,我們自己做出來的 Framework 哪怕 是動態的,最后也還是要拷?到 App 中(App 和 Extension 的 Bundle 是 共享的),因此蘋果又把這種 Framework 稱為 Embedded Framework
    Embedded Framework
    開發中使用的動態庫會被放入到ipa下的framework目錄下,基于沙盒運行。
    不同的App使用相同的動態庫,并不會只在系統中存在一份。而是會在多 個App中各自打包、簽名、加載一份。

2.鏈接靜態庫生成目標文件

2.1.創建一個靜態庫

創建一個靜態庫,里面有一個oc類ReplaceAFNetWorking,編譯后里面兩個文件,一個是libReplaceAFNetWorking.a一個是頭文件ReplaceAFNetWorking.h
查看.a文件到底是什么

file libReplaceAFNetWorking.a
// libReplaceAFNetWorking.a: current ar archive random library

2.2查看.a文件的內容

我們查看a文件的內容,需要使用ar命令
查看ar命令作用 man ar

  ar -- create and maintain library archives

SYNOPSIS
     ar -d [-TLsv] archive file ...
     ar -m [-TLsv] archive file ...
     ar -m [-abiTLsv] position archive file ...
     ar -p [-TLsv] archive [file ...]
     ar -q [-cTLsv] archive file ...
     ar -r [-cuTLsv] archive file ...
     ar -r [-abciuTLsv] position archive file ...
     ar -t [-TLsv] archive [file ...]
     ar -x [-ouTLsv] archive [file ...]

ar可以修改查看a文件的內容

ar -t libReplaceAFNetWorking.a
__.SYMDEF SORTED
ReplaceAFNetWorking.o

我們創建的庫里面就一個目標文件

2.3創建test文件去調用我們的庫

#import <Foundation/Foundation.h>
#import <ReplaceAFNetWorking.h>

int main(){
    ReplaceAFNetWorking *manager = [ReplaceAFNetWorking new];
    NSLog(@"testApp----%@", manager);
    return 0;
}

2.3把我們的test文件編譯成目標文件(.o文件)

我們用clang編譯我們的m文件成目標文件

man clang 
clang - the Clang C, C++, and Objective-C compiler
SYNOPSIS
       clang [options] filename ...

DESCRIPTION
       clang  is  a C, C++, and Objective-C compiler which encompasses prepro-
       cessing, parsing, optimization, code generation, assembly, and linking.
       Depending  on  which high-level mode setting is passed, Clang will stop
       before doing a full link.  While Clang  is  highly  integrated,  it  is
       important to understand the stages of compilation, to understand how to
       invoke it.  These stages are:

使用如下命令

clang -x objective-c \
-target x86_64-apple-macos11.0.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
-I./ReplaceAFNetWorking \
-c test.m -o test.o

2.4.生成可執行文件

clang -target x86_64-apple-macos11.0.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
-L./ReplaceAFNetWorking \
-lReplaceAFNetWorking \
test.o -o test

clang命令參數:
-x: 指定編譯文件語言類型
-g: 生成調試信息
-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

3靜態庫原理

3.1探究的環境。

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文件的內容
#import <Foundation/Foundation.h>
#import "TestExample.h"

int main(){
    NSLog(@"testApp----");
   return 0;
}

3.2. TestExample文件編譯成目標文件

 clang -x objective-c \
-target x86_64-apple-macos11.0.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
-c TestExample.m -o TestExample.o

3.3修改目標文件為庫文件

把TestExample.o文件修改成libTestExample.a。如果系統比較新可以把o文件改成后綴dylib的文件

3.4編譯test.m文件為目標文件

 clang -x objective-c \
-target x86_64-apple-macos11.0.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
-I./StaticLibrary \
> -c test.m -o test.o

3.5鏈接

clang \
-target x86_64-apple-macos11.0.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
-L./StaticLibrary \
-lTestExample \
test.o -o test

3.6執行

lldb
file test
//Current executable set to '/Users/MacW/Desktop/loginlearn/強化版/強化班-3-靜 
//態庫/上課代碼/靜態庫原理/test' (x86_64).
r
//Process 14790 launched: '/Users/MacW/Desktop/loginlearn/強化版/強化班-3-靜
//態庫/上課代碼/靜態庫原理/test' (x86_64)
//2021-01-21 19:19:24.535476+0800 test[14790:1474339] testApp----
//2021-01-21 19:19:24.535943+0800 test[14790:1474339] TestExample----
//Process 14790 exited with status = 0 (0x00000000)

總結,靜態庫就是目標文件.o文件的合集,Framework的合成和.a差不多,唯一區別就是在鏈接的時候-L和-l換成-F 和-framework

4.靜態庫合并

可以通過ar命令,
ar`壓縮目標文件,并對其進行編號和索引,形成靜態庫。同時也可以解壓縮靜態庫,查看有哪些目標文件:
ar -rc a.a a.o
-r: 像a.a添加or替換文件
-c: 不輸出任何信息
-t: 列出包含的目標文件
ar -rc libTestExample.a TestExample.o
我們一般使用xcode給我提供的libtool

libtool \
 -static \
 -o libmerge.a \
 libSDWebImage.a \
 libAFNetworking.a

5.dead code strip

我們在3的探究庫原理的時候,只引用了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的代碼并沒有
我們在main中做如下修改

int main(){
    NSLog(@"testApp----");
    TestExample *manager = [TestExample new];
    [manager lg_test: nil];
    return 0;
}

編譯連接后查看

100003ef9:  48 8b 35 c8 41 00 00    movq    16840(%rip), %rsi ## Objc selector ref: lg_test:

這樣會出現一個問題,因為我們的分類是在運行時創建的,但是我們dead code是在連接的時候生效的,所以我們的分類就會被strip掉,當我們運行時就會出現實例找不到方法的情況。我們配置other linkers flags
我們在config文件配置如下

OTHER_LDFLAGS=-Xlinker -all_load

-Xlinker -all_load:不dead strip,加載全部代碼
-Xlinker -ObjC:加載全部OC相關代碼,包括分類
-Xlinker -force_load: 要加載那個靜態庫的全部代碼
dead code strip和-all_load的區別
-all_load 指定對靜態庫的鏈接情況,-ObjC 是全部鏈接還是只鏈接oc符號, -force_load是指定鏈接某一個靜態庫。和-dead_strip的作用如下

-dead_strip
                 Remove functions and data that are unreachable by the entry
                 point or exported symbols.

我們在實際應用中,可以如下指定參數

clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot $SYSROOT \
-Xlinker -dead_strip \
-Xlinker -all_load \
-Xlinker -why_live -Xlinker _global_function \
-L./StaticLibrary \
-lTestExample \
${FILE_NAME}.o -o ${FILE_NAME}

-why_live -Xlinker:指某個符號為什么存在

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容