动态库&静态库实际操作

多平台架构进行合并

lipo

我们一般会使用lipo来合并和划分不同平台的架构,例如对于一个framework我们想做出可以在真机和模拟器上使用的包时,步骤如下:

  1. 打包出真机的架构

xcodebuild archive -project 'SDWebImage.xcodeproj' \ -scheme 'SDWebImage' \ -configuration Release \ -destination 'generic/platform=iOS' \ -archivePath '../archives/SDWebImage.framework-iphoneos.xcarchive' \ SKIP_INSTALL=NO

image.png

  1. 打包出模拟器使用的架构

xcodebuild archive -project 'SDWebImage.xcodeproj' \ -scheme 'SDWebImage' \ -configuration Release \ -destination 'generic/platform=iOS Simulator' \ -archivePath '../archives/SDWebImage.framework-iphonesimulator.xcarchive' \ SKIP_INSTALL=NO

image.png

  1. 对真机和模拟机架构的包进行合并,但是这样会发现报错,因为这两个架构中含有相同的架构而无法合并

lipo [真机架构包路径] [模拟器架构包路径] -create -output [合并后的包名] image.png

  1. 通过提取或者删除相同架构之后再来合并

模拟器架构中删除arm64

lipo -output SDWebImage_Simulator_remove_arm64 -remove arm64 ./archives/SDWebImage.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SDWebImage.framework/SDWebImage

再将其和真机架构进行合并 lipo -output SDWebImage_lipo -create [真机架构路径] SDWebImage_Simulator_remove_arm64

对合并后的文件进行验证

image.png

扫描二维码关注公众号,回复: 13472033 查看本文章

这个方法是可以,但是会比较麻烦,之前有说过苹果官方推出了xcframework就直接解决了这个问题

xcframework

xcframework的创建:xcodebuild -create-xcframework \ -framework [模拟器架构下framework] \ -debug-symbols '/my/archives/SDWebImage.framework-iphoneos.xcarchive/BCSymbolMaps/1E5BB7B5-03E1-326B-86AF-8CA228A6FBD0.bcsymbolmap' \ -debug-symbols '/my/archives/SDWebImage.framework-iphoneos.xcarchive/BCSymbolMaps/B2930FAB-BD02-3629-8266-510520CD93D1.bcsymbolmap' \ -debug-symbols '/my/archives/SDWebImage.framework-iphoneos.xcarchive/dSYMs/SDWebImage.framework.dSYM' \ -framework [真机环境下framework] \ -debug-symbols '/my/archives/SDWebImage.framework-iphonesimulator.xcarchive/dSYMs/SDWebImage.framework.dSYM' \ -output 'SDWebImage.xcframework'

image.png

和传统的framework相比:

  • 可以用单个.xcframework文件提供多个平台的分发二进制文件
  • Fat Header相比可以按照平台划分,可以包含相同架构的不同平台的文件
  • 在使用时不需要通过脚本去剥离不需要的架构体系

weak_import

在一个项目中想使用framework,具体如下

image.png

要使用链接使用动态库要满足以下条件:

  • -I告诉头文件路径
  • -F动态库的路径
  • -framework要链接的库
  • @rpath

image.png

如果链接的库为空,这里应该如何处理呢?灵活的方案就是对这个动态库使用弱引用 OTHER_LDFLAGS = $(inherited) -Xlinker -weak_framework -Xlinker SDWebImage

image.png

当去动态链接这个库时,由于没找到会直接置空,同样,如果没有把这个库设置为弱引用,同时未给出@rpath,这时会报出Image not Load的错误,也就是之前说过的那个问题。

同时可以通过命令otool -l weak_import | grep 'DYLIB' -A 5查看到

image.png

动态库&静态库实战

动态库与静态库链接调用的情况分为四种:动态库 --> 动态库动态库 --> 静态库静态库 --> 动态库静态库 --> 静态库,接下来就分别描述这些情况

动态库 --> 动态库

这里描述的情景是App调用了动态库A动态库A链接了动态库B,如果是App调用了动态库A是没有问题的,但是动态库A调用动态库B这里会出现问题。

