- 链接一个库的三要素
- 1 动态库链接动态库(App->动态库A->动态库B)
- 2 动态库链接静态库(App -> 动态库A -> 静态库B)
- 3 静态库链接静态库(App -> 静态库A -> 静态库B)
- 4 静态库链接动态库(App -> 静态库A -> 动态库B)
- 5 弱引用动态库
- 6 将工程archive打包成分发包
- 7 脚本打包xcframework
链接一个库的三要素
- 头文件
- 库文件所在位置
- 库文件名称
1 动态库链接动态库(App->动态库A->动态库B)
1.1 脚本手动创建动态库并链接动态
1.1.1 编译链接生成动态库B(TestExampleLog.framework)
1.1.2 动态库A(TestExample.framework)编译链接动态库B(TestExampleLog.framework)
clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
-I./Headers \
-I./Frameworks/TestExampleLog.framework/Headers \
-c TestExample.m -o TestExample.o
clang -dynamiclib \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
-F./Frameworks \
-framework TestExampleLog \
TestExample.o -o TestExample
echo "-------DYLIB---------"
otool -l TestExample | grep 'DYLIB' -A 3
echo "-------ID---------"
otool -l TestExample | grep 'ID' -A 3
1.1.3 test.m编译链接动态库B
1.1.4 运行test,报错
1.1.5 解决方法:修改动态库A的rpath或者copy动态库B到指定路径中。
方式一:修改动态库A的rpath
- 在1.1.1编译链接生成动态库B 是 给链接器传入-install_name(-install_name中的@rpath,谁链接动态库B,谁提供@rpath的路径)
@rpath,由于是动态库A去链接动态库B,所有@rpath是动态库A的路径,谁来链接动态库,@rpath就是代表谁的路径
- 链接生成动态A的时候,需要告诉编译器两个参数,一个是-install_name来确定自己的路径(-install_name中的@rpath,谁链接动态库A,谁提供@rpath的路径);另一参数是A去链接B,需要指定B的@rpath ,即修改动态库A的rpath为动态库B的install_name之前的绝对路径
在test.m编译链接动态库A需要告诉编译器链接A的@rpath
- 总结
动态库B的路径 = 动态库A的@rpath + 动态库B的install_name
动态库A的路径 = test的@rpath + 动态库A的install_name
方式二 copy动态库B到动态库A的Frameworks/目录下
Cocoapods向App中导入动态库B,在导入的过程中,Cocoapods会帮助我们将动态库B拷贝到App的Frameworks/目录下:
方式三 通过脚本进行手动Copy,将动态库B拷贝到App的Frameworks/目录下:
1.1.6 动态库的反向依赖
动态库的反向依赖,因为符号的作用空间问题,那么在运行时,动态库可以动态找到App的符号。所以只要在编译期间不报符号未定义的错误即可。可以通过-U <符号>,来指定一个符号的是动态查找符号。
同时可以在
App
里面通过指定-upward-l<library name>
或者-upward_framework <framework name>
来标志这是一个向上引用的动态库。
1.1.7 App想使用动态库B的方法
#import <Foundation/Foundation.h>
#import "TestExample.h"
#import "TestExampleLog.h"
int main(){
NSLog(@"testApp----");
TestExample *manager = [TestExample new];
[manager lg_test: nil];
TestExampleLog *log = [TestExampleLog new];
NSLog(@"testApp----%@",log);
return 0;
}
如果App想使用动态库B的方法,第一种方式是让App直接链接动态库B。第二种方式是通过-reexport_framework(重新导出.framework)或者-reexport_l (重新导出.a/.dylib)重新将动态库B通过动态库A导出给App。
在编译A动态链接动态库B的时候,重新导出动态库B的符号表( -Xlinker -reexport_framework -Xlinker TestExampleLog
)
查看
-reexport_framework
的功能,man ld
clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
-I./Headers \
-I./Frameworks/TestExampleLog.framework/Headers \
-c TestExample.m -o TestExample.o
clang -dynamiclib \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
-Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample \
-Xlinker -rpath -Xlinker @loader_path/Frameworks \
-Xlinker -reexport_framework -Xlinker TestExampleLog \
-F./Frameworks \
-framework TestExampleLog \
TestExample.o -o TestExample
echo "-------DYLIB---------"
otool -l TestExample | grep 'DYLIB' -A 5
echo "-------ID---------"
otool -l TestExample | grep 'ID' -A 5
#echo "-------RPATH---------"
#otool -l TestExample | grep 'RPATH' -A 5
在编译test.m链接动态库时添加指定头文件-I./Frameworks/TestExample.framework/Frameworks/TestExampleLog.framework/Header
clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
-I./Frameworks/TestExample.framework/Headers \
-I./Frameworks/TestExample.framework/Frameworks/TestExampleLog.framework/Headers \
-c test.m -o test.o
clang \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
-F./Frameworks \
-Xlinker -rpath -Xlinker @executable_path/Frameworks \
-framework TestExample \
test.o -o test
echo "-------DYLIB---------"
otool -l TestExample | grep 'DYLIB' -A 5
echo "-------ID---------"
otool -l TestExample | grep 'ID' -A 5
#echo "-------RPATH---------"
#otool -l TestExample | grep 'RPATH' -A 5
⚠️ 重新导出,不会将符号放到导出符号表中,而是重新生成了一个LC_REEXPORT_DYLIB
注意⚠️:因为
Cocoapods
自动生成的xcconfig
文件包含了-framework AFNetworking
参数,要想重新将AFNetworking
指定为-reexport_framework
,需将其放在$(inherited)
前面。
App
里面设置:
2 动态库链接静态库(App -> 动态库A -> 静态库B)
因为动态库A生成的过程中在链接静态库B时,会把静态库B所有代码都链接进去。所以编译链接都不会报错。
如果动态库A不想把静态库B的导出符号(全局符号)暴露出去,可以通过-hidden-l
注意⚠️:因为Cocoapods自动生成的xcconfig文件包含了-l”AFNetworking”参数,要想重新将AFNetworking指定为-hidden-l,需将其放在$(inherited)前面。
App里面设置:
3 静态库链接静态库(App -> 静态库A -> 静态库B)
静态库A生成时,只保存了静态库B的头文件信息或者静态库B的名称(Auto-Link)。App链接静态库A后,会把静态库A所有代码都链接进去。但是并不知道静态库B的位置和名称。
方式一
通过Cocoapods将静态库B引入到App内:
方式二
手动配置静态库B的位置和名称:
4 静态库链接动态库(App -> 静态库A -> 动态库B)
静态库A生成时,只保存了动态库B的名称(Auto-Link)。App链接静态库A后,会把静态库A所有代码都链接进去。但是App(他链接的动态库B)并不知道动态库B的位置,也没有提供rpath。
保存的@rpath与动态库B的install_name组合的路径下:
动态库B的路径 = App的rpath + 动态库B的install_name
方式一
通过Cocoapods将动态库B引入到App内:
方式二
配置rpath并通过脚本将动态库B引入到App内:
5 弱引用动态库
标记为weak imports,允许在运行时不链接该库。例如,正常情况下,动态库链接一个库文件时,如果库文件不在指定的路径中,会报image not found。通过-weak-l
6 将工程archive打包成分发包
- 编译成模拟器架构
xcodebuild archive -project 'SYTimer.xcodeproj' \
-scheme 'SYTimer' \
-configuration Release \
-destination 'generic/platform=iOS Simulator' \
-archivePath '../archives/SYTimer.framework-iphonesimulator.xcarchive' \
SKIP_INSTALL=NO
SKIP_INSTALL=NO表示将编译的产物拷贝到path/Products/Library/Frameworks/SYTimer.framework
- 编译成真机架构
xcodebuild archive -project 'SYTimer.xcodeproj' \
-scheme 'SYTimer' \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath '../archives/SYTimer.framework-iphoneos.xcarchive' \
SKIP_INSTALL=NO
fat二进制:多个架构打包到一起,不能出现重复的架构 多个动态库合并,是将多个动态库放到一起,含有多个mach-header
SYTimer编译Build Settings中指定的iOS架构为arm64和armv7
查看SYTimer.framework-iphone.xcarchive文件和SYTimer.framework-iphonesimulator.xcarchive的架构
合并动态库(相同架构不能合并)
lipo -output SYTimer -create ../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer ../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer
从模拟器架构中提取x86_64架构的二进制文件
lipo -output SYTimer-x86_64 -extract x86_64 ../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer
再合并生成胖二进制
lipo -output SYTimer -create SYTimer-x86_64 ../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer
XCFramework和传统的Framework相比 1.可以用单个.xcframework文件提供多个平台的分发二进制文件; 2.与Fat Header相比,可以按照平台划分,可以包含不同架构的不同平台的文件 3.在使用时,不需要再通过脚本剥离不需要的架构体系
7 脚本打包xcframework
xcodebuild -create-xcframework \
-framework '../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-framework '../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-output 'SYTimer.xcframework'
打包xcframework添加debug-symbols
xcodebuild -create-xcframework \
-framework '../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-debug-symbols '/Users/mac/Downloads/archiver/archives/SYTimer.framework-iphoneos.xcarchive/BCSymbolMaps/003CE6BF-8F95-3C57-B8B8-4E428A6279C4.bcsymbolmap' \
-debug-symbols '/Users/mac/Downloads/archiver/archives/SYTimer.framework-iphoneos.xcarchive/BCSymbolMaps/65E02E73-3E25-3BD8-84AC-91192C4B0D82.bcsymbolmap' \
-debug-symbols '/Users/mac/Downloads/archiver/archives/SYTimer.framework-iphoneos.xcarchive/dSYMs/SYTimer.framework.dSYM' \
-framework '../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-debug-symbols '/Users/mac/Downloads/archiver/archives/SYTimer.framework-iphonesimulator.xcarchive/dSYMs/SYTimer.framework.dSYM' \
-output 'SYTimer.xcframework'
⚠️ 注意: -debug-symbols 后的path 需是绝对路径,整个目录中避免有中文字符