ios动态库

871 阅读5分钟

前序篇章ios静态库

链接动态库

本地准备如下图所示 image.png image.png 那么接下来使用clong来编译test.m 为目标文件

//可以不指定语言,会自动识别语言
clang -target x86_64-apple-macos10.15 \ 
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-I./AFNetworking \
-c test.m -o test.o

接下来链接AFNetworking这个动态库

clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-L./AFNetworking \
-lAFNetworking \
test.o -o test

此时已经生成了test可执行文件,运行 ./test,出现下面的错误 image.png 相信大家在使用动态库的时候,都碰到过类似的问题.

怎样生成一个动态库

本地准备如下 image.png

  1. 将test.m生成目标文件
clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-I./dylib \
-c test.m -o test.o
  1. 将TestExample.m 生成目标文件
 clang target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-c TestExample.m -o TestExample.o
  1. 生成libTestExample.dylib
clang -dynamiclib \                    \
> -target x86_64-apple-macos10.15 \
> -fobjc-arc \
> -isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
> TestExample.o -o libTestExample.dylib
  1. test.o链接libTestExample.dylib
clang -target x86_64-apple-macos10.15 \\
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-L./dylib \
-lTestExample \
test.o -o test
  1. 运行test可执行文件,之后报同样的错误 image.png

什么是动态库

动态库是一个链接编译的最终产物,而静态库只是目标文件的合集,那么就意味着静态库能够链接变成一个动态库。那接下来就先编译testExample成为一个静态库,然后链接成为一个动态库。

  1. TestExample.o 编译成为一个静态库
libtool \
-static -arch_only x86_64 \
TestExample.o -o libTestExample.a
  1. 链接libTestExample.a成为一个动态库,使用ld
ld -dylib -arch x86_64 \
> -macosx_version_min 10.15 \
> -syslibroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
> -lsystem -framework Foundation \
> libTestExample.a -o libTestExample.dylib
  1. 链接生成可执行文件test
clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-L./dylib \
-lTestExample \
test.o -o test

在生成可执行文件的时候,出现了下面的错误 image.png 那我们用objdump --macho -exports-trie libTestExample.dylib命令查看是否有导出符号,输出如下 image.png 没有任何导出符号,原因是因为连接器默认的参数是-noall_load,并没有加-all_load的参数,导致链接的时候,认为没有用到的符号,就会被strip掉

ld -dylib -arch x86_64 -all_load \
-macosx_version_min 10.15 \
-syslibroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-lsystem -framework Foundation \
libTestExample.a -o libTestExample.dylib

再次查看导出符号表,输出如下 image.png 再次链接TestExample.dylib生成test可执行文件,没有报任何错误。 运行test还是报image not found的错误。

TDB

tdb全称是text-based stub libraries,本质上就是一个YAML描述的文本文件。他的作用是用于记录动态库的一些信息,包括到哦出的符号,动态库的架构信息,动态库的依赖信息。用于避免在真机开发过程中直接使用传统的dylib,对于真机来说,由于动态库都在设备上,在xcode上使用基于tbd格式的伪framework可以大大减少xcode的大小。

tdb 格式可以通过xcode打开查看里面的内容 image.png 当编译的时候链接一个库,只需要知道符号的位置就可以了,不需要知道源码。而在运行的时候,动态库是动态链接的,在运行的过程中是由dyld动态加载动态库的。

链接动态库过程

上述链接动态库之后,运行的时候一直报image not found的错误,接下来就看下dyld 加载动态库的流程 image.png 在可执行文件中的mach-o header中保存这load 动态库的路径,当运行的时候可执行文件的时候,没有找到这个动态库的时候,就会报这个错误. 可以用otool -l test查看test这个mach-o文件的内容 image.png 可以看到其中一个LC_LOAD_DYLIB load一个动态库的路径为@rpath/TestExample.framework/TestExample.

那我们查看下TestExample.framework这个里面的LC_ID_DYLIB动态库(LC_ID_DYLIB标记了动态库的访问路径)使用otool -l TestExample | grep 'ID' -A 5来查看(-A/-B 5 向下/向上多显示5行) image.png 那么LC_ID_DYLIB怎么改动name这个字段呢(改变动态库访问路径), 可以通过install_name_tool来改变.

install_name_tool - change dynamic shared library install names

image.png 再次编译test.m生成可执行文件,执行otool -l test, 输出如下, image.png 发现TestFramework的name已经变了。动态库保存路径,在可执行文件在链接的时候,将动态库的保存路径存到可执行文件的mach-o中。 当指定了正确的路径之后,再次运行test,没有任何输出错误。

那么我们能够生成这个动态库的时候,就指定保存路径吗?当然可以, 我们可以通过连接器参数来指定-install_name /Users/meilinli/Documents/动态库与framework/Frameworks/TestExample.framework/TestExample

clang -dynamiclib  \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-install_name /Users/meilinli/Document/动态库与framework/Frameworks/TestExample.framework/TestExample \
TestExample.o -o TestExample

这个install_name 对应的就是xcode build 中的Dynamic Library install Name image.png 但是-install_name给定的是绝对路径,如果目录更改了,我们就得改编译代码,所以在此引@rpath.

@rpath

@rpath : Runpath seach Paths,dyld 搜索路径,运行时@rpath 指示dyld按顺序搜索路径列表,以找到动态库,@rpath 保存一个或多个路径的变量. @rpath 是由链接的主体提供的,比如A.framework是被B这个可执行文件链接的,那么@rpath就由B提供的。

对TestExample指定保存路径, 命令如下

install_name_tool -id @rpath/TestExample.framework/TestExample TestExample

再次查看 image.png 或者可以通过clang传递link参数

clang -dynamiclib  \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample \
TestExample.o -o TestExample

-Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample指定动态库保存路径

那接下来test这个可执行文件需要提供@rpath, 通过之前的命令,链接生成test 可执行文件之后,查看test 可执行文件mach-o的load command 是否有rpath, 但是最终显示并没有rpath 这个字段,注意查找的时候是大小写敏感的, 查找RPATH, 可以通过如下命令,在test mach-o 中添加rpath

install_name_tool -add_rpath /Users/meilinli/Documents/动态库 与framework/Frameworks test

但是add rpath 里面也是绝对路径,系统提供了以下两个path

  • @executable_path: 表示可执行程序所在的目录,解析为可执行文件的绝对路径
  • @loader_path: 表示被加载的Mach-O所在的目录,每次加载时,都可能被设置为不同的路径,由上层指定. 如果A.framework 被B.framework link,那么@loader_path 就指的是B所在的目录的绝对路径.

所以上面的命令可以替换成

install_name_tool -add_rpath @executable_path test

rpath 对应的build setting中Runpath Search Paths image.png 在xconfig文件中的为LD_RUNPATH_SEARCH_PATHS

@loader_path

有如下业务场景,TestExample.framework 需要link TestExampleLog.framework image.png 那么在编译生成TestExampleLog.framework 代码如下

clang -dynamiclib  \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-Xlinker -install_name -Xlinker @rpath/TestExampleLog.framework/TestExampleLog \
TestExampleLog.o -o TestExampleLog

那么TestExample.frameworklink TestExampleLog.framework的时候则需要指定@rpath@loader_path/Frameworks,

clang -dynamiclib  \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-F./Frameworks \
-framework TestExampleLog \
-Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample \
-Xlinker -rpath -Xlinker @loader_path/Frameworks \
TestExample.o -o TestExample