什么是库?
库(Library):就是⼀段编译好的⼆进制代码,加上头⽂件就可以供别⼈使⽤。
常⽤库⽂件格式:
.a、.dylib、.framework、.xcframework、.tdb
什么时候会⽤到库?
- 某些代码需要给别⼈使⽤,但是我们不希望别⼈看到源码,就需要以库的形式进⾏封装,只暴露出头⽂件
- 对于某些不会进⾏⼤改动的代码,我们想减少编译的时间,就可以把它打包成库。因为库是已经编译好的⼆进制,编译的时候只需要
Link⼀下,不会浪费编译时间
什么是链接?
库(
Library)在使⽤的时候需要链接(Link)链接的⽅式有两种:
- 静态
- 动态
什么是静态库?
静态库即静态链接库:可以简单的看成⼀组⽬标⽂件的集合。即很多⽬标⽂件经过压缩打包后形成的⽂件。
Windows下的.lib,Linux和Mac下的.a。Mac独有的.framework缺点:浪费内存和磁盘空间,模块更新困难
链接静态库
生成目标文件
目录中包含一个
test.m文件和AFNetworking三方库
打开
test.m文件,写入以下代码:#import <Foundation/Foundation.h> #import <AFNetworking.h> 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+<library_name>的动态库,找不到,再去找lib+<library_name>的静态库,还找不到,就报错
静态库链接成功的三要素:
-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 <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此时目录中生成了
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 <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参数,指定头文件路径此时目录中生成了
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可执行文件包装成一个targetCurrent 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>,代码中使用这个Framework格式的库文件,在生成目标文件时,会自动在目标文件的Mach-O中,插入一个load command,格式是LC_LINKER_OPTION,存储链接器参数-framework <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 <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此时目录中生成了
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 <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此时目录中生成了
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可执行文件包装成一个targetCurrent 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 <Foundation/Foundation.h> #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 <Foundation/Foundation.h> #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 <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>导入
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 <LGStaticFramework/LGOneObject.h> @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 <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是静态库,在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 <Foundation/Foundation.h> 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


















































