APP->动态库A->动态库B
通过Pod方式操作动态库与App与动态库链接时的区别:
- 动态库链接动态库场景,Pod install的时候,不会生成脚本,只会生成XCConfig文件,仅仅只生成链接器的参数,库文件并没有在生成的Framework中。导出Framework需要开发者自己把动态库用到的第三方Framework拷贝到产物Framework中。
- APP链接动态库场景,Pod install的时候,会生成脚本,把动态库拷贝到我们的APP中。
构建测试代码
现有一个Framework
项目,叫LGFramework
,也就是我们的动态库A。用pod给它添加一个AFNetworking
的库,作为动态库B。
target 'LGFramework' do
use_frameworks!
pod 'AFNetworking'
end
再新建一个项目LGApp
,作为我们的主项目。在LGFramework
中Pod Install之后,打开LGFramework.xcworkspace
,然后将LGApp
添加进来,此时形成LGApp
使用动态库LGFramework
,LGFramework
调用AFNetworking
。
运行项目,出现image not found
。出错的原因是什么呢?链接一个库的三要素:头文件,库文件所在目录,库文件名称。此时找不到动态库AFN,出错的因素是库文件所在目录配置出错。项目中查找库文件所在目录的配置是RPATH,项目中配置的是
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks' ${FRAMEWORK_SEARCH_PATHS}
我们可以查看生成的产物的目录结构:
再查看LGApp包中是否包含AFN动态库,发现并没有存在。原因就是动态库在通过pod install
安装的时候,AFN动态库并没有拷贝的Apk中来。
所以解决思路就是让LGFramework
可以找到AFNetworking
动态库。
三种解决方案:
- 在本地模拟器调试时,可以修改
Pods-LGFramework.debug.xcconfig
中的LD_RUNPATH_SEARCH_PATHS
,只要能找到AFNetworking
动态库就行。但是AFNetworking
动态库并没有存在与APK包中,那么真机运行就会出错。- 修改pod文件中的配置,安装时会将
AFNetworking
动态库添加到LGApp的target中。- 通过脚本将
AFNetworking
动态库拷贝到LGApp中。
解决方案一
根据查看apk目录中AFNetwork.framework所在的目录位置,修改xcconfig文件中的rpath,就可以找到AFNetwork动态库的完整路径,修改后再次运行,运行成功。
也可以自己新建一个xcconfig文件,然后在LGFramework
的Project中设置Configurations。
#include "Pods/Target Support Files/Pods-LGFramework/Pods-LGFramework.debug.xcconfig"
LD_RUNPATH_SEARCH_PATHS = $(inherited) ${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking
真机运行还是会出错。
解决方案二
修改pod文件中的配置,安装时会将AFNetworking
动态库添加到LGApp的target中。
workspace './LGFramework.xcworkspace'
target 'LGFramework' do
use_frameworks!
pod 'AFNetworking'
end
target 'LGApp' do
project '../LGApp/LGApp.xcodeproj'
use_frameworks!
pod 'AFNetworking'
end
解决方案三
在一个pod操作多个工程,pod中删除一些库后,出现如下错误:
Showing All Messages
Unable to load contents of file list: '/Target Support Files/Pods-LGApp/Pods-LGApp-frameworks-Debug-input-files.xcfilelist'
原因是在项目配置中有遗留配置没有删除,删除即可:
通过脚本将AFNetworking
动态库拷贝到LGApp中。那么需要在LGApp工程中添加配置,我们直接调用方法二中pod为我们生成的脚本。
App想使用动态库B的方法
如果App想使用动态库B的方法,第一种方式是让App直接链接动态库B。第二种方式是通过-reexport_framework
或者-reexport_l
重新将动态库B通过动态库A导出给App。在LGFramework的xcconfig中设置
OTHER_LDFLAGS = -Xlinker -reexport_framework -Xlinker AFNetworking $(inherited)
因为Cocoapods生成的xcconfig文件包含了-framework AFNetworking
参数,想要将AFNetworking指定为-reexport_framework
,需将其放在$(inherited)前面。
回顾之前动态库中导出指令
clang -dynamiclib \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample \
-Xlinker -rpath -Xlinker @loader_path/Frameworks \
-Xlinker -reexport_framework -Xlinker AFNetworking \
-F./Frameworks \
-framework AFNetworking \
TestExample.o -o TestExample
在LGAPP中使用AFN,除了在LGFramework中设置的OTHER_LDFLAGS ,还需要在LGApp中设置其他两要素。在LGApp的xcconfig写入
HEADER_SEARCH_PATHS = $(inherited) "${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/AFNetworking/AFNetworking.framework/Headers"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/AFNetworking"
动态库的反向依赖
动态库的反向依赖,由于符合的作用空间,在运行时,动态库可以动态找到App的符号。所以只要在编译期间,绕过符号未定义的错误即可。
在LGApp中新建一个类LGAppTest,写一个方法testAction,在LGFramework中引入LGAppTest头文件,调用testAction方法。
在LGFramework的XCConfig中配置
HEADER_SEARCH_PATHS = $(inherited) "${SRCROOT}/../LGApp/LGApp"
OTHER_LDFLAGS = -Xlinker -U -Xlinker _OBJC_CLASS_$_LGAppTest
也可以使用-undefined参数,将动态库中的符号全部标记为动态查找符号。这个方法不推荐。
OTHER_LDFLAGS = -Xlinker -undefined -Xlinker dynamic_lookup
APP->动态库A->静态库B
pod file
中将use_frameworks!
注释,让cocoapod
帮我们以静态库的方式安装AFNetworking
target 'LGFramework' do
# use_frameworks!
pod 'AFNetworking'
end
动态库A在链接静态库B时,会将静态库B中所有的符号都加入到动态库A中,静态库B中所有的导出符号在动态库A中均有效。所以编译链接均不会报错。
查看动态库LGFramework的导出符号:
objdump --macho --exports-trie <动态库路径>
或者如下指令查看所有符号:
objdump --macho --syms <动态库路径>
App中想使用静态库B中方法,因为符号已经存在,所以只需要配置头文件即可:
HEADER_SEARCH_PATHS = "${SRCROOT}/../LGFramework/Pods/Headers/Public/AFNetworking" $(inherited)
但是在App中使用静态库B的方法时,要做如下修改。因为以#import
添加到实现文件(.m文件)时,我们需要将-framework AFNetworking
标志添加到链接器。#import
会使用自动链接,从代码中的导入语句派生库链接器的 flag 标志,直接使用framework / Library
。 关于自动链接的原理,可以参考文章。
//这种会报错#import
#import
如果想在动态库A中,将静态库B的符号隐藏,可以使用-hidden-l
隐藏静态库的全局符号。隐藏在LGApp中使用AFN静态库,会出现找不到符号的错误。
OTHER_LDFLAGS = -Xlinker -hidden-l"AFNetworking" $(inherited)
因为
Cocoapods
自动生成的xcconfig
文件包含了-l"AFNetworking"
参数,要想重新将AFNetworking
指定为-hidden-l
,需将其放在$(inherited)
前面。
APP->静态库A->静态库B
静态库A生成时,只保存了静态库B的头文件信息(Auto-Link),并没有将静态B合并到静态库A中。App链接静态库A后,会把静态库A所有代码都链接进去,但是并不知道静态库B的位置和名称。
两种现象:(1)在静态库A中不使用静态库B的方法,App在链接静态库A时,编译器会进行“死代码剥离”,静态库B在静态库A中头文件信息被剥离掉,不会去查找对应的符号,项目运行正常。(2)在静态库A中使用静态库B的方法,编译直接报错,找不到符号。
对于APP来说,加载了静态库A,会进行合并,相当于静态库A中的代码在App中运行;造成App包含静态库B的头文件,却没有静态库B的符号。
想在静态库A中调用静态库B的解决办法:将静态库B的位置和名称,配置到APP即可:
LIBRARY_SEARCH_PATHS = $(inherited) "${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/AFNetworking"
OTHER_LDFLAGS = $(inherited) -ObjC -l"AFNetworking"
APP直接使用静态库B的办法:静态库B对于App来说是不可见,头文件
,库路径
和库名称
全部未知;所以只能手动设置3要素,也就是将静态库B也用Pod导入到App中。
App -> 静态库A -> 动态库B
静态库A生成时,只保存了动态库B的头文件信息(Auto-Link
)。App链接静态库A后,会把静态库A所有代码都链接进去。但是App并不知道动态库B的位置,也没有提供@rpath
。保存的@rpath
与动态库B的install_name
组合的路径下:
动态库B的路径 = App的rpath + 动态库B的install_name
解决办法:在App中指定头文件路径、Framework所在目录、@rpath路径、通过脚本,将AFNetworking.framework拷贝到App中的Frameworks目录。
弱引用动态库
标记-weak_framework参数,允许在运行时不链接该动态库。
正常情况下,运行时找不到动态库所在位置,程序崩溃并提示image not found
。
使用-weak-l
或-weak_framework
指定动态库为weak imports
。如果在运行时找不到该库,会自动将该库的地址及内容返回NULL。
OTHER_LDFLAGS = $(inherited) -Xlinker -weak_framework -Xlinker "SYTimer"
使用-weak_framework
标记为弱引用动态库,Mach-O
中记录的Load Command
名称不再是LC_LOAD_DYLIB
,变为LC_LOAD_WEAK_DYLIB
静态库代码冲突
两次或多次引入同一个静态库到App中,如果使用-all_load
或-ObjC
参数导入,必然会出现符号重复定义的错误,duplicate symbols for architecture x86_64
。
我们可以使用-force_load
参数,强制链接的静态库AFNetworking;使用-load_hidden
参数,将静态库AFNetworking2的所有符号设置为隐藏。
OTHER_LDFLAGS = $(inherited) -ObjC -l"AFNetworking" -l"AFNetworking2" -Xlinker -force_load -Xlinker "${SRCROOT}/AFNetworking/libAFNetworking.a" -Xlinker -load_hidden -Xlinker "${SRCROOT}/AFNetworking2/libAFNetworking2.a"