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 <AFNetworking/AFNetworking.h>添加到实现文件(.m文件)时,我们需要将-framework AFNetworking标志添加到链接器。#import <AFNetworking.h>会使用自动链接,从代码中的导入语句派生库链接器的 flag 标志,直接使用framework / Library关于自动链接的原理,可以参考文章。

//这种会报错#import <AFNetworking/AFNetworking.h>
#import <AFNetworking.h>

如果想在动态库A中,将静态库B的符号隐藏,可以使用-hidden-l<library name>隐藏静态库的全局符号。隐藏在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<library name>-weak_framework <framework name>指定动态库为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"
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容