链接一个库的三要素

  • 头文件
  • 库文件所在位置
  • 库文件名称

1 动态库链接动态库(App->动态库A->动态库B)

1.1 脚本手动创建动态库并链接动态

1.1.1 编译链接生成动态库B(TestExampleLog.framework)

image.png

1.1.2 动态库A(TestExample.framework)编译链接动态库B(TestExampleLog.framework)

  1. clang -target x86_64-apple-macos10.15 \
  2. -fobjc-arc \
  3. -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
  4. -I./Headers \
  5. -I./Frameworks/TestExampleLog.framework/Headers \
  6. -c TestExample.m -o TestExample.o
  7. clang -dynamiclib \
  8. -target x86_64-apple-macos10.15 \
  9. -fobjc-arc \
  10. -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
  11. -F./Frameworks \
  12. -framework TestExampleLog \
  13. TestExample.o -o TestExample
  14. echo "-------DYLIB---------"
  15. otool -l TestExample | grep 'DYLIB' -A 3
  16. echo "-------ID---------"
  17. otool -l TestExample | grep 'ID' -A 3

动态库B的路径name不完整
image.png

1.1.3 test.m编译链接动态库B

image.png

1.1.4 运行test,报错

image.png

1.1.5 解决方法:修改动态库A的rpath或者copy动态库B到指定路径中。

方式一:修改动态库A的rpath

  • 在1.1.1编译链接生成动态库B 是 给链接器传入-install_name(-install_name中的@rpath,谁链接动态库B,谁提供@rpath的路径)

image.png

@rpath,由于是动态库A去链接动态库B,所有@rpath是动态库A的路径,谁来链接动态库,@rpath就是代表谁的路径

  • 链接生成动态A的时候,需要告诉编译器两个参数,一个是-install_name来确定自己的路径(-install_name中的@rpath,谁链接动态库A,谁提供@rpath的路径);另一参数是A去链接B,需要指定B的@rpath ,即修改动态库A的rpath为动态库B的install_name之前的绝对路径

image.png
在test.m编译链接动态库A需要告诉编译器链接A的@rpath
image.png

  • 总结
    1. 动态库B的路径 = 动态库A的@rpath + 动态库B的install_name
    2. 动态库A的路径 = test的@rpath + 动态库A的install_name

    方式二 copy动态库B到动态库A的Frameworks/目录下

    Cocoapods向App中导入动态库B,在导入的过程中,Cocoapods会帮助我们将动态库B拷贝到App的Frameworks/目录下:
    静态库和动态库(三) - 图8

方式三 通过脚本进行手动Copy,将动态库B拷贝到App的Frameworks/目录下:

静态库和动态库(三) - 图9

静态库和动态库(三) - 图10

1.1.6 动态库的反向依赖

动态库的反向依赖,因为符号的作用空间问题,那么在运行时,动态库可以动态找到App的符号。所以只要在编译期间不报符号未定义的错误即可。可以通过-U <符号>,来指定一个符号的是动态查找符号。
静态库和动态库(三) - 图11

同时可以在App里面通过指定-upward-l<library name>或者-upward_framework <framework name>来标志这是一个向上引用的动态库。

1.1.7 App想使用动态库B的方法

  1. #import <Foundation/Foundation.h>
  2. #import "TestExample.h"
  3. #import "TestExampleLog.h"
  4. int main(){
  5. NSLog(@"testApp----");
  6. TestExample *manager = [TestExample new];
  7. [manager lg_test: nil];
  8. TestExampleLog *log = [TestExampleLog new];
  9. NSLog(@"testApp----%@",log);
  10. return 0;
  11. }

如果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

  1. clang -target x86_64-apple-macos10.15 \
  2. -fobjc-arc \
  3. -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
  4. -I./Headers \
  5. -I./Frameworks/TestExampleLog.framework/Headers \
  6. -c TestExample.m -o TestExample.o
  7. clang -dynamiclib \
  8. -target x86_64-apple-macos10.15 \
  9. -fobjc-arc \
  10. -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
  11. -Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample \
  12. -Xlinker -rpath -Xlinker @loader_path/Frameworks \
  13. -Xlinker -reexport_framework -Xlinker TestExampleLog \
  14. -F./Frameworks \
  15. -framework TestExampleLog \
  16. TestExample.o -o TestExample
  17. echo "-------DYLIB---------"
  18. otool -l TestExample | grep 'DYLIB' -A 5
  19. echo "-------ID---------"
  20. otool -l TestExample | grep 'ID' -A 5
  21. #echo "-------RPATH---------"
  22. #otool -l TestExample | grep 'RPATH' -A 5

在编译test.m链接动态库时添加指定头文件-I./Frameworks/TestExample.framework/Frameworks/TestExampleLog.framework/Header

  1. clang -target x86_64-apple-macos10.15 \
  2. -fobjc-arc \
  3. -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
  4. -I./Frameworks/TestExample.framework/Headers \
  5. -I./Frameworks/TestExample.framework/Frameworks/TestExampleLog.framework/Headers \
  6. -c test.m -o test.o
  7. clang \
  8. -target x86_64-apple-macos10.15 \
  9. -fobjc-arc \
  10. -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
  11. -F./Frameworks \
  12. -Xlinker -rpath -Xlinker @executable_path/Frameworks \
  13. -framework TestExample \
  14. test.o -o test
  15. echo "-------DYLIB---------"
  16. otool -l TestExample | grep 'DYLIB' -A 5
  17. echo "-------ID---------"
  18. otool -l TestExample | grep 'ID' -A 5
  19. #echo "-------RPATH---------"
  20. #otool -l TestExample | grep 'RPATH' -A 5

