iOS高級強(qiáng)化--008:靜態(tài)庫

什么是庫?

庫(Library):就是?段編譯好的?進(jìn)制代碼,加上頭?件就可以供別?使?。

常?庫?件格式:.a.dylib、.framework、.xcframework、.tdb

什么時(shí)候會(huì)?到庫?
  • 某些代碼需要給別?使?,但是我們不希望別?看到源碼,就需要以庫的形式進(jìn)?封裝,只暴露出頭?件
  • 對于某些不會(huì)進(jìn)??改動(dòng)的代碼,我們想減少編譯的時(shí)間,就可以把它打包成庫。因?yàn)閹焓且呀?jīng)編譯好的?進(jìn)制,編譯的時(shí)候只需要Link?下,不會(huì)浪費(fèi)編譯時(shí)間
什么是鏈接?

庫(Library)在使?的時(shí)候需要鏈接(Link

鏈接的?式有兩種:

  • 靜態(tài)
  • 動(dòng)態(tài)
什么是靜態(tài)庫?

靜態(tài)庫即靜態(tài)鏈接庫:可以簡單的看成?組?標(biāo)?件的集合。即很多?標(biāo)?件經(jīng)過壓縮打包后形成的?件。Windows下的.lib,LinuxMac下的 .a。Mac獨(dú)有的.framework

缺點(diǎn):浪費(fèi)內(nèi)存和磁盤空間,模塊更新困難

鏈接靜態(tài)庫
生成目標(biāo)文件

目錄中包含一個(gè)test.m文件和AFNetworking三方庫

打開test.m文件,寫入以下代碼:

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

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

AFNetworking 為靜態(tài)庫,打開AFNetworking目錄,里面包含了頭文件.a文件

打開終端,進(jìn)入指定目錄,使用file libAFNetworking.a命令,查看.a的文件格式

libAFNetworking.a: current ar archive
  • 從打印結(jié)果來看,.a文件是一個(gè)文檔格式

使用ar -t libAFNetworking.a命令,查看.a文件

  • .a中包含了AFNetworking編譯出來的所有目標(biāo)文件,驗(yàn)證了靜態(tài)庫其實(shí)就是.o文件的合集

使用man clang查看clang命令

  • clangC、C++Objective-C的編譯器。它也是一個(gè)工具的合集,包含了預(yù)處理、解析、優(yōu)化、代碼生成、匯編化、鏈接

使用clang命令,將.m文件編譯成.o文件

clang -x objective-c \
-target x86_64-apple-ios14-simulator \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.3.sdk \
-I ./AFNetworking \
-c test.m -o test.o
  • -x:指定編譯文件語言類型
  • -target:指定生成架構(gòu)
  • -fobjc-arc:使用ARC
  • -isysroot:使用SDK的路徑
  • -I:指定頭文件路徑Header Search Paths
  • -c:生成目標(biāo)文件
  • -o:輸出文件

此時(shí)目錄中生成了.o目標(biāo)文件

  • 目標(biāo)文件中包含重定位符號表,它保存了文件中使用的所有符號,鏈接時(shí)會(huì)根據(jù)重定位符號表生成具體的符號信息
  • 所以在生成目標(biāo)文件時(shí),只需靜態(tài)庫的頭文件即可。重定位符號表只需要記錄哪個(gè)地方的符號需要重定位,然后在鏈接過程中會(huì)自動(dòng)將符號重定位
生成可執(zhí)行文件

使用clang命令,將.o文件鏈接成可執(zhí)行文件

clang -target x86_64-apple-ios14-simulator \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.3.sdk \
-L ./AFNetworking \
-lAFNetworking \
test.o -o test
  • -L:指定庫文件路徑(.a\.dylib庫文件)Library Search Paths
  • -l:指定鏈接的庫文件名稱(.a\.dylib庫文件)Other Linker Flags -lAFNetworking

此時(shí)目錄中生成了test可執(zhí)行文件

  • 鏈接成可執(zhí)行文件,會(huì)將重定位符號表中的符號進(jìn)行重定位,此時(shí)需要知道符號的真實(shí)地址,而真實(shí)地址保存在靜態(tài)庫的.o文件中,需要指定庫文件路徑和將要鏈接的庫文件名稱
  • 庫文件名稱的查找規(guī)則:先找lib+<library_name>的動(dòng)態(tài)庫,找不到,再去找lib+<library_name>的靜態(tài)庫,還找不到,就報(bào)錯(cuò)

靜態(tài)庫鏈接成功的三要素:

  • -I:指定頭文件路徑Header Search Paths
  • -L:指定庫文件路徑(.a\.dylib庫文件)Library Search Paths
  • -l:指定鏈接的庫文件名稱(.a\.dylib庫文件)Other Linker Flags -lAFNetworking
鏈接靜態(tài)庫的原理

靜態(tài)庫是.o文件的合集,下面我們證明這一點(diǎn):

生成可執(zhí)行文件

項(xiàng)目中包含test.m文件和一個(gè)StaticLibrary子目錄,StaticLibrary目錄下包含TestExample.h文件和TestExample.m文件

打開TestExample.h文件,寫入以下代碼:

#import <Foundation/Foundation.h>

@interface TestExample : NSObject

- (void)lg_test:(_Nullable id)e;

@end

打開TestExample.m文件,寫入以下代碼:

#import "TestExample.h"

@implementation TestExample

- (void)lg_test:(_Nullable id)e {
   NSLog(@"TestExample----");
}

@end

使用clang命令,將TestExample.m文件編譯成.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 \
-c TestExample.m -o TestExample.o

此時(shí)目錄中生成了TestExample.o目標(biāo)文件

如果只有一個(gè).o文件,那它自身就相當(dāng)于這個(gè)合集。將TestExample.o文件重命名為libTestExample.dylib(改為.a格式也可以)

使用file libTestExample.dylib命令,查看libTestExample.dylib的文件格式

libTestExample.dylib: Mach-O 64-bit object x86_64
  • 此時(shí)libTestExample.dylib依然是目標(biāo)文件

打開test.m文件,寫入以下代碼:

#import <Foundation/Foundation.h>
#import "TestExample.h"

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

使用clang命令,將test.m文件編譯成.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 ./StaticLibrary \
-c test.m -o test.o
  • test.m文件使用了TestExample中的代碼,所以要使用-I參數(shù),指定頭文件路徑

此時(shí)目錄中生成了test.o目標(biāo)文件

使用clang命令,將test.o文件鏈接成可執(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

此時(shí)目錄中生成了test可執(zhí)行文件

運(yùn)行可執(zhí)行文件

使用lldb命令,在終端中進(jìn)入lldb環(huán)境

使用file test命令,將test可執(zhí)行文件包裝成一個(gè)target

Current executable set to '/Users/zang/Zang/Spark/test' (x86_64).

使用r命令,開始運(yùn)行

Process 96467 launched: '/Users/zang/Zang/Spark/test' (x86_64)
2021-02-26 18:10:47.713761+0800 test[96467:6978980] testApp----
2021-02-26 18:10:47.713991+0800 test[96467:6978980] TestExample----
Process 96467 exited with status = 0 (0x00000000)
  • 執(zhí)行成功,打印的內(nèi)容正是test.mTestExample.m輸出的內(nèi)容

使用q,退出lldb環(huán)境

使用objdump --macho --private-header libTestExample.dylib命令,查看libTestExample.dylib文件的Mach Header信息

Mach header
     magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64  X86_64        ALL  0x00      OBJECT     4       1160 SUBSECTIONS_VIA_SYMBOLS
  • 通過filetype可以看出,libTestExample.dylib依然是一個(gè)目標(biāo)文件

由此證明:靜態(tài)庫就是.o文件的合集

靜態(tài)庫的合并

靜態(tài)庫合并的兩種方式:

  • 使用ar命令
  • 使用Xcode提供的libtool命令
ar命令

使用man ar,查看ar命令

  • 可以查看靜態(tài)庫
  • 也能將多個(gè).o文件合并成靜態(tài)庫
  • 可以將靜態(tài)庫中包含的.o文件全部解壓出來

目錄下,分別是libAFNetworking.alibSDWebImage.a兩個(gè)靜態(tài)庫

使用ar x libAFNetworking.a命令,解壓libAFNetworking.a靜態(tài)庫

使用ar x libSDWebImage.a命令,解壓libSDWebImage.a靜態(tài)庫

使用ar r lib_AF_SD.a *.o命令,將目錄下的所有.o文件,合并成一個(gè).a文件

使用objdump --macho --rebase lib_AF_SD.a命令,查看lib_AF_SD.a的重定位符號表

  • 此時(shí)lib_AF_SD.a文件中,包含了libAFNetworking.alibSDWebImage.a的所有.o文件,相當(dāng)于將兩個(gè)靜態(tài)庫進(jìn)行合并
libtool

使用man libtool,查看libtool命令

  • 可以創(chuàng)建庫文件
  • 可以添加或更新一系列的靜態(tài)庫文件

使用libtool命令,合并libAFNetworking.alibSDWebImage.a兩個(gè)靜態(tài)庫

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

目錄下,成功合并出libCat.a靜態(tài)庫

使用objdump --macho --rebase libCat.a命令,查看libCat.a的重定位符號表

  • 此時(shí)libCat.a文件中,包含了libAFNetworking.alibSDWebImage.a的所有.o文件,相當(dāng)于將兩個(gè)靜態(tài)庫進(jìn)行合并
mudule

muduleclang提供的解析.h頭文件的一種解析格式

mudule可以把.h頭文件預(yù)先編譯成二進(jìn)制,存儲(chǔ)到系統(tǒng)緩存目錄中。好處:當(dāng)編譯.m文件時(shí),避免反復(fù)編譯.h文件

例如:當(dāng)一個(gè).h文件在很多.m中被import,當(dāng)這些.m文件被編譯時(shí),不需要一遍又一遍的重復(fù)編譯.h文件,它會(huì)直接使用mudule預(yù)先編譯好的二進(jìn)制

Auto-Link

Auto-LinkLC_LINKER_OPTION鏈接器的特性。啟用該特性后,在import <模塊>時(shí)不需要再往鏈接器去配置鏈接參數(shù)

例如:import <Framework>,代碼中使用這個(gè)Framework格式的庫文件,在生成目標(biāo)文件時(shí),會(huì)自動(dòng)在目標(biāo)文件的Mach-O中,插入一個(gè)load command,格式是LC_LINKER_OPTION,存儲(chǔ)鏈接器參數(shù)-framework <Framework>

Framework

Mac OS/iOS平臺還可以使?Framework

Framework實(shí)際上是?種打包?式,將庫的?進(jìn)制?件,頭?件和有關(guān)的資源?件打包到?起,?便管理和分發(fā)

Framework和系統(tǒng)的UIKit.Framework還是有很?區(qū)別。系統(tǒng)的Framework不需要拷?到?標(biāo)程序中,而自定義的Framework哪怕是動(dòng)態(tài)的,最后也要拷?到App中(AppExtensionBundle是共享的),因此蘋果?把這種Framework稱為Embedded Framework

Framework可以是靜態(tài)庫,也可以是動(dòng)態(tài)庫

  • 無論是靜態(tài)庫還是動(dòng)態(tài)庫,Framework都包含了頭文件、庫的原始文件、簽名和資源文件
  • Framework是靜態(tài)庫還是動(dòng)態(tài)庫,取決于庫的原始文件
Embedded Framework

開發(fā)中使?的動(dòng)態(tài)庫會(huì)被放?到ipa下的framework?錄中,基于沙盒運(yùn)?

不同的App使?相同的動(dòng)態(tài)庫,并不會(huì)只在系統(tǒng)中存在?份。?是會(huì)在多個(gè)App中各?打包、簽名、加載一份

App Framework存放位置

手動(dòng)創(chuàng)建Framework

創(chuàng)建Frameworks目錄,目錄中包含TestExample.h文件和TestExample.m文件

打開TestExample.h文件,寫入以下代碼:

#import <Foundation/Foundation.h>

@interface TestExample : NSObject

- (void)lg_test:(_Nullable id)e;

@end

打開TestExample.m文件,寫入以下代碼:

#import "TestExample.h"

@implementation TestExample

- (void)lg_test:(_Nullable id)e {
   NSLog(@"TestExample----");
}

@end

使用clang命令,將TestExample.m文件編譯成.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 \
-c TestExample.m -o TestExample.o

此時(shí)目錄中生成了TestExample.o目標(biāo)文件

使用ar -rc libTestExample.a TestExample.o命令,生成libTestExample.a文件

Frameworks目錄下,手動(dòng)創(chuàng)建TestExample.framework靜態(tài)庫

  • Frameworks目錄下,創(chuàng)建TestExample.framework目錄
  • TestExample.framework目錄下,創(chuàng)建Headers目錄
  • TestExample.h頭文件移動(dòng)到TestExample.framework/Headers目錄下
  • libTestExample.a庫文件移動(dòng)到TestExample.framework目錄下,和Headers目錄平級
  • 重命名libTestExample.a庫文件,去掉lib開頭,去掉.a后綴名

創(chuàng)建test.m文件,和Frameworks目錄平級

打開test.m文件,寫入以下代碼:

#import <Foundation/Foundation.h>
#import "TestExample.h"

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

使用clang命令,將test.m文件編譯成.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 ./Frameworks/TestExample.frameworks/Headers \
-c test.m -o test.o

此時(shí)目錄中生成了test.o目標(biāo)文件

使用clang命令,將test.o文件鏈接成可執(zhí)行文件

clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-F ./Frameworks \
-framework TestExample \
test.o -o test
  • -F:指定Framework所在目錄Framework Search Paths
  • -framework:指定鏈接Framework的名稱Other Linker Flags -framework AFNetworking

使用lldb命令,進(jìn)入lldb終端。使用file test命令,將test可執(zhí)行文件包裝成一個(gè)target

Current executable set to '/Users/zang/Zang/Spark/Framework/test' (x86_64).

使用r命令,開始運(yùn)行

Process 6365 launched: '/Users/zang/Zang/Spark/Framework/test' (x86_64)
2021-03-02 15:44:56.562585+0800 test[6365:7259836] testApp----
2021-03-02 15:44:56.562824+0800 test[6365:7259836] TestExample----
Process 6365 exited with status = 0 (0x00000000)
  • 執(zhí)行成功,打印的內(nèi)容正是test.mTestExample.m輸出的內(nèi)容

Framework鏈接成功的三要素:

  • -I:指定頭文件路徑Header Search Paths
  • -F:指定Framework所在目錄Framework Search Paths
  • -framework:指定鏈接Framework的名稱Other Linker Flags -framework AFNetworking

Shell腳本的使用

Shell是一門解釋型的編程語言(腳本語言),它的解釋器就Shell這個(gè)程序。作為解釋型語言,Shell語言具有明顯的“膠水語言”的特性,并且這種特性由于其直接運(yùn)行在Shell中而被放大;通過Shell編程可以極大地緩解“重復(fù)的人機(jī)交互命令”給使用者帶來的疲勞,實(shí)現(xiàn)辦公自動(dòng)化。

使用Shell腳本,將.o文件鏈接成可執(zhí)行文件,實(shí)現(xiàn)自動(dòng)化

創(chuàng)建build.sh文件,和test.m文件平級

按以下步驟寫入代碼:

【步驟一】:使用clang命令,將test.m文件編譯成.o文件

echo "-------------編譯test.m to 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 ./StaticLibrary \
-c test.m -o test.o
  • echo命令,用于字符串輸出,一般起到提示作用

【步驟二】:進(jìn)入StaticLibrary目錄

echo "-------------進(jìn)入到StaticLibrary目錄------------------"
pushd ./StaticLibrary
  • 使用cd命令,也可以進(jìn)入一個(gè)目錄,但這里不推薦使用,因?yàn)?code>cd會(huì)直接修改目錄棧里最上層的元素,修改后無法返回
  • 推薦使用pushd命令,pushd是往目錄棧中重新push一個(gè)目錄,修改后可以使用popd命令返回

【步驟三】:使用clang命令,將TestExample.m文件編譯成.o文件

echo "-------------編譯TestExample.m to TestExample.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 \
-c TestExample.m -o TestExample.o

【步驟四】:使用ar命令,生成libTestExample.a文件

echo "-------------TestExample.o to libTestExample.a------------------"
ar -rc libTestExample.a TestExample.o

【步驟五】:退出StaticLibrary目錄

echo "-------------退出StaticLibrary目錄------------------"
popd

【步驟六】:使用clang命令,將test.o文件鏈接成可執(zhí)行文件

echo "-------------將test.o鏈接成可執(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
  • 此時(shí)build.sh的腳本代碼全部完成

打開終端,使用chmod命令,為build.sh文件增加可執(zhí)行權(quán)限

chmod +x ./build.sh

使用ls -all命令,查看文件權(quán)限

drwxr-xr-x@  5 zang  staff   160  3  2 16:56 StaticLibrary
-rwxr-xr-x@  1 zang  staff  1126  3  2 16:50 build.sh
-rw-r--r--@  1 zang  staff   196  1 20 12:07 test.m

使用./build.sh命令,執(zhí)行Shell腳本

-------------編譯test.m to test.o------------------
-------------進(jìn)入到StaticLibrary目錄------------------
~/Zang/Spark/Shell/StaticLibrary ~/Zang/Spark/Shell
-------------編譯TestExample.m to TestExample.o------------------
-------------TestExample.o to libTestExample.a------------------
-------------退出StaticLibrary目錄------------------
~/Zang/Spark/Shell
-------------將test.o鏈接成可執(zhí)行文件------------------

執(zhí)行成功,目錄下自動(dòng)生成test可執(zhí)行文件

優(yōu)化Shell腳本,將腳本內(nèi)多次出現(xiàn)的參數(shù)提取成變量

LANGUAGE=objective-c
TAREGT=x86_64-apple-macos11.1
SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk

FILE_NAME=test
STATICLIBRARY=TestExample
HEAD_PATH=./StaticLibrary
LIBRARY_PATH=./StaticLibrary

echo "-------------編譯test.m to test.o------------------"
clang -x $LANGUAGE  \
-target $TAREGT     \
-fobjc-arc          \
-isysroot $SYSROOT  \
-I${HEAD_PATH}   \
-c ${FILE_NAME}.m -o ${FILE_NAME}.o

echo "-------------進(jìn)入到StaticLibrary目錄------------------"
pushd ${HEAD_PATH}

echo "-------------編譯TestExample.m to TestExample.o------------------"
clang -x $LANGUAGE  \
-target $TAREGT     \
-fobjc-arc          \
-isysroot $SYSROOT  \
-c ${STATICLIBRARY}.m -o ${STATICLIBRARY}.o

echo "-------------TestExample.o to libTestExample.a------------------"
ar -rc lib${STATICLIBRARY}.a ${STATICLIBRARY}.o

echo "-------------退出StaticLibrary目錄------------------"
popd

echo "-------------將test.o鏈接成可執(zhí)行文件------------------"
clang -target $TAREGT   \
-fobjc-arc              \
-isysroot $SYSROOT      \
-L${LIBRARY_PATH}       \
-l${STATICLIBRARY}      \
${FILE_NAME}.o -o $FILE_NAME
  • 使用變量的方式,可以通過$${}兩種方式
  • $:當(dāng)沒有其他內(nèi)容的拼接,可以直接使用$XXX
  • ${}:變量前后有其他內(nèi)容的拼接,例如:${STATICLIBRARY}.m,需要使用${XXX}
-noall_load

鏈接庫文件的過程中,默認(rèn)設(shè)置為-noall_load,符合剝離條件的代碼全部剝離

打開test.m文件,只導(dǎo)入TestExample.h頭文件,不使用TestExample.m的任何代碼

#import <Foundation/Foundation.h>
#import "TestExample.h"

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

使用./build.sh命令,鏈接成可執(zhí)行文件

使用objdump --macho -d test命令,查看__TEXT代碼段信息

(__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
  • 只有一個(gè)main函數(shù)
  • 說明clang在鏈接過程中,默認(rèn)就是-noall_load

打開test.m文件,使用TestExample.mlg_test方法

#import <Foundation/Foundation.h>
#import "TestExample.h"

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

使用./build.sh命令,鏈接成可執(zhí)行文件

使用objdump --macho -d test命令,查看__TEXT代碼段信息

(__TEXT,__text) section
-[TestExample lg_test:]:
100003e70:  55  pushq   %rbp
100003e71:  48 89 e5    movq    %rsp, %rbp
100003e74:  48 83 ec 20 subq    $32, %rsp
100003e78:  48 89 7d f8 movq    %rdi, -8(%rbp)
100003e7c:  48 89 75 f0 movq    %rsi, -16(%rbp)
100003e80:  48 c7 45 e8 00 00 00 00 movq    $0, -24(%rbp)
100003e88:  48 8d 7d e8 leaq    -24(%rbp), %rdi
100003e8c:  48 89 d6    movq    %rdx, %rsi
100003e8f:  e8 a4 00 00 00  callq   0x100003f38 ## symbol stub for: _objc_storeStrong
100003e94:  48 8d 05 75 01 00 00    leaq    373(%rip), %rax ## Objc cfstring ref: @"TestExample----"
100003e9b:  48 89 c7    movq    %rax, %rdi
100003e9e:  b0 00   movb    $0, %al
100003ea0:  e8 87 00 00 00  callq   0x100003f2c ## symbol stub for: _NSLog
100003ea5:  31 c9   xorl    %ecx, %ecx
100003ea7:  89 ce   movl    %ecx, %esi
100003ea9:  48 8d 7d e8 leaq    -24(%rbp), %rdi
100003ead:  e8 86 00 00 00  callq   0x100003f38 ## symbol stub for: _objc_storeStrong
100003eb2:  48 83 c4 20 addq    $32, %rsp
100003eb6:  5d  popq    %rbp
100003eb7:  c3  retq
100003eb8:  90  nop
100003eb9:  90  nop
100003eba:  90  nop
100003ebb:  90  nop
100003ebc:  90  nop
100003ebd:  90  nop
100003ebe:  90  nop
100003ebf:  90  nop
_main:
100003ec0:  55  pushq   %rbp
100003ec1:  48 89 e5    movq    %rsp, %rbp
100003ec4:  48 83 ec 10 subq    $16, %rsp
100003ec8:  48 8d 05 61 01 00 00    leaq    353(%rip), %rax ## Objc cfstring ref: @"testApp----"
100003ecf:  c7 45 fc 00 00 00 00    movl    $0, -4(%rbp)
100003ed6:  48 89 c7    movq    %rax, %rdi
100003ed9:  b0 00   movb    $0, %al
100003edb:  e8 4c 00 00 00  callq   0x100003f2c ## symbol stub for: _NSLog
100003ee0:  48 8b 0d e9 41 00 00    movq    16873(%rip), %rcx ## Objc class 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:
100003f00:  48 89 c7    movq    %rax, %rdi
100003f03:  ff 15 f7 00 00 00   callq   *247(%rip) ## Objc message: +[TestExample lg_test:]
100003f09:  45 31 c0    xorl    %r8d, %r8d
100003f0c:  44 89 c6    movl    %r8d, %esi
100003f0f:  c7 45 fc 00 00 00 00    movl    $0, -4(%rbp)
100003f16:  48 8d 45 f0 leaq    -16(%rbp), %rax
100003f1a:  48 89 c7    movq    %rax, %rdi
100003f1d:  e8 16 00 00 00  callq   0x100003f38 ## symbol stub for: _objc_storeStrong
100003f22:  8b 45 fc    movl    -4(%rbp), %eax
100003f25:  48 83 c4 10 addq    $16, %rsp
100003f29:  5d  popq    %rbp
100003f2a:  c3  retq
  • 除了之前的main函數(shù),還多了lg_test方法
  • 說明在鏈接過程中,將多個(gè).o文件中的代碼和符號放到一起,最終鏈接出一個(gè)可執(zhí)行文件

使用-noall_load的問題

OC中,分類是在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的,但-noall_load參數(shù)在鏈接時(shí)已經(jīng)生效。在鏈接時(shí)發(fā)現(xiàn)分類的代碼沒有被使用,就會(huì)將其剝離

搭建LGStaticFramework靜態(tài)庫

打開LGOneObject+Category.h文件,寫入以下代碼:

#import <LGOneObject.h>

@interface LGOneObject (Category)

- (void)lg_test_category;

@end

打開LGOneObject+Category.m文件,寫入以下代碼:

#import "LGOneObject+Category.h"

@implementation LGOneObject (Category)

- (void)lg_test_category {
   NSLog(@"lg_test_category");
}

@end

打開LGOneObject.h文件,寫入以下代碼:

#import <Foundation/Foundation.h>

@interface LGOneObject : NSObject

- (void)lg_test;

@end

打開LGOneObject.m文件,寫入以下代碼:

#import "LGOneObject.h"
#import <LGOneObject+Category.h>

@implementation LGOneObject

- (void)lg_test {
   [self lg_test_category];
}

@end

打開LGStaticFramework.h文件,寫入以下代碼:

#import <Foundation/Foundation.h>
#import <LGStaticFramework/LGOneObject.h>

導(dǎo)入LGApp項(xiàng)目,通過LGApp鏈接LGStaticFramework靜態(tài)庫

使用workspace搭建多項(xiàng)目合集

  • 可重?性。多個(gè)模塊可以在多個(gè)項(xiàng)?中使?。節(jié)約開發(fā)和維護(hù)時(shí)間
  • 節(jié)省測試時(shí)間。單獨(dú)模塊意味著每個(gè)模塊中都可以添加測試功能
  • 更好的理解模塊化思想

選擇File->Save As Workspace...

在項(xiàng)目根目錄下創(chuàng)建TestDeadStrip.xcworkspace

關(guān)閉Xcode,來到項(xiàng)目根目錄,使用TestDeadStrip.xcworkspace打開項(xiàng)目

點(diǎn)擊左下角+,選擇Add Files to “TestDeadStrip”...

  • 注意:上面一定不要選擇任何文件,先叉掉所有文件,再點(diǎn)+

找到一個(gè)已有項(xiàng)目,選擇LGApp.xcodeproj,點(diǎn)擊Add

LGApp項(xiàng)目,加入到TestDeadStrip.xcworkspace

將靜態(tài)庫添加到LGApp項(xiàng)目

選擇LGStaticFramework.framework

添加成功后,當(dāng)編譯LGApp項(xiàng)目時(shí),靜態(tài)庫會(huì)一起編譯

將靜態(tài)庫的Embed選擇為Do Not Embed

  • Do Not Embed:不會(huì)將Framework拷貝到IPA包里。用于靜態(tài)庫
    項(xiàng)目使用的Framework是靜態(tài)庫,鏈接時(shí)靜態(tài)庫的代碼和符號會(huì)跟App進(jìn)行合并,故此不需要將Framework拷貝到IPA包里
  • Embed & Sign:將Framework拷貝到IPA包里,并進(jìn)行簽名操作。用于動(dòng)態(tài)庫
  • Embed Without Signing:如果Framework已有正確簽名,可以使用此項(xiàng)

LGApp項(xiàng)目中,打開ViewController.m文件,寫入以下代碼:

#import "ViewController.h"
#import <LGStaticFramework/LGOneObject.h>

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   LGOneObject *obj = [LGOneObject new];
   [obj lg_test];
}

@end

運(yùn)行項(xiàng)目后,程序直接崩潰

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[LGOneObject lg_test_category]: unrecognized selector sent to instance 0x6000002b4240'
  • 崩潰的原因就是在于-noall_load,因?yàn)榉诸愂窃谶\(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的,但-noall_load參數(shù)導(dǎo)致在鏈接時(shí),已經(jīng)將靜態(tài)庫的分類代碼剝離

解決此問題,需要借助鏈接器的參數(shù)配置

打開LGApp項(xiàng)目,創(chuàng)建xcconfig,寫入以下代碼:

LGSTATIC_FRAMEWORK_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/LGStaticFramework.framework/LGStaticFramework
OTHER_LDFLAGS=-Xlinker -force_load ${LGSTATIC_FRAMEWORK_PATH}
  • -noall_load:默認(rèn)值,符合剝離條件的代碼全部剝離
  • -all_load:不要?jiǎng)冸x任何代碼
  • -ObjCOC代碼不要?jiǎng)冸x
  • -force_load:指定某個(gè)靜態(tài)庫不要?jiǎng)冸x代碼,參數(shù)后面需要拼接靜態(tài)庫路徑

