美文网首页
iOS开发进阶七:静态库和动态库实战

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

作者: 轰天裂天罗三炮 | 来源:发表于2022-02-17 17:16 被阅读0次

    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"
    

    相关文章

      网友评论

          本文标题:iOS开发进阶七:静态库和动态库实战

          本文链接:https://www.haomeiwen.com/subject/amsslrtx.html