本文涉及的知识点:
- 动态库的创建&使用
- 编译各个架构通用的Framework
- 动态库剥离
1. 创建一个Framework
项目&使用
1.1 创建步骤
Xcode
版本为12.2
-
操作步骤:
Create a new Xcode Project -> iOS -> Framework & Library -> Framework -> next
-
在项目里添加代码,比如
HDLogTool
,在DylibFramework
中添加代码:#import <HDLogTool.h>
,如下:
-
开放共用代码的头文件。把需要暴露给外部的头文件拖拽到
Target -> Build Phases -> Headers -> Public
中
接下来,选择一个模拟器进行编译(后面会讲到支持各个CPU架构的编译)
1.2 使用它
在当前项目中添加一个
Target
:File-> New -> Target -> iOS -> Application -> App
-
在
Demo
中导入动态库
接下来,你就可以在
Demo
中使用动态库的代码了,并且应该能正确编译执行,如果修改了动态库中的代码,直接运行demo
就能看到效果
编译通用架构的 Framework
1.1 在其他项目中使用Framework
- 在上节我通过添加子项目的方式,可以实现动态库的使用,但是在实际开发中,Framework需要单独拿出来提供给开发者们使用。
- 在
Products -> DylibFramework.framework -> Show in finder
找到打包的好的库文件。 - 通过命令查看
Framework
支持的架构类型为x86_64
# lipo 是mac系统自带的一个工具,可以在终端直接敲入查看使用时的一些参数
lipo -info DylibFramework
- 拖入新建的工程
DylibTest
中,在General -> Frameworks,Libararies,and Embedded content
中把导入的库设置为Embed & Sign
(老版Xcode中,Embed是单独设置的)
Embed & Sign
可以理解为:在build
时需要拷贝进App Bundle
里的库,这是相对苹果官方的动态库而言的,官方提供的系统库是不需要拷贝进App Bundle
中的。Sign
代表签名,导入到App Bundle
中的库在打包上传时需要签名操作
- 在项目中使用后,运行成功
-
当我们选择运行在真机上时,会报错,如图:
Xcode编译Framework时针对模拟器和真机打的包是不一样的,支持的平台自然也不一样
1.2 编译各个平台的Framework
1.2.1 合并
- 编译生成各个平台的动态库。选中任一模拟器编译一下,选中
Any iOS Device
编译一下,此时,在沙盒中已经生成了两种类型的动态库 - 使用
Xcode
中的Aggregat
来完成Framework的合并 - 在
DylibFramework
项目中创建一个Aggregat Target
:File -> New -> Target -> Other -> Aggregat
- 在
Aggregat
的target
中新建脚本
- 脚本如下(作用:合并支持模拟器和真机的动态库,并生成在根目录的
Products
中):
if [ "${ACTION}" = "build" ]
then
INSTALL_DIR=${SRCROOT}/Products/${PROJECT_NAME}.framework
DEVICE_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework
SIMULATOR_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework
if [ -d "${INSTALL_DIR}" ]x86_64
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
#ditto "${DEVICE_DIR}/Headers" "${INSTALL_DIR}/Headers"
# 使用lipo命令将其合并成一个通用framework
# 最后将生成的通用framework放置在工程根目录下新建的Products目录下
lipo -create "${DEVICE_DIR}/${PROJECT_NAME}" "${SIMULATOR_DIR}/${PROJECT_NAME}" -output "${INSTALL_DIR}/${PROJECT_NAME}"
#open "${DEVICE_DIR}"
# 打开生成的文件夹
open "${SRCROOT}/Products"
fi
-
此时我们可以看到动态库也合并完成。通过命令可以看到支持的CUP类型:
- 我们把动态库拖入项目中,再次运行,又出错了
QAQ
# 因为后来没复现,所以这里只把报错信息发出来
/DylibTest.xcodeproj Building for iOS Simulator, but the linked and embedded framework 'DylibFramework.framework' was built for iOS + iOS Simulator.
- 解决办法:
Build Settings -> Validate Workspace -> YES
参考链接:
https://stackoverflow.com/questions/63267897/building-for-ios-simulator-but-the-linked-framework-framework-was-built
- 修改为
YES
后,完美运行。 - 由于
iOS
编译的特殊性,为了方便调试,很多SDK
将i386(iOS14模拟器已不支持)、x86_64、armv7、arm64
几个平台合并到一起了。上传app store
时,需要将i386、x86_64
两个平台的库删除,否则无法正常提交审核。
1.2.2 剥离
- 主要是通过
lipo
命令对库进行一些操作 - 我们先拷贝一份现在的动态库
DylibFramework.framework
- 从动态库中剥离出
armv7
和arm64
的库
# armv7
lipo DylibFramework.framework/DylibFramework -thin armv7 -output DylibFramework_armv7
# arm64
lipo DylibFramework.framework/DylibFramework -thin arm64 -output DylibFramework_arm64
- 将
armv7
和arm64
的库打包
lipo -create DylibFramework_armv7 DylibFramework_arm64 -output DylibFramework
- 修改文件名
# 把生成的新的库替换掉备份里面的库
mv DylibFramework DylibFramework.framework/
- 把
DylibFramework.framework
重新导入项目中,就可以使用了
思考:
- 编译通用版本的动态库,操作相对复杂。可以考虑写一些脚本来支持。
- 制作动态库时需要根据实际的业务来进行代码抽取,建议先私有化,再考虑编译为动态库
- 设计合理的动态库更新方案相对复杂,需要契合现有开发模式。
- 要编写完善的项目开发文档,供后续开发人员使用。
- 目前
CocoaPods
支持以动态库的形式集成第三方,同时CocoaPods
可以看到代码,便于调试,所以可以优先考虑使用CocoaPods
来做。