xcconfig配置到Tatget

再次運(yùn)行項(xiàng)目,分類方法被成功調(diào)用,打印lg_test_category

-noall_load和Dead Code Stripping區(qū)別

-noall_loadXcodeDead Code Stripping配置項(xiàng),完全不是一個(gè)東西

-noall_load

  • 鏈接庫文件時(shí),控制死代碼剝離的參數(shù)之一,符合剝離條件的代碼全部剝離
  • ld中有-noall_load-all_load、-ObjC、-force_load四種參數(shù)的配置

Dead Code Stripping

  • 在鏈接過程中,鏈接器提供的一種代碼優(yōu)化方式
  • ld中指定-dead_strip參數(shù),決定此功能是否啟用。啟用后,沒有被入口點(diǎn)或?qū)С龇柺褂玫暮瘮?shù)和數(shù)據(jù)將被刪除

打開test.m文件,寫入以下代碼:

#import <Foundation/Foundation.h>
#import "TestExample.h"

void global_function() {
}

int main(){
//    global_function();
   NSLog(@"testApp----");
//    TestExample *manager = [TestExample new];
//    [manager lg_test: nil];
   return 0;
}
  • TestExample是靜態(tài)庫,在test.m文件中只導(dǎo)入了.h頭文件,沒有使用里面的代碼
  • global_function是一個(gè)全局函數(shù),但并沒有被調(diào)用

