简介
FatFramework即传统的Framework大包,一个二进制文件同时包含了真机和模拟器在不同架构下的代码,是以前闭源分发常用的形式。
XCFramework本质上是一个文件夹合集,里面包含了真机和模拟器两个子文件夹,再里面才是Framework本体。另外还有一个Info.plist配置文件,在xcode11之后可以用来将当前环境所需的指令集自动对应到指定的Framework。
现在cocoapods上很多闭源库都已经使用XCFramework取代了FatFramework。我们不能落后于时代,必须了解一下这一实现流程。
打包方式
首先在编译阶段,需要确保真机和模拟器的产出包含了所需的archs。
我们参考谷歌的GoogleAppMeasurement.xcframework,打开它的目录,可以看到如下两个文件夹:
1 2
| ios-arm64_armv7 ios-arm64_i386_x86_64-simulator
|
可以看到真机环境所需的archs是arm64
、armv7
,模拟器环境所需的archs是arm64
、i386
、x86_64
。
因为现在M1环境的模拟器arch是arm64
,模拟器需要包含arm64
,这是以前很多教程没提及的。
如果你使用的是x86
环境,在编译模拟器时,需要手动指定archs,否则不会包含arm64
.
顺便一提,一些教程和问题解答会让xcode12和x86的用户在项目配置里设置Excluded Architectures
的模拟器配置项为arm64
。这也会让模拟器编译默认排除arm64
。
我们不需要这么做。
我用的是fastlane构建,不会影响项目本身的配置,fastlane脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| desc "打包SDK" lane :buildSDK do customScheme = "MySDK" xcbuild( workspace: "MySDK.xcworkspace", scheme: customScheme, clean: true, configuration: "Release", xcargs: "-sdk iphonesimulator ARCHS='arm64 x86_64 i386' " )
xcbuild( workspace: "MySDK.xcworkspace", scheme: customScheme, clean: true, configuration: "Release", xcargs: "-sdk iphoneos ARCHS='arm64 armv7' " ) end
|
这样我们在执行fastlane构建时,可以在真机和模拟器下获得各自所需的原始Framework。
接下来是将这两个Framework合并打包。
XCFramework的打包脚本非常简单:
1 2
| xcodebuild -create-xcframework -framework "${DEVICE_DIR}" -framework "${SIMULATOR_DIR}" -output "${Merged_DIR}"
|
前两个参数即真机和模拟器的Framework路径,第三个是产出路径。
传统的FatFramework打包的麻烦之处在于,两个Framework如果包含了重复的arch就会报错,因此需要在合并前,先移除重复的部分。
1 2 3 4 5 6
| cp -r ../dist/${PRODUCT_NAME}/iphoneos/ ${MergePath}
lipo "${SIMULATOR_DIR}/${PRODUCT_NAME}" -remove arm64 -output "${SIMULATOR_DIR}/${PRODUCT_NAME}"
lipo -create "${DEVICE_DIR}/${PRODUCT_NAME}" "${SIMULATOR_DIR}/${PRODUCT_NAME}" -output "${MergePath}${PRODUCT_NAME}.framework/${PRODUCT_NAME}"
|
这样就在产出目录下同时生成了FatFramework和XCFramework。
最后可以使用lipo -info ${Framework二进制文件路径}
来验证。
使用方式
在podspec文件中引入Framework时,直接将以前的.framework改为.xcframework就可以了。
完整示例
完整代码如下,将其保存为post_build.sh。
在项目target的Build Phases的末尾创建一个Run Script Phase并执行该脚本,就可以在fastlane脚本执行完毕后自动打包。
1 2 3 4 5
| echo "run script begin" script_path=${SRCROOT}/post_build.sh chmod 777 $script_path
sh $script_path
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #!/bin/sh
if [ $CONFIGURATION == Release ] then echo 'release build' else echo 'debug build' exit 0 fi
rootPub=../dist/${PRODUCT_NAME}/iphoneos/
if [ $EFFECTIVE_PLATFORM_NAME == -iphonesimulator ] then echo '模拟器' rootPub=../dist/${PRODUCT_NAME}/iphonesimulator/ else echo '真机' fi
rm -rf ${rootPub} mkdir -p ${rootPub}
cp -r ${CODESIGNING_FOLDER_PATH} ${rootPub}
DEVICE_DIR=../dist/${PRODUCT_NAME}/iphoneos/${PRODUCT_NAME}.framework SIMULATOR_DIR=../dist/${PRODUCT_NAME}/iphonesimulator/${PRODUCT_NAME}.framework
if [ ! -d "${DEVICE_DIR}" ] || [ ! -d "${SIMULATOR_DIR}" ] then echo "真机包或模拟包不存在,退出合并" exit 0 fi
MergePath=../dist/${PRODUCT_NAME}/merge/ rm -rf ${MergePath} mkdir -p ${MergePath}
Merged_DIR=../dist/${PRODUCT_NAME}/merge/${PRODUCT_NAME}.xcframework xcodebuild -create-xcframework -framework "${DEVICE_DIR}" -framework "${SIMULATOR_DIR}" -output "${Merged_DIR}" cp -r ../dist/${PRODUCT_NAME}/iphoneos/ ${MergePath} lipo "${SIMULATOR_DIR}/${PRODUCT_NAME}" -remove arm64 -output "${SIMULATOR_DIR}/${PRODUCT_NAME}" lipo -create "${DEVICE_DIR}/${PRODUCT_NAME}" "${SIMULATOR_DIR}/${PRODUCT_NAME}" -output "${MergePath}${PRODUCT_NAME}.framework/${PRODUCT_NAME}"
rm -rf ${DWARF_DSYM_FOLDER_PATH}/${PRODUCT_NAME}.bundle/*.plist rm -rf ${DWARF_DSYM_FOLDER_PATH}/${PRODUCT_NAME}.bundle/_CodeSignature cp -r ${DWARF_DSYM_FOLDER_PATH}/${PRODUCT_NAME}.bundle ${MergePath}
rm -rf ../${PRODUCT_NAME}/Frameworks/* mv ${MergePath}/* ../${PRODUCT_NAME}/Frameworks/ rm -rf ../dist/${PRODUCT_NAME}/
|