什么是库?
库(Library
):就是⼀段编译好的⼆进制代码,加上头⽂件就可以供别⼈使⽤。
常⽤库⽂件格式:
.a
、.dylib
、.framework
、.xcframework
、.tdb
什么时候会⽤到库?
- 某些代码需要给别⼈使⽤,但是我们不希望别⼈看到源码,就需要以库的形式进⾏封装,只暴露出头⽂件
- 对于某些不会进⾏⼤改动的代码,我们想减少编译的时间,就可以把它打包成库。因为库是已经编译好的⼆进制,编译的时候只需要
Link
⼀下,不会浪费编译时间
什么是链接?
库(
Library
)在使⽤的时候需要链接(Link
)链接的⽅式有两种:
- 静态
- 动态
什么是静态库?
静态库即静态链接库:可以简单的看成⼀组⽬标⽂件的集合。即很多⽬标⽂件经过压缩打包后形成的⽂件。
Windows
下的.lib
,Linux
和Mac
下的.a
。Mac
独有的.framework
缺点:浪费内存和磁盘空间,模块更新困难
链接静态库
生成目标文件
目录中包含一个
test.m
文件和AFNetworking
三方库
打开
test.m
文件,写入以下代码:#import
#import int main(){ AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; NSLog(@"testApp----%@", manager); return 0; }
AFNetworking
为静态库,打开AFNetworking
目录,里面包含了头文件
和.a
文件
打开终端,进入指定目录,使用
file libAFNetworking.a
命令,查看.a
的文件格式libAFNetworking.a: current ar archive
- 从打印结果来看,
.a
文件是一个文档格式使用
ar -t libAFNetworking.a
命令,查看.a
文件
.a
中包含了AFNetworking
编译出来的所有目标文件,验证了静态库其实就是.o
文件的合集使用
man clang
查看clang
命令
clang
是C
、C++
、Objective-C
的编译器。它也是一个工具的合集,包含了预处理、解析、优化、代码生成、汇编化、链接使用
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
:指定生成架构-fobjc-arc
:使用ARC
-isysroot
:使用SDK
的路径-I
:指定头文件路径Header Search Paths
-c
:生成目标文件-o
:输出文件此时目录中生成了
.o
目标文件
- 目标文件中包含重定位符号表,它保存了文件中使用的所有符号,链接时会根据重定位符号表生成具体的符号信息
- 所以在生成目标文件时,只需静态库的头文件即可。重定位符号表只需要记录哪个地方的符号需要重定位,然后在链接过程中会自动将符号重定位
生成可执行文件
使用
clang
命令,将.o
文件链接成可执行文件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
此时目录中生成了
test
可执行文件
- 链接成可执行文件,会将重定位符号表中的符号进行重定位,此时需要知道符号的真实地址,而真实地址保存在静态库的
.o
文件中,需要指定库文件路径和将要链接的库文件名称- 库文件名称的查找规则:先找
lib+
的动态库,找不到,再去找lib+
的静态库,还找不到,就报错
静态库链接成功的三要素:
-I
:指定头文件路径Header Search Paths
-L
:指定库文件路径(.a\.dylib
库文件)Library Search Paths
-l
:指定链接的库文件名称(.a\.dylib
库文件)Other Linker Flags -lAFNetworking
链接静态库的原理
静态库是.o
文件的合集,下面我们证明这一点:
生成可执行文件
项目中包含
test.m
文件和一个StaticLibrary
子目录,StaticLibrary
目录下包含TestExample.h
文件和TestExample.m
文件
打开
TestExample.h
文件,写入以下代码:#import
@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
此时目录中生成了
TestExample.o
目标文件
如果只有一个
.o
文件,那它自身就相当于这个合集。将TestExample.o
文件重命名为libTestExample.dylib
(改为.a
格式也可以)
使用
file libTestExample.dylib
命令,查看libTestExample.dylib
的文件格式libTestExample.dylib: Mach-O 64-bit object x86_64
- 此时
libTestExample.dylib
依然是目标文件打开
test.m
文件,写入以下代码:#import
#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
参数,指定头文件路径此时目录中生成了
test.o
目标文件
使用
clang
命令,将test.o
文件链接成可执行文件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
此时目录中生成了
test
可执行文件
运行可执行文件
使用
lldb
命令,在终端中进入lldb
环境
使用
file test
命令,将test
可执行文件包装成一个target
Current executable set to '/Users/zang/Zang/Spark/test' (x86_64).
使用
r
命令,开始运行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)
- 执行成功,打印的内容正是
test.m
和TestExample.m
输出的内容使用
q
,退出lldb
环境
使用
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
依然是一个目标文件由此证明:静态库就是
.o
文件的合集
静态库的合并
静态库合并的两种方式:
- 使用
ar
命令- 使用
Xcode
提供的libtool
命令
ar命令
使用
man ar
,查看ar
命令
- 可以查看静态库
- 也能将多个
.o
文件合并成静态库- 可以将静态库中包含的
.o
文件全部解压出来目录下,分别是
libAFNetworking.a
和libSDWebImage.a
两个静态库
使用
ar x libAFNetworking.a
命令,解压libAFNetworking.a
静态库
使用
ar x libSDWebImage.a
命令,解压libSDWebImage.a
静态库
使用
ar r lib_AF_SD.a *.o
命令,将目录下的所有.o
文件,合并成一个.a
文件
使用
objdump --macho --rebase lib_AF_SD.a
命令,查看lib_AF_SD.a
的重定位符号表
- 此时
lib_AF_SD.a
文件中,包含了libAFNetworking.a
和libSDWebImage.a
的所有.o
文件,相当于将两个静态库进行合并
libtool
使用
man libtool
,查看libtool
命令
- 可以创建库文件
- 可以添加或更新一系列的静态库文件
使用
libtool
命令,合并libAFNetworking.a
和libSDWebImage.a
两个静态库libtool -static \ -o \ libCat.a \ libAFNetworking.a \ libSDWebImage.a
目录下,成功合并出
libCat.a
静态库
使用
objdump --macho --rebase libCat.a
命令,查看libCat.a
的重定位符号表
- 此时
libCat.a
文件中,包含了libAFNetworking.a
和libSDWebImage.a
的所有.o
文件,相当于将两个静态库进行合并
mudule
mudule
是clang
提供的解析.h
头文件的一种解析格式
mudule
可以把.h
头文件预先编译成二进制,存储到系统缓存目录中。好处:当编译.m
文件时,避免反复编译.h
文件例如:当一个
.h
文件在很多.m
中被import
,当这些.m
文件被编译时,不需要一遍又一遍的重复编译.h
文件,它会直接使用mudule
预先编译好的二进制
Auto-Link
Auto-Link
是LC_LINKER_OPTION
链接器的特性。启用该特性后,在import <模块>
时不需要再往链接器去配置链接参数例如:
import
,代码中使用这个Framework
格式的库文件,在生成目标文件时,会自动在目标文件的Mach-O
中,插入一个load command
,格式是LC_LINKER_OPTION
,存储链接器参数-framework
Framework
Mac OS/iOS
平台还可以使⽤Framework
Framework
实际上是⼀种打包⽅式,将库的⼆进制⽂件,头⽂件和有关的资源⽂件打包到⼀起,⽅便管理和分发
Framework
和系统的UIKit.Framework
还是有很⼤区别。系统的Framework
不需要拷⻉到⽬标程序中,而自定义的Framework
哪怕是动态的,最后也要拷⻉到App
中(App
和Extension
的Bundle
是共享的),因此苹果⼜把这种Framework
称为Embedded Framework
Framework
可以是静态库,也可以是动态库
- 无论是静态库还是动态库,
Framework
都包含了头文件、库的原始文件、签名和资源文件Framework
是静态库还是动态库,取决于库的原始文件
Embedded Framework
开发中使⽤的动态库会被放⼊到
ipa
下的framework
⽬录中,基于沙盒运⾏不同的
App
使⽤相同的动态库,并不会只在系统中存在⼀份。⽽是会在多个App
中各⾃打包、签名、加载一份
App Framework
存放位置
手动创建Framework
创建
Frameworks
目录,目录中包含TestExample.h
文件和TestExample.m
文件
打开
TestExample.h
文件,写入以下代码:#import
@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
此时目录中生成了
TestExample.o
目标文件
使用
ar -rc libTestExample.a TestExample.o
命令,生成libTestExample.a
文件
在
Frameworks
目录下,手动创建TestExample.framework
静态库
- 在
Frameworks
目录下,创建TestExample.framework
目录- 在
TestExample.framework
目录下,创建Headers
目录- 将
TestExample.h
头文件移动到TestExample.framework/Headers
目录下- 将
libTestExample.a
库文件移动到TestExample.framework
目录下,和Headers
目录平级- 重命名
libTestExample.a
库文件,去掉lib
开头,去掉.a
后缀名创建
test.m
文件,和Frameworks
目录平级
打开
test.m
文件,写入以下代码:#import
#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
此时目录中生成了
test.o
目标文件
使用
clang
命令,将test.o
文件链接成可执行文件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
命令,进入lldb
终端。使用file test
命令,将test
可执行文件包装成一个target
Current executable set to '/Users/zang/Zang/Spark/Framework/test' (x86_64).
使用
r
命令,开始运行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)
- 执行成功,打印的内容正是
test.m
和TestExample.m
输出的内容
Framework
链接成功的三要素:
-I
:指定头文件路径Header Search Paths
-F
:指定Framework
所在目录Framework Search Paths
-framework
:指定链接Framework
的名称Other Linker Flags -framework AFNetworking
Shell脚本的使用
Shell
是一门解释型的编程语言(脚本语言),它的解释器就Shell
这个程序。作为解释型语言,Shell
语言具有明显的“胶水语言”的特性,并且这种特性由于其直接运行在Shell
中而被放大;通过Shell
编程可以极大地缓解“重复的人机交互命令”给使用者带来的疲劳,实现办公自动化。
使用
Shell
脚本,将.o
文件链接成可执行文件,实现自动化创建
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
命令,用于字符串输出,一般起到提示作用【步骤二】:进入
StaticLibrary
目录echo "-------------进入到StaticLibrary目录------------------" pushd ./StaticLibrary
- 使用
cd
命令,也可以进入一个目录,但这里不推荐使用,因为cd
会直接修改目录栈里最上层的元素,修改后无法返回- 推荐使用
pushd
命令,pushd
是往目录栈中重新push
一个目录,修改后可以使用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
文件链接成可执行文件echo "-------------将test.o链接成可执行文件------------------" 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
- 此时
build.sh
的脚本代码全部完成
打开终端,使用
chmod
命令,为build.sh
文件增加可执行权限chmod +x ./build.sh
使用
ls -all
命令,查看文件权限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
命令,执行Shell
脚本-------------编译test.m to test.o------------------ -------------进入到StaticLibrary目录------------------ ~/Zang/Spark/Shell/StaticLibrary ~/Zang/Spark/Shell -------------编译TestExample.m to TestExample.o------------------ -------------TestExample.o to libTestExample.a------------------ -------------退出StaticLibrary目录------------------ ~/Zang/Spark/Shell -------------将test.o链接成可执行文件------------------
执行成功,目录下自动生成
test
可执行文件
优化
Shell
脚本,将脚本内多次出现的参数提取成变量
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 "-------------进入到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链接成可执行文件------------------"
clang -target $TAREGT \
-fobjc-arc \
-isysroot $SYSROOT \
-L${LIBRARY_PATH} \
-l${STATICLIBRARY} \
${FILE_NAME}.o -o $FILE_NAME
- 使用变量的方式,可以通过
$
或${}
两种方式$
:当没有其他内容的拼接,可以直接使用$XXX
${}
:变量前后有其他内容的拼接,例如:${STATICLIBRARY}.m
,需要使用${XXX}
-noall_load
链接库文件的过程中,默认设置为
-noall_load
,符合剥离条件的代码全部剥离打开
test.m
文件,只导入TestExample.h
头文件,不使用TestExample.m
的任何代码#import
#import "TestExample.h" int main(){ NSLog(@"testApp----"); // TestExample *manager = [TestExample new]; // [manager lg_test: nil]; return 0; } 使用
./build.sh
命令,链接成可执行文件使用
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
- 只有一个
main
函数- 说明
clang
在链接过程中,默认就是-noall_load
打开
test.m
文件,使用TestExample.m
的lg_test
方法#import
#import "TestExample.h" int main(){ NSLog(@"testApp----"); TestExample *manager = [TestExample new]; [manager lg_test: nil]; return 0; } 使用
./build.sh
命令,链接成可执行文件使用
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
函数,还多了lg_test
方法- 说明在链接过程中,将多个
.o
文件中的代码和符号放到一起,最终链接出一个可执行文件
使用
-noall_load
的问题在
OC
中,分类是在运行时动态创建的,但-noall_load
参数在链接时已经生效。在链接时发现分类的代码没有被使用,就会将其剥离搭建
LGStaticFramework
静态库
打开
LGOneObject+Category.h
文件,写入以下代码:#import
@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
@interface LGOneObject : NSObject - (void)lg_test; @end 打开
LGOneObject.m
文件,写入以下代码:#import "LGOneObject.h" #import
@implementation LGOneObject - (void)lg_test { [self lg_test_category]; } @end 打开
LGStaticFramework.h
文件,写入以下代码:#import
#import 导入
LGApp
项目,通过LGApp
链接LGStaticFramework
静态库使用
workspace
搭建多项目合集
- 可重⽤性。多个模块可以在多个项⽬中使⽤。节约开发和维护时间
- 节省测试时间。单独模块意味着每个模块中都可以添加测试功能
- 更好的理解模块化思想
选择
File
->Save As Workspace...
在项目根目录下创建
TestDeadStrip.xcworkspace
关闭
Xcode
,来到项目根目录,使用TestDeadStrip.xcworkspace
打开项目
点击左下角
+
,选择Add Files to “TestDeadStrip”...
- 注意:上面一定不要选择任何文件,先叉掉所有文件,再点
+
找到一个已有项目,选择
LGApp.xcodeproj
,点击Add
将
LGApp
项目,加入到TestDeadStrip.xcworkspace
中
将静态库添加到
LGApp
项目
选择
LGStaticFramework.framework
添加成功后,当编译
LGApp
项目时,静态库会一起编译
将静态库的
Embed
选择为Do Not Embed
Do Not Embed
:不会将Framework
拷贝到IPA
包里。用于静态库
项目使用的Framework
是静态库,链接时静态库的代码和符号会跟App
进行合并,故此不需要将Framework
拷贝到IPA
包里Embed & Sign
:将Framework
拷贝到IPA
包里,并进行签名操作。用于动态库Embed Without Signing
:如果Framework
已有正确签名,可以使用此项在
LGApp
项目中,打开ViewController.m
文件,写入以下代码:#import "ViewController.h" #import
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; LGOneObject *obj = [LGOneObject new]; [obj lg_test]; } @end 运行项目后,程序直接崩溃
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[LGOneObject lg_test_category]: unrecognized selector sent to instance 0x6000002b4240'
- 崩溃的原因就是在于
-noall_load
,因为分类是在运行时动态创建的,但-noall_load
参数导致在链接时,已经将静态库的分类代码剥离
解决此问题,需要借助链接器的参数配置
打开
LGApp
项目,创建xcconfig
,写入以下代码:LGSTATIC_FRAMEWORK_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/LGStaticFramework.framework/LGStaticFramework OTHER_LDFLAGS=-Xlinker -force_load ${LGSTATIC_FRAMEWORK_PATH}
-noall_load
:默认值,符合剥离条件的代码全部剥离-all_load
:不要剥离任何代码-ObjC
:OC
代码不要剥离-force_load
:指定某个静态库不要剥离代码,参数后面需要拼接静态库路径将
xcconfig
配置到Tatget
上
再次运行项目,分类方法被成功调用,打印
lg_test_category
-noall_load和Dead Code Stripping区别
-noall_load
和Xcode
中Dead Code Stripping
配置项,完全不是一个东西
-noall_load
- 链接库文件时,控制死代码剥离的参数之一,符合剥离条件的代码全部剥离
- 在
ld
中有-noall_load
、-all_load
、-ObjC
、-force_load
四种参数的配置
Dead Code Stripping
- 在链接过程中,链接器提供的一种代码优化方式
- 在
ld
中指定-dead_strip
参数,决定此功能是否启用。启用后,没有被入口点或导出符号使用的函数和数据将被删除
打开
test.m
文件,写入以下代码:#import
#import "TestExample.h" void global_function() { } int main(){ // global_function(); NSLog(@"testApp----"); // TestExample *manager = [TestExample new]; // [manager lg_test: nil]; return 0; }
TestExample
是静态库,在test.m
文件中只导入了.h
头文件,没有使用里面的代码global_function
是一个全局函数,但并没有被调用
打开
build.sh
脚本,最后一步链接成可执行文件,增加-dead_strip
参数echo "-------------将test.o链接成可执行文件------------------" clang -target $TAREGT \ -fobjc-arc \ -isysroot $SYSROOT \ -Xlinker -dead_strip \ -L${LIBRARY_PATH} \ -l${STATICLIBRARY} \ ${FILE_NAME}.o -o $FILE_NAME
-dead_strip
:启用代码优化,没有被入口点或导出符号使用的函数和数据将被删除使用
./build.sh
命令,链接成可执行文件使用
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
- 设置
-dead_strip
参数后,global_function
的符号被剥离了。虽然它是全局符号,也是导出符号,但它没有被入口点或其他导出符号使用,所以被剥离- 由于链接静态库时,默认为
-noall_load
,符合剥离条件的代码全部剥离,所以静态库相关代码也被剥离
打开
build.sh
脚本,增加-all_load
参数echo "-------------将test.o链接成可执行文件------------------" 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
:针对静态库,不要剥离任何代码使用
./build.sh
命令,链接成可执行文件使用
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
- 设置
-all_load
参数后,静态库的符号全部被保留下来global_function
符号依然被剥离了,因为控制它的是-dead_strip
参数,而-all_load
只针对静态库有效由此证明:
-noall_load
和Xcode
中Dead Code Stripping
配置项,完全不是一个东西
查看指定符号的使用链
打开
test.m
文件,写入以下代码:#import
void global_function() { } int main(){ global_function(); NSLog(@"testApp----"); return 0; } 打开
build.sh
脚本,最后一步链接成可执行文件,增加-why_live -Xlinker 【符号名称】
参数echo "-------------将test.o链接成可执行文件------------------" 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
命令,链接成可执行文件-------------将test.o链接成可执行文件------------------ _global_function from test.o _main from test.o _main from test.o
- 终端打印出
_global_function
符号的使用链,它被test.o
中的main
函数使用
Link-Time Optimization
多个
.o
文件链接成可执行文件,和.o
链接静态库有一些差异:
.o
链接成可执行文件:先将多个.o
文件合并成一个大的.o
文件,再去链接成可执行文件。先组合再链接,故此Dead Code Stripping
的优化无法生效.o
链接静态库:相当于.o
直接使用静态库。先Dead Code Stripping
再使用此时可以使用链接器提供的另一个参数,
LTO
(Link-Time Optimization
)链接时间优化
- 使用
Monolothic
选项,在多个.o
文件链接时进行优化,此优化在Dead Code Stripping
之后触发
总结:
静态库链接成功的三要素:
- 指定头文件路径
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