iOS开发进阶七:静态库和动态库实战

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使用动态库LGFrameworkLGFramework调用AFNetworking

直接运行出错.jpg

运行项目,出现image not found。出错的原因是什么呢?链接一个库的三要素:头文件,库文件所在目录,库文件名称。此时找不到动态库AFN,出错的因素是库文件所在目录配置出错。项目中查找库文件所在目录的配置是RPATH,项目中配置的是

LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks' ${FRAMEWORK_SEARCH_PATHS}

我们可以查看生成的产物的目录结构:


AFNetwork跟APK处于同一目录.png
在LGApp包中并未包含AFNetwork.png

再查看LGApp包中是否包含AFN动态库,发现并没有存在。原因就是动态库在通过pod install安装的时候,AFN动态库并没有拷贝的Apk中来。

所以解决思路就是让LGFramework可以找到AFNetworking动态库。

三种解决方案:

  1. 在本地模拟器调试时,可以修改Pods-LGFramework.debug.xcconfig中的LD_RUNPATH_SEARCH_PATHS,只要能找到AFNetworking动态库就行。但是AFNetworking动态库并没有存在与APK包中,那么真机运行就会出错。
  2. 修改pod文件中的配置,安装时会将AFNetworking动态库添加到LGApp的target中。
  3. 通过脚本将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
模拟器运行时将动态库RPATH重定向后能运行成功.png

真机运行还是会出错。

解决方案二

修改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'

原因是在项目配置中有遗留配置没有删除,删除即可:


解决错误.png

通过脚本将AFNetworking动态库拷贝到LGApp中。那么需要在LGApp工程中添加配置,我们直接调用方法二中pod为我们生成的脚本。

将脚本放到LGApp根目录后,调用成功.png

pod自动生成脚本实现copy.png
LGApp安装包中存在了AFN动态库.png

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
反向依赖成功.png

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->动->静调用成功.png

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"

你可能感兴趣的:(iOS开发进阶七:静态库和动态库实战)