前言:
查看某一个命令作用有两种方式
// 第一种方式
1. $ man nm
// 第二种方式,可以输出简短的命令参数信息
2. $ nm --help
// -p 表示不排序 -a表示全部符号 这些命令作用都可以在终端查看
// 比如查看 nm 作用,终端输入 $ man nm 然后输入 /-p , n向下查找,N向上查找 , q 退出查找
3. $ nm -pa test
库(Library)就是一段编译好的二进制代码,加上头文件提供给别人使用。
.a:常见的静态库文件格式
.dylib:传统意义上的动态库文件格式
.framework:可以是静态库也可以是动态库。
.xcframework:刚推出,不同架构库放到一块。
一. 链接静态库
1.1 file命令查看一个文件具体格式,.a是一个文档格式
$ file libAFNetworking.a
//libAFNetworking.a: current ar archive
1.2 查看.a文件的内容
$ 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 创建test文件去调用我们的库
#import
#import
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
\ 是一个转义字符,本来敲回车是要执行命令,现在加上转义字符\,变成了换行
-target: 指定架构平台,生成的是X86_64_macOS架构的代码
-fobjc-arc: 编译arc环境
-isysroot: 指定系统Foundation库路径,因为在Mac上编译,所以指定了MacOSX11.0.sdk,也可以换成手机或者模拟器,指定好对应的Foundation路径即可
-I: 用到的其他库的头文件地址在./Frameworks,./表示AFNetWorking与test.m是同一级目录,如果是多个引用库,可以写多个 -I/.AFNetWorking -I/.SDWebImage
1.4 生成可执行文件
$ 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可执行文件
-L./StaticLibrary 在当前目录的子目录StaticLibrary查找需要的库文件
-lTestExample 链接的名称为libTestExample/TestExample的动态库或者静态库
查找规则:先找lib+
小结:
clang命令参数:
-x: 指定编译文件语言类型
-g: 生成调试信息
-c: 生成目标文件,只运行preprocess,compile,assemble,不链接
-o: 输出文件
-isysroot: 使用的SDK路径
链接一个库文件的三要素:
1. -I 在指定目录寻找头文件 header search path
2. -L 指定库文件路径(.a.dylib库文件) library search path
3. -l 指定链接的库文件名称(.a.dylib库文件)other link flags -lAFNetworking
-F 在指定目录寻找framework framework search path
-framework 指定链接的framework名称 other link flags -framework AFNetworking
二. 静态库原理
2.1 首先创建这样的文件,如下图所示
//TestExample的h文件
#import
@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
#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。如果系统比较新可以把o文件改成后缀dylib的文件,改成可执行文件之后,可以选择把.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 链接生成可执行文件
$ 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,直接执行脚本文件
// build.sh文件内容
// 变量定义,注意!!!不能有空格
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进入文件夹会修改系统默认文件目录,所以这里使用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}
//执行脚本,./build.sh 后面也可以指定一些参数
$ ./build.sh
//报无权限zsh: permission denied: ./build.sh
// 通过下面命令授权
$ chmod +x ./build.sh
//再次执行脚本
$ ./build.sh
2.7 执行
$ lldb
file test
Current executable set to '/Users/wangning/Documents/资料/1:20/静态库/上课代码/静态库原理/test' (x86_64).
r
Process 21101 launched: '/Users/wangning/Documents/资料/1:20/静态库/上课代码/静态库原理/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
小结:
shell是一个解释型语言,一行一行解释执行
TestExample 文件是把 TestExample.m 打包.a文件,同时删除lib前缀与.a后缀
创建脚本文件build.sh
静态库就是目标文件.o文件的合集,Framework的合成和.a差不多,唯一区别就是在链接的时候-L和-l换成-F 和-framework
三. 静态库合并
ar压缩目标文件,并对其进行编号和索引,形成静态库。同时也可以解压缩静态库,查看有哪些目标文件:
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
静态库合并最要考虑的点:libSDWebImage.a与libAFNetworking.a中.o文件合并之后,头文件.h要怎么处理?
四. module文件
4.1 下面代码中TestExample导入之后,虽然没有使用,但是TestExample文件会进行编译,如果多个类都倒入,就会编译多次。这个时候Clang 提供了module,module可以提前把.h文件编译成二进制缓存到系统目录中,这样就解决了多次编译的问题。
#import
#import "TestExample.h"
int main(){
NSLog(@"testApp----");
return 0;
}
4.2 Auto-Link
链接器的特性,Auto-Link
。启用这个特性后,当我们import <模块>
,不需要我们再去往链接器去配置链接参数。比如import
我们在代码里使用这个是framework格式的库文件,那么在生成目标文件时,会自动在目标文件的Mach-O
中,插入一个 load command
格式是LC_LINKER_OPTION
,存储这样一个链接器参数-framework
。
4.3 Framework
Mac OS/iOS 平台还可以使用 Framework。Framework 实际上是一种打包 方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管 理和分发。
Framework 和系统的 UIKit.Framework 还是有很大区别。系统的 Framework 不需要拷⻉到目标程序中,我们自己做出来的 Framework 哪怕 是动态的,最后也还是要拷⻉到 App 中(App 和 Extension 的 Bundle 是 共享的),因此苹果又把这种 Framework 称为 Embedded Framework。
五. dead code strip
我们把test.m文件修改如下
#import
#import "TestExample.h"
int main(){
NSLog(@"testApp----");
// TestExample *manager = [TestExample new];
// [manager lg_test: nil];
return 0;
}
现在只引用了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的代码并没有
现在把test.m文件还原如下
#import
#import "TestExample.h"
int main(){
NSLog(@"testApp----");
TestExample *manager = [TestExample new];
[manager lg_test: nil];
return 0;
}
执行上述脚本以及$ 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:
这样会出现一个问题,因为我们的类别是在运行时动态创建的,但是我们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.