打開build.sh腳本,最后一步鏈接成可執(zhí)行文件,增加-dead_strip參數(shù)

echo "-------------將test.o鏈接成可執(zhí)行文件------------------"
clang -target $TAREGT   \
-fobjc-arc              \
-isysroot $SYSROOT      \
-Xlinker -dead_strip    \
-L${LIBRARY_PATH}       \
-l${STATICLIBRARY}      \
${FILE_NAME}.o -o $FILE_NAME
  • -dead_strip:啟用代碼優(yōu)化,沒有被入口點(diǎn)或?qū)С龇柺褂玫暮瘮?shù)和數(shù)據(jù)將被刪除

使用./build.sh命令,鏈接成可執(zhí)行文件

使用objdump --macho --syms test命令,查看test符號表

SYMBOL TABLE:
0000000100008008 l     O __DATA,__data __dyld_private
0000000100000000 g     F __TEXT,__text __mh_execute_header
0000000100003f60 g     F __TEXT,__text _main
0000000000000000         *UND* _NSLog
0000000000000000         *UND* ___CFConstantStringClassReference
0000000000000000         *UND* dyld_stub_binder
  • 設(shè)置-dead_strip參數(shù)后,global_function的符號被剝離了。雖然它是全局符號,也是導(dǎo)出符號,但它沒有被入口點(diǎn)或其他導(dǎo)出符號使用,所以被剝離
  • 由于鏈接靜態(tài)庫時(shí),默認(rèn)為-noall_load,符合剝離條件的代碼全部剝離,所以靜態(tài)庫相關(guān)代碼也被剝離