⚠️ 重新导出,不会将符号放到导出符号表中,而是重新生成了一个LC_REEXPORT_DYLIB

image.png

静态库和动态库(三) - 图13

注意⚠️:因为Cocoapods自动生成的xcconfig文件包含了-framework AFNetworking参数,要想重新将AFNetworking指定为-reexport_framework,需将其放在$(inherited)前面。

App里面设置:
静态库和动态库(三) - 图14

2 动态库链接静态库(App -> 动态库A -> 静态库B)

因为动态库A生成的过程中在链接静态库B时,会把静态库B所有代码都链接进去。所以编译链接都不会报错。
如果动态库A不想把静态库B的导出符号(全局符号)暴露出去,可以通过-hidden-l隐藏静态库的全局符号。

静态库和动态库(三) - 图15

注意⚠️:因为Cocoapods自动生成的xcconfig文件包含了-l”AFNetworking”参数,要想重新将AFNetworking指定为-hidden-l,需将其放在$(inherited)前面。

App里面设置:
静态库和动态库(三) - 图16

3 静态库链接静态库(App -> 静态库A -> 静态库B)

静态库A生成时,只保存了静态库B的头文件信息或者静态库B的名称(Auto-Link)。App链接静态库A后,会把静态库A所有代码都链接进去。但是并不知道静态库B的位置和名称。

方式一

通过Cocoapods将静态库B引入到App内:

静态库和动态库(三) - 图17

方式二

手动配置静态库B的位置和名称:

静态库和动态库(三) - 图18

4 静态库链接动态库(App -> 静态库A -> 动态库B)

静态库A生成时,只保存了动态库B的名称(Auto-Link)。App链接静态库A后,会把静态库A所有代码都链接进去。但是App(他链接的动态库B)并不知道动态库B的位置,也没有提供rpath。
保存的@rpath与动态库B的install_name组合的路径下:

  1. 动态库B的路径 = Apprpath + 动态库Binstall_name

方式一

通过Cocoapods将动态库B引入到App内:

静态库和动态库(三) - 图19

方式二

配置rpath并通过脚本将动态库B引入到App内:

静态库和动态库(三) - 图20

静态库和动态库(三) - 图21

5 弱引用动态库

标记为weak imports,允许在运行时不链接该库。例如,正常情况下,动态库链接一个库文件时,如果库文件不在指定的路径中,会报image not found。通过-weak-l或者-weak_framework 指定为库为weak imports,如果在运行时找不到该库,会自动将该库的地址及内容设置为0。

静态库和动态库(三) - 图22

image.png

6 将工程archive打包成分发包

  • 编译成模拟器架构
    1. xcodebuild archive -project 'SYTimer.xcodeproj' \
    2. -scheme 'SYTimer' \
    3. -configuration Release \
    4. -destination 'generic/platform=iOS Simulator' \
    5. -archivePath '../archives/SYTimer.framework-iphonesimulator.xcarchive' \
    6. SKIP_INSTALL=NO
    image.png

    SKIP_INSTALL=NO表示将编译的产物拷贝到path/Products/Library/Frameworks/SYTimer.framework

  • 编译成真机架构
    1. xcodebuild archive -project 'SYTimer.xcodeproj' \
    2. -scheme 'SYTimer' \
    3. -configuration Release \
    4. -destination 'generic/platform=iOS' \
    5. -archivePath '../archives/SYTimer.framework-iphoneos.xcarchive' \
    6. SKIP_INSTALL=NO

    fat二进制:多个架构打包到一起,不能出现重复的架构 多个动态库合并,是将多个动态库放到一起,含有多个mach-header

SYTimer编译Build Settings中指定的iOS架构为arm64和armv7
image.png
查看SYTimer.framework-iphone.xcarchive文件和SYTimer.framework-iphonesimulator.xcarchive的架构
image.png
image.png
合并动态库(相同架构不能合并)

  1. 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

image.png
从模拟器架构中提取x86_64架构的二进制文件

  1. lipo -output SYTimer-x86_64 -extract x86_64 ../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer

image.png
再合并生成胖二进制

  1. 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

  1. xcodebuild -create-xcframework \
  2. -framework '../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
  3. -framework '../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
  4. -output 'SYTimer.xcframework'

打包xcframework添加debug-symbols

  1. xcodebuild -create-xcframework \
  2. -framework '../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
  3. -debug-symbols '/Users/mac/Downloads/archiver/archives/SYTimer.framework-iphoneos.xcarchive/BCSymbolMaps/003CE6BF-8F95-3C57-B8B8-4E428A6279C4.bcsymbolmap' \
  4. -debug-symbols '/Users/mac/Downloads/archiver/archives/SYTimer.framework-iphoneos.xcarchive/BCSymbolMaps/65E02E73-3E25-3BD8-84AC-91192C4B0D82.bcsymbolmap' \
  5. -debug-symbols '/Users/mac/Downloads/archiver/archives/SYTimer.framework-iphoneos.xcarchive/dSYMs/SYTimer.framework.dSYM' \
  6. -framework '../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
  7. -debug-symbols '/Users/mac/Downloads/archiver/archives/SYTimer.framework-iphonesimulator.xcarchive/dSYMs/SYTimer.framework.dSYM' \
  8. -output 'SYTimer.xcframework'

⚠️ 注意: -debug-symbols 后的path 需是绝对路径,整个目录中避免有中文字符