多平台架构进行合并
lipo
我们一般会使用lipo
来合并和划分不同平台的架构,例如对于一个framework
我们想做出可以在真机和模拟器上使用的包时,步骤如下:
- 打包出真机的架构
xcodebuild archive -project 'SDWebImage.xcodeproj' \ -scheme 'SDWebImage' \ -configuration Release \ -destination 'generic/platform=iOS' \ -archivePath '../archives/SDWebImage.framework-iphoneos.xcarchive' \ SKIP_INSTALL=NO
- 打包出模拟器使用的架构
xcodebuild archive -project 'SDWebImage.xcodeproj' \ -scheme 'SDWebImage' \ -configuration Release \ -destination 'generic/platform=iOS Simulator' \ -archivePath '../archives/SDWebImage.framework-iphonesimulator.xcarchive' \ SKIP_INSTALL=NO
- 对真机和模拟机架构的包进行合并,但是这样会发现报错,因为这两个架构中含有相同的架构而无法合并
lipo [真机架构包路径] [模拟器架构包路径] -create -output [合并后的包名]
- 通过提取或者删除相同架构之后再来合并
模拟器架构中删除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
对合并后的文件进行验证
这个方法是可以,但是会比较麻烦,之前有说过苹果官方推出了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'
和传统的framework
相比:
- 可以用单个
.xcframework
文件提供多个平台的分发二进制文件 - 与
Fat Header
相比可以按照平台划分,可以包含相同架构的不同平台的文件 - 在使用时不需要通过脚本去剥离不需要的架构体系
weak_import
在一个项目中想使用framework
,具体如下
要使用链接使用动态库要满足以下条件:
-I
告诉头文件路径-F
动态库的路径-framework
要链接的库@rpath
如果链接的库为空,这里应该如何处理呢?灵活的方案就是对这个动态库使用弱引用 OTHER_LDFLAGS = $(inherited) -Xlinker -weak_framework -Xlinker SDWebImage
当去动态链接这个库时,由于没找到会直接置空,同样,如果没有把这个库设置为弱引用,同时未给出@rpath
,这时会报出Image not Load
的错误,也就是之前说过的那个问题。
同时可以通过命令otool -l weak_import | grep 'DYLIB' -A 5
查看到
动态库&静态库实战
动态库与静态库链接调用的情况分为四种:动态库 --> 动态库
、动态库 --> 静态库
、静态库 --> 动态库
、静态库 --> 静态库
,接下来就分别描述这些情况
动态库 --> 动态库
这里描述的情景是App
调用了动态库A
,动态库A
链接了动态库B
,如果是App
调用了动态库A
是没有问题的,但是动态库A
调用动态库B
这里会出现问题。
首先确认一下调用动态库的三要素:头文件
、库文件所在位置
、库文件名称
,这里都是没有问题的,但是运行后会报出之前看过的错误Library not loaded
,这时候发现动态库B
并不在动态库A
中给出的@rpath
和动态库B
自身提供的install_name
组合的路径之下
那么根据之前探究过的结果来看有三种方案来解决:
- 修改
动态库A
给出的@rpath
Cocoapods
向App
中导入动态库B
,在导入的过程中,Cocoapods
会帮助我们将动态库B
拷贝到App
的Frameworks/
目录下- 通过脚本进行动手拷贝,将
动态库B
拷贝到App
的Frameworks/
目录下(具体可参考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
通过脚本拷贝
在使用脚本拷贝之前
拷贝之后
这样就不用像之前一样修改@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"
但是此时会报错
这个错是因为符号的作用空间问题,在编译过程找不到对应符号,但是动态库在运行的过程中可以动态的找到App
中的符号,所以只要在编译期间不报符号未定义的错误即可,根据编译器手册可以看到如下
所以对应如下:
-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 [动态库]
来查看
如果不想App
能直接调用静态库
文件的话可以把静态库
隐藏起来
OTHER_LDFLAGS = -Xlinker -hidden-l"AFNetworking" $(inherited)
静态库 --> 静态库
静态库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
链接静态库
后,会把静态库
的代码都链接进去,但是并不知道动态库
的位置,这种情况和之前分析动态库
链接动态库
的情况一样。
首先这里会报符号找不到的错误:
这里是静态库中引用时的未定义符号错误,正是因为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"
这时编译不会报错了,但是运行后会报很熟悉的错误:
这就可以根据之前的经验来解决这个问题了
可以使用配置App
的@rpath
来解决: LD_RUNPATH_SEARCH_PATHS = $(inherited) '${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/AFNetworking'
配置到AFNetworking
的绝对路径
或者用脚本拷贝到App
目录下