打開build.sh腳本,增加-all_load參數(shù)

echo "-------------將test.o鏈接成可執(zhí)行文件------------------"
clang -target $TAREGT   \
-fobjc-arc              \
-isysroot $SYSROOT      \
-Xlinker -dead_strip    \
-Xlinker -all_load      \
-L${LIBRARY_PATH}       \
-l${STATICLIBRARY}      \
${FILE_NAME}.o -o $FILE_NAME
  • -all_load:針對靜態(tài)庫,不要?jiǎng)冸x任何代碼

使用./build.sh命令,鏈接成可執(zhí)行文件

使用objdump --macho --syms test命令,查看test符號表

SYMBOL TABLE:
0000000100003460 l     F __TEXT,__text -[TestExample lg_test:]
00000001000034e0 l     F __TEXT,__text ___cpu_indicator_init
0000000100003d60 l     F __TEXT,__text ___cpu_indicator_init.cold.1
0000000100003d80 l     F __TEXT,__text ___cpu_indicator_init.cold.2
0000000100008018 l     O __DATA,__objc_const __OBJC_METACLASS_RO_$_TestExample
0000000100008060 l     O __DATA,__objc_const __OBJC_$_INSTANCE_METHODS_TestExample
0000000100008080 l     O __DATA,__objc_const __OBJC_CLASS_RO_$_TestExample
0000000100008118 l     O __DATA,__data __dyld_private
0000000100008120 l     O __DATA,__common ___cpu_model
0000000100008130 l     O __DATA,__common ___cpu_features2
00000001000080f0 g     O __DATA,__objc_data _OBJC_CLASS_$_TestExample
00000001000080c8 g     O __DATA,__objc_data _OBJC_METACLASS_$_TestExample
0000000100000000 g     F __TEXT,__text __mh_execute_header
00000001000034b0 g     F __TEXT,__text _main
0000000000000000         *UND* _NSLog
0000000000000000         *UND* _OBJC_CLASS_$_NSObject
0000000000000000         *UND* _OBJC_METACLASS_$_NSObject
0000000000000000         *UND* ___CFConstantStringClassReference
0000000000000000         *UND* ___assert_rtn
0000000000000000         *UND* __objc_empty_cache
0000000000000000         *UND* _objc_storeStrong
0000000000000000         *UND* dyld_stub_binder
  • 設(shè)置-all_load參數(shù)后,靜態(tài)庫的符號全部被保留下來
  • global_function符號依然被剝離了,因?yàn)榭刂扑氖?code>-dead_strip參數(shù),而-all_load只針對靜態(tài)庫有效

