iOS 编译与链接四:静态库和动态库

一:静态链接库

1.制作.a文件
.a文件即 static library

library和framework

创建library,project的配置对最终产物.a文件基本没有影响,只需要关注target的配置

copy files会在product文件夹生成include文件夹,里面就是cpoy file


copy files

Header会在product文件夹生成一个usr/local/include/文件夹,里面是暴露的.h文件


Header

除了默认的架构,还可以添加其他架构,不过需要对应上iOS版本号


架构支持

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)

Fat Binary

.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放进来


image.png

然后引用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


build phases -> Header

添加Link Binary With Libraries


build phases -> Link Binary With Libraries

然后运行,生成的framework里面是这样的


image.png

用machOView查看这个MakeFramework,和.a没区别,是.o文件的集合,这里只有一个x86_64架构,所以没有Fat Binary.


image.png

引入到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里没有这个类.


没找到MakeLibrary

把MakeLibrary2 *m = [[MakeLibrary2 alloc]init];这行注释,就可以运行成功,得到framework,可以看到它被系统标记为可执行文件


image.png

然后用MachOView查看这个可执行文件,它是一个Shared Libray, file type是dylib,可以看到已经没有.o了


image.png

2.制作一个动态的.a

创建target选择static Library时,这项默认是static Library,但是也可以手动改成dynamic Library


image.png

运行,然后machOView查看.a文件,果真是dylib,和上面的framework一样,甚至如果把.a扩展名删掉,会被标记为可执行文件


image.png

image.png

实际上生成的就是一个动态库,可以直接使用
  • 总结

在生成时:动态链接库在生成时会执行静态链接,把目标文件,包括依赖的静态库里的目标文件合并到dylib中,这个过程还包括符号解析和重定位,这时根据不同的依赖关系和链接策略进行检查并抛出异常.后面具体说明.

在使用时:静态链接时会把引用自动态链接库的符号进行标记,设置占位地址,真正的符号重定位需要在运行时由dyld来做,因此动态库是不是真的定义了这个符号,需要运行时才知道.

embedded framework会被复制到.app里面的framework文件夹中.

三.依赖关系

build setting -> linking可以配置链接的选项.这些就会影响依赖关系.
比如静态链接时,链接器ld64会进行dead code striping,此时就需要解析依赖关系,然后剥离未被引用的符号.
比如ohter link flags.

制作静态库的时候,静态库本身经常需要访问外部文件,比如依赖别的静态库,或者直接引用外部的文件.

首先创建一个新项目,创建时选择app target,叫做UseLibrary用来使用静态库
然后再添加一个target,选择Static Library 叫做makeLibrary用来生成静态库.


image.png

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


image.png

然后在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

image.png

生成之后,在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扔进去

image.png

#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中添加需要一起打包的静态库


Link Binary With Libraries
ilibMakeLibrary3.a

然后把libMakeLibrary3.a放到UseLibrary中使用


image.png
#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

image.png

-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进去

image.png

设置上embed和link
image.png

然后运行,生成成功
可以看到在MakeFramework2.framework中被包含了一个framework文件夹
image.png

放到工程里是可以正常运行,相当于分别引入两个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


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.


image.png

第二种方案就是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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容