一:静态链接库
1.制作.a文件
.a文件即 static library
创建library,project的配置对最终产物.a文件基本没有影响,只需要关注target的配置
copy files会在product文件夹生成include文件夹,里面就是cpoy file
Header会在product文件夹生成一个usr/local/include/文件夹,里面是暴露的.h文件
除了默认的架构,还可以添加其他架构,不过需要对应上iOS版本号
接下来运行,运行出来的有四种product:
Debug-iphoneos
Release-iphoneos
Debug-iphonesimulator
Release-iphonesimulator
我们需要考虑2个问题,
第一是使用静态库的时候通常并不区分debug和release,这要看具体需求,如果debug和release的配置不同,则需要分别处理;
第二是包的体积大小,实质就是这个静态库支持的架构是不是都需要的.比支持iOS12以上的情况下,ipa里的这个静态库只需要arm64就行了,但是模拟器需要考虑mac的架构,并且模拟器不需要关心包体积.
因此要结合实际情况合并和拆分架构.
有相同架构的包是不能直接合并的,要把其中一个去除掉.
lipo -info (绝对路径或相对路径) 查看架构
lipo -output (新文件绝对路径或相对路径) -remove (架构) (旧文件绝对路径或相对路径) 移除架构
lipo -output (新文件绝对路径或相对路径) -create (旧文件绝对路径或相对路径1) (旧文件绝对路径或相对路径2) 合并架构
最终生成一个包含armv7 armv7s i386 x86_64 arm64 的.a
Architectures in the fat file: TestStatic.a are: armv7 armv7s i386 x86_64 arm64
2..a的特性
用machOView看一下这个.a,(关于Mach-O)
静态链接库指的是参与静态链接,通常.a文件就是.o的集合,是编译的产物,并非链接的产物;
因此本身是不经过静态链接的,compile source里有多少个文件,就有多少个.o;
所以只要能编译通过,就可以生成静态链接库,.h文件不参与编译,所以只声明没实现的类和方法会被作为外部符号引用,等到项目真正链接的时候,再去决议和重定位.
3.制作静态framework
之前的MakeLibrary2里面是这样的
#import "MakeLibrary2.h"
#import "MakeLibrary/MakeLibrary.h"
@implementation MakeLibrary2
- (instancetype)init{
if(self = [super init]){
NSLog(@"MakeLibrary2 init");
MakeLibrary *m = [[MakeLibrary alloc]init];
}
return self;
}
@end
MakeLibrary2使用了MakeLibrary,但是制作MakeLibrary2.a的时候没有引入MakeLibrary.a,也就是说MakeLibrary2.a里没有MakeLibrary符号.
新建一个framework的target,在build setting -> linking 中有个mach-o type, 默认是dynamic Library, 改成static Library,然后把MakeLibrary2.a放进来
然后引用makeLibrary2类
#import "FrameClass.h"
#import "MakeLibrary2.h"
@implementation FrameClass
- (instancetype)init{
if(self = [super init]){
NSLog(@"FrameClass init");
MakeLibrary2 *m = [[MakeLibrary2 alloc]init];
}
return self;
}
@end
添加Headers
添加Link Binary With Libraries
然后运行,生成的framework里面是这样的
用machOView查看这个MakeFramework,和.a没区别,是.o文件的集合,这里只有一个x86_64架构,所以没有Fat Binary.
引入到UseLibrary中,使用
#import "ViewController.h"
#import "FrameClass.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
FrameClass *fc = [[FrameClass alloc]init];
}
@end
运行,输出
FrameClass init
MakeLibrary2 init
MakeLibrary init
Make FirstObj init
- 总结
再生成时:静态链接库的生成过程不包含静态链接,所做的事主要是编译,而符号解析重定位都没有做,引用的符号是不是真的存在,是不是有重复的符号都不重要,要做的只有编译,然后把目标文件打包起来.最终.a里可能会有重复的.o(指的是同一架构内).
在使用时:在静态库是参与静态链接的,静态链接时会合并mach-o,因此对于主程序来说,编译之后,静态库里的目标文件被复制并且合并到了主程序的mach-o中,而静态库本身不会发生变化.
用命令行工具libtool可以拆分和组合static Library的.o并生成新的static Library
比如合并
libtool -static -o 输出文件 staticLibraryA staticLibraryB
二.动态链接库
1.制作动态framework
动态链接库经过了静态链接,已经没有.o文件在了,会进行符号解析.
可以验证一下
接下来运行,打算生成framework.结果报错了,因为MakeLibrary2里引用了MakeLibrary,但是实际上现在这个target里没有这个类.
把MakeLibrary2 *m = [[MakeLibrary2 alloc]init];这行注释,就可以运行成功,得到framework,可以看到它被系统标记为可执行文件
然后用MachOView查看这个可执行文件,它是一个Shared Libray, file type是dylib,可以看到已经没有.o了
2.制作一个动态的.a
创建target选择static Library时,这项默认是static Library,但是也可以手动改成dynamic Library
运行,然后machOView查看.a文件,果真是dylib,和上面的framework一样,甚至如果把.a扩展名删掉,会被标记为可执行文件
实际上生成的就是一个动态库,可以直接使用
- 总结
在生成时:动态链接库在生成时会执行静态链接,把目标文件,包括依赖的静态库里的目标文件合并到dylib中,这个过程还包括符号解析和重定位,这时根据不同的依赖关系和链接策略进行检查并抛出异常.后面具体说明.
在使用时:静态链接时会把引用自动态链接库的符号进行标记,设置占位地址,真正的符号重定位需要在运行时由dyld来做,因此动态库是不是真的定义了这个符号,需要运行时才知道.
embedded framework会被复制到.app里面的framework文件夹中.
三.依赖关系
build setting -> linking可以配置链接的选项.这些就会影响依赖关系.
比如静态链接时,链接器ld64会进行dead code striping,此时就需要解析依赖关系,然后剥离未被引用的符号.
比如ohter link flags.
制作静态库的时候,静态库本身经常需要访问外部文件,比如依赖别的静态库,或者直接引用外部的文件.
首先创建一个新项目,创建时选择app target,叫做UseLibrary用来使用静态库
然后再添加一个target,选择Static Library 叫做makeLibrary用来生成静态库.
1.静态库引用外部文件
这个MakeLibrary封装了一些功能,并且需要引用UseLibrary里的一个叫做FirstObj的类.
直接import并且使用,是可以编译通过的.
#import "FirstObj.h"
@implementation FirstObj
- (instancetype)init{
if(self = [super init]){
NSLog(@"FirstObj init");
}
return self;
}
@end
#import "MakeLibrary.h"
#import "FirstObj.h"
@implementation MakeLibrary
- (instancetype)init{
if(self = [super init]){
NSLog(@"MakeLibrary init");
FirstObj *f = [[FirstObj alloc]init];
}
return self;
}
@end
接下来生成.a文件,然后添加到UseLibrary
然后在viewcontroller中使用
#import "ViewController.h"
#import "MakeLibrary.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
MakeLibrary *m = [[MakeLibrary alloc]init];
}
@end
切换target运行,输出
MakeLibrary init
FirstObj init
如果用MachOView打开这个.a,去查看这个MakeLibrary.o的符号表,我们会看到一个叫FirstObj的外部符号
当UseLibrary编译的时候,在静态链接的过程中会把这个符号的地址给重定位.
所以如果删除UseLibrary中的FirstObj这个类,就会在编译时报下面这个错:
clang报错:在libMakeLibrary.a里的x86_64里的MakeLibrary.o中,引用了一个OBJC_CLASS$_FirstObj符号,这个符号没能成功重定位,也就是没找到.
2.静态库引用静态库
再创建一个target叫做MakeLibrary2,这个MakeLibrary2会引用MakeLibrary
#import "MakeLibrary2.h"
#import "MakeLibrary/MakeLibrary.h"
@implementation MakeLibrary2
- (instancetype)init{
if(self = [super init]){
NSLog(@"MakeLibrary2 init");
MakeLibrary *m = [[MakeLibrary alloc]init];
}
return self;
}
@end
生成之后,在Viewcontroller中使用MakeLibrary2
#import "ViewController.h"
#import "MakeLibrary2.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
MakeLibrary2 *m = [[MakeLibrary2 alloc]init];
}
@end
运行输出
MakeLibrary2 init
MakeLibrary init
FirstObj init
3.静态库和静态库(或主程序)包含重复符号
创建一个新的target叫MakeLibrary3, 然后直接把libMakeLibrary.a扔进去
#import "MakeLibrary3.h"
#import "MakeLibrary.h"
@implementation MakeLibrary3
- (instancetype)init{
if(self = [super init]){
NSLog(@"MakeLibrary2 init");
MakeLibrary *m = [[MakeLibrary alloc]init];
}
return self;
}
@end
制作出libMakeLibrary3.a,查看结构,可以看到.a无非就是.o文件的集合,就算套再多层,静态链接也会重组结构,合并成新的Mach-o,把.o排列在一起.
需要注意的是,制作静态库时引入其他静态库,如果没有引用头文件,是不会被打包进来的.或者在build phases -> Link Binary With Libraries中添加需要一起打包的静态库
然后把libMakeLibrary3.a放到UseLibrary中使用
#import "ViewController.h"
#import "MakeLibrary3.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
MakeLibrary3 *m = [[MakeLibrary3 alloc]init];
}
@end
运行输出
MakeLibrary2 init
MakeLibrary init
FirstObj init
现在UseLibrary其实有两份MakeLibrary.o,运行起来没有问题,也没有警告,
即便两个相同的.o有着不同的实现,运行的时候总是只有一个被链接.
注意这个引用了重复的.a还能正常运行,前提是ohter link flags什么都不设置.
ld自行进行了取舍,假如设置了-ObjC,或者-all_load或者-force_load,就会报错 duplicated symbols
-all_load一般不考虑;
添加-ObjC报错重复,如果是和主target里的文件重复,那么可以考虑去掉-ObjC,给静态库单独一个个的添加-force_load,如果两个静态库互相重复,那就麻烦了,如果二选一-force_load不能解决,那就只能重新制作静态库.
4.动态库包含静态库
制作动态库的时候,包含一个静态库,动态库生成需要经过静态链接,最终静态库会被拷贝合并进去,叫做吸附性.
需要设置link Binary with libraries
5.动态库和静态库(或主程序)包含重复符号
在主程序和动态 Framework中分别创建一个类都叫FirstObj
//主程序的FirstObj
- (instancetype)init{
if(self = [super init]){
NSLog(@"common FirstObj init");
}
return self;
}
//动态Framework的FirstObj
- (instancetype)init{
if(self = [super init]){
NSLog(@"Make2 FirstObj init");
}
return self;
}
//在主程序分别调用
#import <MakeFramework2/MakeLibrary2.h>
#import "FirstObj.h"
///这个是Framework的类,init里面会调用[[FirstObj alloc]init];
MakeLibrary2 *m = [[MakeLibrary2 alloc]init];
FirstObj *f = [[FirstObj alloc]init];
运行:
控制台会报警告:One of the two will be used. Which one is undefined.
然后输出
MakeLibrary2 init
Make2 FirstObj init
common FirstObj init
说明分别创建了不同的FirstObj,这是因为动态库的独立性,有独立的命名空间,动态库会使用动态库的本地符号.
另外与头文件无关,即便是下面这样
#import <MakeFramework2/FirstObj.h>,
FirstObj *f = [[FirstObj alloc]init];
输出的依然是 common FirstObj init
6.动态库和动态库包含重复符号
安装makeFramework2的文件给makeFramework来一遍
运行输出
One of the two will be used. Which one is undefined.
MakeLibrary init
Make FirstObj init
MakeLibrary2 init
Make2 FirstObj init
即便与上一条相同,会警告,但是各用个的.
7.在动态库中引用动态库
在makeFramework2中拖一个makeFramework进去
设置上embed和link
然后运行,生成成功
可以看到在MakeFramework2.framework中被包含了一个framework文件夹
放到工程里是可以正常运行,相当于分别引入两个framework.
四.embedded framework和tbd
framework可以理解为框架,是一个文件夹,是mach-o+资源文件+描述文件+签名
在info.plist中可以设置当前framework的版本,兼容的最低系统版本等等描述信息,资源文件可以直接放进来,也可以打包成bundle.
framework可以是static Library 也可以是shared Library;
通过Xcode -> Framework-> dynamic Library生成的叫做 embedded framework,它也是通过dyld在运行时动态链接,但是和系统的Framework不同.系统的Framework才是真正只有一份在硬盘或内存中.
使用是需要设置为embed
.tbd文件是文本文件,其中包含架构信息,以及在真实运行时候二进制所在的位置,以及包含了动态库的符号表还有类的一些信息,目的是不把动态库放在Xcode中;
系统tbd的位置:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks
这里面全是.tbd,而tbd文本里面指向了这样一个路径/System/Library/Frameworks/,这里面仍然没有mach-O,真正的二进制文件被合成一个很大的文件,在手机上保存在/System/Library/Caches/com.apple.dyld中.
五:XCFramework
为什么模拟器会报错找不到第三方库
在M1芯片之前,模拟器是x86_64架构(更早的是i386),真机是arm系列,armv7,armv7s,arm64,arm64e这些.
制作静态库的时候,分别制作了模拟器和真机,Intel芯片制作的模拟器版本静态库是x86_64,而M1是arm64,
通常会把模拟器和真机的用lipo命令合在一起,并且合并的时候相同的架构是需要去掉一个的,通常我们去掉模拟器的arm64,因为去掉真机的,手机就不能编译了.
现在M1的mac制作的模拟器版本是arm64,真机也有arm64,就无法合并了,本来m1的模拟器就需要arm64的架构,现在删掉模拟器的arm64导致m1上的模拟器无法使用这个合并后的静态库.
那为什么真机的arm64模拟器不能用呢,因为实际上这俩还是不一样的.
现在有两种解决方案,一是把模拟器和Xcode设置为Rosetta模式(只是运行app project的话,只设置模拟器也行),这样的话模拟器就使用x86_64的架构.
同时记得在Excluded Architecture中添加arm64.
第二种方案就是wwdc19新增的XCFramework.
还是合并,只不过使用新的指令,这样的话就不用删除模拟器的arm64了
1.合并.a
xcodebuild -create-xcframework -library <path> [-headers <path>] [-library <path> [-headers <path>]...] -output <path>
xcodebuild -create-xcframework -library youpath/TestFramework.a -headers youpath/TestFramework -library youpath/TestFramework.a -headers youpath/TestFramework -output youpath/TestFramework.xcframework
2.合并.framework
xcodebuild -create-xcframework -framework <path> [-framework <path>...] -output <path>
xcodebuild -create-xcframework -framework Release-iphoneos/TestFramework.framework -framework Release-iphonesimulator/TestFramework.framework -output TestFramework.xcframework