首先确认一下调用动态库的三要素:头文件库文件所在位置库文件名称,这里都是没有问题的,但是运行后会报出之前看过的错误Library not loaded,这时候发现动态库B并不在动态库A中给出的@rpath动态库B自身提供的install_name组合的路径之下

image.png

那么根据之前探究过的结果来看有三种方案来解决:

  • 修改动态库A给出的@rpath
  • CocoapodsApp中导入动态库B,在导入的过程中,Cocoapods会帮助我们将动态库B拷贝到AppFrameworks/目录下
  • 通过脚本进行动手拷贝,将动态库B拷贝到AppFrameworks/目录下(具体可参考Cocoapods脚本做法)

接下来来演示具体做法

修改动态库A@rpath

通过修改动态库A@rpath直接定位到动态库B的绝对路径

PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

LD_RUNPATH_SEARCH_PATHS = $(inherited) ${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking

image.png

通过脚本拷贝

在使用脚本拷贝之前

image.png

拷贝之后

image.png

这样就不用像之前一样修改@rpath

App 调用 动态库B

在重新导出动态库B的基础上给到三要素即可

通过-reexport_framework\-reexport_l\重新将动态库B通过动态库A导出给App

-Xlinker -reexport_framework -Xlinker AFNetworking

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"

动态库A反向依赖App

首先暴露出头文件HEADER_SEARCH_PATHS = $(inherited) "${SRCROOT}/../LGApp/LGApp"

但是此时会报错

image.png

这个错是因为符号的作用空间问题,在编译过程找不到对应符号,但是动态库在运行的过程中可以动态的找到App中的符号,所以只要在编译期间不报符号未定义的错误即可,根据编译器手册可以看到如下

image.png

所以对应如下:

-Xlinker -U -Xlinker  _OBJC_CLASS_$_LGAppObject

-Xlinker -undefined -Xlinker dynamic_lookup

建议使用第一种,第二种是所有未定义的符号都让它去动态查找,范围会比较大,而且如果使用第二种,那么在编译过程中都不会报未定义符号的错误了。

动态库 --> 静态库

动态库链接静态库会直接把静态库所有的代码都链接进去,所以编译链接都不会报错,而且App可以直接调用静态库的代码,只需要暴露出头文件路径即可

HEADER_SEARCH_PATHS = "${SRCROOT}/../IUFramework/Pods/Headers/Public/AFNetworking" $(inherited)

但是当动态库链接静态库时会把静态库所有代码链接进去,所以动态库的导出符号中会包含静态库代码

objdump --macho --exports-trie [动态库]来查看

image.png

如果不想App能直接调用静态库文件的话可以把静态库隐藏起来

OTHER_LDFLAGS =  -Xlinker -hidden-l"AFNetworking" $(inherited)

image.png

静态库 --> 静态库

静态库A生成时,只保存了静态库B的头文件信息,App链接静态库A后,会把静态库A所有代码都链接进去。但是并不知道静态库B的位置和名称。

解决方法:

  • 通过Cocoapods静态库B引入到App
  • 手动配置静态库B的位置和名称

LIBRARY_SEARCH_PATHS = $(inherited) "${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/AFNetworking"

OTHER_LDFLAGS = $(inherited) -ObjC -l"AFNetworking"

静态库 --> 动态库

静态库在生成时因为Auto-Link仅仅保留了动态库的名称,当App链接静态库后,会把静态库的代码都链接进去,但是并不知道动态库的位置,这种情况和之前分析动态库链接动态库的情况一样。

首先这里会报符号找不到的错误:

image.png

这里是静态库中引用时的未定义符号错误,正是因为App静态库合并之后App并未有动态库的相关信息,三要素里只有动态库的名称因为Auto-Link得以保存,所以还应该提供头文件路径动态库路径

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"

这时编译不会报错了,但是运行后会报很熟悉的错误:

image.png

这就可以根据之前的经验来解决这个问题了

可以使用配置App@rpath来解决: LD_RUNPATH_SEARCH_PATHS = $(inherited) '${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/AFNetworking' 配置到AFNetworking的绝对路径 image.png

或者用脚本拷贝到App目录下 image.png

猜你喜欢

转载自juejin.im/post/7039584669771235336