由此證明:-noall_loadXcodeDead Code Stripping配置項(xiàng),完全不是一個(gè)東西

查看指定符號的使用鏈

打開test.m文件,寫入以下代碼:

#import <Foundation/Foundation.h>

void global_function() {
}

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

打開build.sh腳本,最后一步鏈接成可執(zhí)行文件,增加-why_live -Xlinker 【符號名稱】參數(shù)

echo "-------------將test.o鏈接成可執(zhí)行文件------------------"
clang -target $TAREGT   \
-fobjc-arc              \
-isysroot $SYSROOT      \
-Xlinker -dead_strip    \
-Xlinker -all_load      \
-Xlinker -why_live -Xlinker _global_function \
-L${LIBRARY_PATH}       \
-l${STATICLIBRARY}      \
${FILE_NAME}.o -o $FILE_NAME

使用./build.sh命令,鏈接成可執(zhí)行文件

-------------將test.o鏈接成可執(zhí)行文件------------------
_global_function from test.o
 _main from test.o
   _main from test.o
  • 終端打印出_global_function符號的使用鏈,它被test.o中的main函數(shù)使用
Link-Time Optimization

多個(gè).o文件鏈接成可執(zhí)行文件,和.o鏈接靜態(tài)庫有一些差異:

  • .o鏈接成可執(zhí)行文件:先將多個(gè).o文件合并成一個(gè)大的.o文件,再去鏈接成可執(zhí)行文件。先組合再鏈接,故此Dead Code Stripping的優(yōu)化無法生效
  • .o鏈接靜態(tài)庫:相當(dāng)于.o直接使用靜態(tài)庫。先Dead Code Stripping再使用

此時(shí)可以使用鏈接器提供的另一個(gè)參數(shù),LTOLink-Time Optimization)鏈接時(shí)間優(yōu)化

  • 使用Monolothic選項(xiàng),在多個(gè).o文件鏈接時(shí)進(jìn)行優(yōu)化,此優(yōu)化在Dead Code Stripping之后觸發(fā)
總結(jié):

靜態(tài)庫鏈接成功的三要素:

  • 指定頭文件路徑Header Search Paths
  • 指定庫文件路徑(.a\.dylib庫文件)Library Search Paths
  • 指定鏈接的庫文件名稱(.a\.dylib庫文件)Other Linker Flags -lAFNetworking

Framework鏈接成功的三要素:

  • 指定頭文件路徑Header Search Paths
  • 指定Framework所在目錄Framework Search Paths
  • 指定鏈接Framework的名稱Other Linker Flags -framework AFNetworking
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

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

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