如何将多个内部Swift模块打包为单个第三方Framework?
解决iOS多模块合并为单个Framework交付的问题
这个场景我太熟了——动态Framework的依赖链在第三方真机环境里直接断了,改静态又踩了链接错误和体积暴涨的坑,对吧?别慌,咱们一步步来搞定:
一、先把所有内部模块转成静态库(正确姿势)
之前改静态出问题,大概率是依赖没理顺或者打包方式不对。按下面的步骤来:
- 修改每个模块的Mach-O类型:打开Common、Product1、Product2这些模块的Target设置,在
Build Settings里找到Mach-O Type,改成Static Library。注意:Xcode里的静态Framework其实是静态库的包装,选这个选项后,咱们后续可以把它打包成标准Framework格式。 - 理顺模块依赖:
- Product1依赖Common?那就在Product1的
Build Phases里,Target Dependencies加Common(保证编译顺序正确),Link Binary With Libraries里也加上Common的静态库/Framework。 - 你的ProductFramework3rdParty要把所有内部模块都拉进来,同样在
Link Binary With Libraries里加Common、Product1、Product2,Target Dependencies也同步加上。
- Product1依赖Common?那就在Product1的
- 解决链接错误:
链接报错无非两种:重复符号或者缺失符号。重复符号的话,检查是不是有公共文件被多个模块引用了,赶紧移到Common里统一管理;缺失符号的话,确保所有依赖的系统库(比如Foundation)都加上,要是用了Swift,记得开启Always Embed Swift Standard Libraries。
二、用Aggregate Target打包成单个静态Framework
手动合并太麻烦,用Xcode的Aggregate Target写个脚本自动化搞定:
- 新建Aggregate Target:点击项目文件 →
Add Target→ 选Other下面的Aggregate,命名成PackageFramework就行。 - 添加打包脚本:打开这个Aggregate的
Build Phases,点+加个New Run Script Phase,把下面的脚本粘进去(记得替换成你的实际模块名):# 输出目录,可自定义 OUTPUT_DIR="${SRCROOT}/FrameworkOutput" mkdir -p "${OUTPUT_DIR}" # 你的内部模块列表 MODULES=("Common" "Product1" "Product2" "ProductFramework3rdParty") # 先合并每个模块的模拟器和真机版本 for MODULE in "${MODULES[@]}"; do SIMULATOR_LIB="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${MODULE}.framework/${MODULE}" DEVICE_LIB="${BUILD_DIR}/${CONFIGURATION}-iphoneos/${MODULE}.framework/${MODULE}" lipo -create "${SIMULATOR_LIB}" "${DEVICE_LIB}" -output "${OUTPUT_DIR}/${MODULE}" done # 构建最终的Framework结构 FINAL_FRAMEWORK="${OUTPUT_DIR}/ProductFramework3rdParty.framework" mkdir -p "${FINAL_FRAMEWORK}/Versions/A/Headers" mkdir -p "${FINAL_FRAMEWORK}/Versions/A/Resources" # 把所有静态库合并成一个 lipo -create "${OUTPUT_DIR}/Common" "${OUTPUT_DIR}/Product1" "${OUTPUT_DIR}/Product2" "${OUTPUT_DIR}/ProductFramework3rdParty" -output "${FINAL_FRAMEWORK}/Versions/A/ProductFramework3rdParty" # 复制公开头文件——把所有需要给第三方用的头文件都复制过来 cp -R "${SRCROOT}/ProductFramework3rdParty/Headers/"* "${FINAL_FRAMEWORK}/Versions/A/Headers/" # 如果Common、Product1里有第三方需要的头文件,也得复制 cp -R "${SRCROOT}/Common/Headers/"* "${FINAL_FRAMEWORK}/Versions/A/Headers/" cp -R "${SRCROOT}/Product1/Headers/"* "${FINAL_FRAMEWORK}/Versions/A/Headers/" # 创建Framework需要的符号链接 ln -s A "${FINAL_FRAMEWORK}/Versions/Current" ln -s Versions/Current/Headers "${FINAL_FRAMEWORK}/Headers" ln -s Versions/Current/ProductFramework3rdParty "${FINAL_FRAMEWORK}/ProductFramework3rdParty" ln -s Versions/Current/Resources "${FINAL_FRAMEWORK}/Resources" # 复制资源文件(如果你的框架有图片、xib这些资源的话) cp -R "${SRCROOT}/ProductFramework3rdParty/Resources/"* "${FINAL_FRAMEWORK}/Versions/A/Resources/" - 执行打包:选择
PackageFramework这个Target,先编译模拟器版本,再编译真机版本,或者直接用xcodebuild命令行一次性编译。之后去你设置的FrameworkOutput目录里找合并好的单个Framework就行。
三、把体积降下来
100MB确实太夸张了,咱们来剪一剪:
- 裁剪架构:如果不需要支持老设备,在
Build Settings的Architectures里只保留arm64(真机)和x86_64(模拟器),打包后也可以用lipo -remove命令删掉不需要的架构。 - 开启代码裁剪:
Build Settings里把Dead Code Stripping设为YES,Strip Debug Symbols During Copy设为YES,Strip Style选Non-Global Symbols,让Xcode自动删掉没用的代码。 - 开启Bitcode:如果第三方支持Bitcode,把
Enable Bitcode设为YES,这样苹果分发的时候还会再优化体积,打包出来的Framework也会小很多。
四、验证交付的Framework
把生成的Framework拖到第三方示例App里:
- 在App的
Build Phases的Link Binary With Libraries里添加这个Framework。 - 静态Framework不需要Embed,所以在
Embed Frameworks里别加它,只需要Link就行。 - 跑真机测试,应该就不会再出现dyld的错误了。
内容的提问来源于stack exchange,提问作者Miro
相关产品推荐
相关产品推荐

