一:靜態(tài)鏈接庫(kù)
1.制作.a文件
.a文件即 static library
創(chuàng)建library,project的配置對(duì)最終產(chǎn)物.a文件基本沒(méi)有影響,只需要關(guān)注target的配置
copy files會(huì)在product文件夾生成include文件夾,里面就是cpoy file
Header會(huì)在product文件夾生成一個(gè)usr/local/include/文件夾,里面是暴露的.h文件
除了默認(rèn)的架構(gòu),還可以添加其他架構(gòu),不過(guò)需要對(duì)應(yīng)上iOS版本號(hào)
接下來(lái)運(yùn)行,運(yùn)行出來(lái)的有四種product:
Debug-iphoneos
Release-iphoneos
Debug-iphonesimulator
Release-iphonesimulator
我們需要考慮2個(gè)問(wèn)題,
第一是使用靜態(tài)庫(kù)的時(shí)候通常并不區(qū)分debug和release,這要看具體需求,如果debug和release的配置不同,則需要分別處理;
第二是包的體積大小,實(shí)質(zhì)就是這個(gè)靜態(tài)庫(kù)支持的架構(gòu)是不是都需要的.比支持iOS12以上的情況下,ipa里的這個(gè)靜態(tài)庫(kù)只需要arm64就行了,但是模擬器需要考慮mac的架構(gòu),并且模擬器不需要關(guān)心包體積.
因此要結(jié)合實(shí)際情況合并和拆分架構(gòu).
有相同架構(gòu)的包是不能直接合并的,要把其中一個(gè)去除掉.
lipo -info (絕對(duì)路徑或相對(duì)路徑) 查看架構(gòu)
lipo -output (新文件絕對(duì)路徑或相對(duì)路徑) -remove (架構(gòu)) (舊文件絕對(duì)路徑或相對(duì)路徑) 移除架構(gòu)
lipo -output (新文件絕對(duì)路徑或相對(duì)路徑) -create (舊文件絕對(duì)路徑或相對(duì)路徑1) (舊文件絕對(duì)路徑或相對(duì)路徑2) 合并架構(gòu)
最終生成一個(gè)包含armv7 armv7s i386 x86_64 arm64 的.a
Architectures in the fat file: TestStatic.a are: armv7 armv7s i386 x86_64 arm64
2..a的特性
用machOView看一下這個(gè).a,(關(guān)于Mach-O)
靜態(tài)鏈接庫(kù)指的是參與靜態(tài)鏈接,通常.a文件就是.o的集合,是編譯的產(chǎn)物,并非鏈接的產(chǎn)物;
因此本身是不經(jīng)過(guò)靜態(tài)鏈接的,compile source里有多少個(gè)文件,就有多少個(gè).o;
所以只要能編譯通過(guò),就可以生成靜態(tài)鏈接庫(kù),.h文件不參與編譯,所以只聲明沒(méi)實(shí)現(xiàn)的類(lèi)和方法會(huì)被作為外部符號(hào)引用,等到項(xiàng)目真正鏈接的時(shí)候,再去決議和重定位.
3.制作靜態(tài)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的時(shí)候沒(méi)有引入MakeLibrary.a,也就是說(shuō)MakeLibrary2.a里沒(méi)有MakeLibrary符號(hào).
新建一個(gè)framework的target,在build setting -> linking 中有個(gè)mach-o type, 默認(rèn)是dynamic Library, 改成static Library,然后把MakeLibrary2.a放進(jìn)來(lái)
然后引用makeLibrary2類(lèi)
#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
然后運(yùn)行,生成的framework里面是這樣的
用machOView查看這個(gè)MakeFramework,和.a沒(méi)區(qū)別,是.o文件的集合,這里只有一個(gè)x86_64架構(gòu),所以沒(méi)有Fat Binary.
引入到UseLibrary中,使用
#import "ViewController.h"
#import "FrameClass.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
FrameClass *fc = [[FrameClass alloc]init];
}
@end
運(yùn)行,輸出
FrameClass init
MakeLibrary2 init
MakeLibrary init
Make FirstObj init
- 總結(jié)
再生成時(shí):靜態(tài)鏈接庫(kù)的生成過(guò)程不包含靜態(tài)鏈接,所做的事主要是編譯,而符號(hào)解析重定位都沒(méi)有做,引用的符號(hào)是不是真的存在,是不是有重復(fù)的符號(hào)都不重要,要做的只有編譯,然后把目標(biāo)文件打包起來(lái).最終.a里可能會(huì)有重復(fù)的.o(指的是同一架構(gòu)內(nèi)).
在使用時(shí):在靜態(tài)庫(kù)是參與靜態(tài)鏈接的,靜態(tài)鏈接時(shí)會(huì)合并mach-o,因此對(duì)于主程序來(lái)說(shuō),編譯之后,靜態(tài)庫(kù)里的目標(biāo)文件被復(fù)制并且合并到了主程序的mach-o中,而靜態(tài)庫(kù)本身不會(huì)發(fā)生變化.
用命令行工具libtool可以拆分和組合static Library的.o并生成新的static Library
比如合并
libtool -static -o 輸出文件 staticLibraryA staticLibraryB
二.動(dòng)態(tài)鏈接庫(kù)
1.制作動(dòng)態(tài)framework
動(dòng)態(tài)鏈接庫(kù)經(jīng)過(guò)了靜態(tài)鏈接,已經(jīng)沒(méi)有.o文件在了,會(huì)進(jìn)行符號(hào)解析.
可以驗(yàn)證一下
接下來(lái)運(yùn)行,打算生成framework.結(jié)果報(bào)錯(cuò)了,因?yàn)镸akeLibrary2里引用了MakeLibrary,但是實(shí)際上現(xiàn)在這個(gè)target里沒(méi)有這個(gè)類(lèi).
把MakeLibrary2 *m = [[MakeLibrary2 alloc]init];這行注釋,就可以運(yùn)行成功,得到framework,可以看到它被系統(tǒng)標(biāo)記為可執(zhí)行文件
然后用MachOView查看這個(gè)可執(zhí)行文件,它是一個(gè)Shared Libray, file type是dylib,可以看到已經(jīng)沒(méi)有.o了
2.制作一個(gè)動(dòng)態(tài)的.a
創(chuàng)建target選擇static Library時(shí),這項(xiàng)默認(rèn)是static Library,但是也可以手動(dòng)改成dynamic Library
運(yùn)行,然后machOView查看.a文件,果真是dylib,和上面的framework一樣,甚至如果把.a擴(kuò)展名刪掉,會(huì)被標(biāo)記為可執(zhí)行文件
實(shí)際上生成的就是一個(gè)動(dòng)態(tài)庫(kù),可以直接使用
- 總結(jié)
在生成時(shí):動(dòng)態(tài)鏈接庫(kù)在生成時(shí)會(huì)執(zhí)行靜態(tài)鏈接,把目標(biāo)文件,包括依賴(lài)的靜態(tài)庫(kù)里的目標(biāo)文件合并到dylib中,這個(gè)過(guò)程還包括符號(hào)解析和重定位,這時(shí)根據(jù)不同的依賴(lài)關(guān)系和鏈接策略進(jìn)行檢查并拋出異常.后面具體說(shuō)明.
在使用時(shí):靜態(tài)鏈接時(shí)會(huì)把引用自動(dòng)態(tài)鏈接庫(kù)的符號(hào)進(jìn)行標(biāo)記,設(shè)置占位地址,真正的符號(hào)重定位需要在運(yùn)行時(shí)由dyld來(lái)做,因此動(dòng)態(tài)庫(kù)是不是真的定義了這個(gè)符號(hào),需要運(yùn)行時(shí)才知道.
embedded framework會(huì)被復(fù)制到.app里面的framework文件夾中.
三.依賴(lài)關(guān)系
build setting -> linking可以配置鏈接的選項(xiàng).這些就會(huì)影響依賴(lài)關(guān)系.
比如靜態(tài)鏈接時(shí),鏈接器ld64會(huì)進(jìn)行dead code striping,此時(shí)就需要解析依賴(lài)關(guān)系,然后剝離未被引用的符號(hào).
比如ohter link flags.
制作靜態(tài)庫(kù)的時(shí)候,靜態(tài)庫(kù)本身經(jīng)常需要訪(fǎng)問(wèn)外部文件,比如依賴(lài)別的靜態(tài)庫(kù),或者直接引用外部的文件.
首先創(chuàng)建一個(gè)新項(xiàng)目,創(chuàng)建時(shí)選擇app target,叫做UseLibrary用來(lái)使用靜態(tài)庫(kù)
然后再添加一個(gè)target,選擇Static Library 叫做makeLibrary用來(lái)生成靜態(tài)庫(kù).
1.靜態(tài)庫(kù)引用外部文件
這個(gè)MakeLibrary封裝了一些功能,并且需要引用UseLibrary里的一個(gè)叫做FirstObj的類(lèi).
直接import并且使用,是可以編譯通過(guò)的.
#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
接下來(lái)生成.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運(yùn)行,輸出
MakeLibrary init
FirstObj init
如果用MachOView打開(kāi)這個(gè).a,去查看這個(gè)MakeLibrary.o的符號(hào)表,我們會(huì)看到一個(gè)叫FirstObj的外部符號(hào)
當(dāng)UseLibrary編譯的時(shí)候,在靜態(tài)鏈接的過(guò)程中會(huì)把這個(gè)符號(hào)的地址給重定位.
所以如果刪除UseLibrary中的FirstObj這個(gè)類(lèi),就會(huì)在編譯時(shí)報(bào)下面這個(gè)錯(cuò):
clang報(bào)錯(cuò):在libMakeLibrary.a里的x86_64里的MakeLibrary.o中,引用了一個(gè)OBJC_CLASS$_FirstObj符號(hào),這個(gè)符號(hào)沒(méi)能成功重定位,也就是沒(méi)找到.
2.靜態(tài)庫(kù)引用靜態(tài)庫(kù)
再創(chuàng)建一個(gè)target叫做MakeLibrary2,這個(gè)MakeLibrary2會(huì)引用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
運(yùn)行輸出
MakeLibrary2 init
MakeLibrary init
FirstObj init
3.靜態(tài)庫(kù)和靜態(tài)庫(kù)(或主程序)包含重復(fù)符號(hào)
創(chuàng)建一個(gè)新的target叫MakeLibrary3, 然后直接把libMakeLibrary.a扔進(jìn)去
#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,查看結(jié)構(gòu),可以看到.a無(wú)非就是.o文件的集合,就算套再多層,靜態(tài)鏈接也會(huì)重組結(jié)構(gòu),合并成新的Mach-o,把.o排列在一起.
需要注意的是,制作靜態(tài)庫(kù)時(shí)引入其他靜態(tài)庫(kù),如果沒(méi)有引用頭文件,是不會(huì)被打包進(jìn)來(lái)的.或者在build phases -> Link Binary With Libraries中添加需要一起打包的靜態(tài)庫(kù)
然后把libMakeLibrary3.a放到UseLibrary中使用
#import "ViewController.h"
#import "MakeLibrary3.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
MakeLibrary3 *m = [[MakeLibrary3 alloc]init];
}
@end
運(yùn)行輸出
MakeLibrary2 init
MakeLibrary init
FirstObj init
現(xiàn)在UseLibrary其實(shí)有兩份MakeLibrary.o,運(yùn)行起來(lái)沒(méi)有問(wèn)題,也沒(méi)有警告,
即便兩個(gè)相同的.o有著不同的實(shí)現(xiàn),運(yùn)行的時(shí)候總是只有一個(gè)被鏈接.
注意這個(gè)引用了重復(fù)的.a還能正常運(yùn)行,前提是ohter link flags什么都不設(shè)置.
ld自行進(jìn)行了取舍,假如設(shè)置了-ObjC,或者-all_load或者-force_load,就會(huì)報(bào)錯(cuò) duplicated symbols
-all_load一般不考慮;
添加-ObjC報(bào)錯(cuò)重復(fù),如果是和主target里的文件重復(fù),那么可以考慮去掉-ObjC,給靜態(tài)庫(kù)單獨(dú)一個(gè)個(gè)的添加-force_load,如果兩個(gè)靜態(tài)庫(kù)互相重復(fù),那就麻煩了,如果二選一-force_load不能解決,那就只能重新制作靜態(tài)庫(kù).
4.動(dòng)態(tài)庫(kù)包含靜態(tài)庫(kù)
制作動(dòng)態(tài)庫(kù)的時(shí)候,包含一個(gè)靜態(tài)庫(kù),動(dòng)態(tài)庫(kù)生成需要經(jīng)過(guò)靜態(tài)鏈接,最終靜態(tài)庫(kù)會(huì)被拷貝合并進(jìn)去,叫做吸附性.
需要設(shè)置link Binary with libraries
5.動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)(或主程序)包含重復(fù)符號(hào)
在主程序和動(dòng)態(tài) Framework中分別創(chuàng)建一個(gè)類(lèi)都叫FirstObj
//主程序的FirstObj
- (instancetype)init{
if(self = [super init]){
NSLog(@"common FirstObj init");
}
return self;
}
//動(dòng)態(tài)Framework的FirstObj
- (instancetype)init{
if(self = [super init]){
NSLog(@"Make2 FirstObj init");
}
return self;
}
//在主程序分別調(diào)用
#import <MakeFramework2/MakeLibrary2.h>
#import "FirstObj.h"
///這個(gè)是Framework的類(lèi),init里面會(huì)調(diào)用[[FirstObj alloc]init];
MakeLibrary2 *m = [[MakeLibrary2 alloc]init];
FirstObj *f = [[FirstObj alloc]init];
運(yùn)行:
控制臺(tái)會(huì)報(bào)警告:One of the two will be used. Which one is undefined.
然后輸出
MakeLibrary2 init
Make2 FirstObj init
common FirstObj init
說(shuō)明分別創(chuàng)建了不同的FirstObj,這是因?yàn)閯?dòng)態(tài)庫(kù)的獨(dú)立性,有獨(dú)立的命名空間,動(dòng)態(tài)庫(kù)會(huì)使用動(dòng)態(tài)庫(kù)的本地符號(hào).
另外與頭文件無(wú)關(guān),即便是下面這樣
#import <MakeFramework2/FirstObj.h>,
FirstObj *f = [[FirstObj alloc]init];
輸出的依然是 common FirstObj init
6.動(dòng)態(tài)庫(kù)和動(dòng)態(tài)庫(kù)包含重復(fù)符號(hào)
安裝makeFramework2的文件給makeFramework來(lái)一遍
運(yùn)行輸出
One of the two will be used. Which one is undefined.
MakeLibrary init
Make FirstObj init
MakeLibrary2 init
Make2 FirstObj init
即便與上一條相同,會(huì)警告,但是各用個(gè)的.
7.在動(dòng)態(tài)庫(kù)中引用動(dòng)態(tài)庫(kù)
在makeFramework2中拖一個(gè)makeFramework進(jìn)去
設(shè)置上embed和link
然后運(yùn)行,生成成功
可以看到在MakeFramework2.framework中被包含了一個(gè)framework文件夾
放到工程里是可以正常運(yùn)行,相當(dāng)于分別引入兩個(gè)framework.
四.embedded framework和tbd
framework可以理解為框架,是一個(gè)文件夾,是mach-o+資源文件+描述文件+簽名
在info.plist中可以設(shè)置當(dāng)前framework的版本,兼容的最低系統(tǒng)版本等等描述信息,資源文件可以直接放進(jìn)來(lái),也可以打包成bundle.
framework可以是static Library 也可以是shared Library;
通過(guò)Xcode -> Framework-> dynamic Library生成的叫做 embedded framework,它也是通過(guò)dyld在運(yùn)行時(shí)動(dòng)態(tài)鏈接,但是和系統(tǒng)的Framework不同.系統(tǒng)的Framework才是真正只有一份在硬盤(pán)或內(nèi)存中.
使用是需要設(shè)置為embed
.tbd文件是文本文件,其中包含架構(gòu)信息劫樟,以及在真實(shí)運(yùn)行時(shí)候二進(jìn)制所在的位置,以及包含了動(dòng)態(tài)庫(kù)的符號(hào)表還有類(lèi)的一些信息,目的是不把動(dòng)態(tài)庫(kù)放在Xcode中;
系統(tǒng)tbd的位置:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks
這里面全是.tbd,而tbd文本里面指向了這樣一個(gè)路徑/System/Library/Frameworks/,這里面仍然沒(méi)有mach-O,真正的二進(jìn)制文件被合成一個(gè)很大的文件,在手機(jī)上保存在/System/Library/Caches/com.apple.dyld中.
五:XCFramework
為什么模擬器會(huì)報(bào)錯(cuò)找不到第三方庫(kù)
在M1芯片之前,模擬器是x86_64架構(gòu)(更早的是i386),真機(jī)是arm系列,armv7,armv7s,arm64,arm64e這些.
制作靜態(tài)庫(kù)的時(shí)候,分別制作了模擬器和真機(jī),Intel芯片制作的模擬器版本靜態(tài)庫(kù)是x86_64,而M1是arm64,
通常會(huì)把模擬器和真機(jī)的用lipo命令合在一起,并且合并的時(shí)候相同的架構(gòu)是需要去掉一個(gè)的,通常我們?nèi)サ裟M器的arm64,因?yàn)槿サ粽鏅C(jī)的,手機(jī)就不能編譯了.
現(xiàn)在M1的mac制作的模擬器版本是arm64,真機(jī)也有arm64,就無(wú)法合并了,本來(lái)m1的模擬器就需要arm64的架構(gòu),現(xiàn)在刪掉模擬器的arm64導(dǎo)致m1上的模擬器無(wú)法使用這個(gè)合并后的靜態(tài)庫(kù).
那為什么真機(jī)的arm64模擬器不能用呢,因?yàn)閷?shí)際上這倆還是不一樣的.
現(xiàn)在有兩種解決方案,一是把模擬器和Xcode設(shè)置為Rosetta模式(只是運(yùn)行app project的話(huà),只設(shè)置模擬器也行),這樣的話(huà)模擬器就使用x86_64的架構(gòu).
同時(shí)記得在Excluded Architecture中添加arm64.
第二種方案就是wwdc19新增的XCFramework.
還是合并,只不過(guò)使用新的指令,這樣的話(huà)就不用刪除模擬器的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