最近开发SDK时在项目中使用静态库的时候都会有两个版本,一个用于模拟器,一个用于真机,这样就比较麻烦。为了模拟器与真机之间切换调试的方便,需要制作通用版的Framework交叉包。
根据Build时选择的机器类型,会分为模拟器Framework和真机Framework,两者是不能混用的。此时可以通过配置一个Run Script,在Script中使用lipo命令来合并两个版本的Framework,重新生成一个新的Framework,这个Framework将同时支持在模拟器和真机上运行。
1、检查Framework包信息
这个两个目录下包含了生成的Framework文件夹,第一个是真机、第二个是模拟器(当前使用的是Debug模式,也可以使用Release模式)。按照预期:真机的包是显示armv7 arm64 架构的,模拟器的包是显示x86_64架构的。
现在我们分别对这两个目录中的Framework进行检测:
1)、Debug-iphoneos(真机)目录下的Framework:
MiniPC:~ lijl$ lipo -info /Users/lijl/Library/Developer/Xcode/DerivedData/SwiftCommon-dyhotzllcfgqjibahyytumthguvh/Build/Products/Debug-iphoneos/SwiftCommon.framework/SwiftCommon
Architectures in the fat file: /Users/lijl/Library/Developer/Xcode/DerivedData/SwiftCommon-dyhotzllcfgqjibahyytumthguvh/Build/Products/Debug-iphoneos/SwiftCommon.framework/SwiftCommon are: armv7 arm64
MiniPC:~ lijl$
//注:Framework是文件夹,命令再往下一层,使用SDK的二进制文件
真机Framework的结果是:armv7 arm64。可见,如果把这个Framework运行在模拟器中,肯定会报错,因为模拟器架构是x86_64的。
2)、Debug-iphonesimulator(模拟器)目录下的Framework:
MiniPC:~ lijl$ lipo -info /Users/lijl/Library/Developer/Xcode/DerivedData/SwiftCommon-dyhotzllcfgqjibahyytumthguvh/Build/Products/Debug-iphonesimulator/SwiftCommon.framework/SwiftCommon
Non-fat file: /Users/lijl/Library/Developer/Xcode/DerivedData/SwiftCommon-dyhotzllcfgqjibahyytumthguvh/Build/Products/Debug-iphonesimulator/SwiftCommon.framework/SwiftCommon is architecture: x86_64
MiniPC:~ lijl$
模拟器Framework的结果是x86_64。可见这个库只能运行在模拟器中,真机设备是无法运行的。
2、创建脚本打交叉包
接下来,我们要对这2个库(其实是Framework目录下2个可执行文件)进行合并。
在项目的Build Phases中,新建一个Run Script,输入下面内容:
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}" ]
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
#ditto "${DEVICE_DIR}/Headers" "${INSTALL_DIR}/Headers"
lipo -create "${DEVICE_DIR}/${PROJECT_NAME}" "${SIMULATOR_DIR}/${PROJECT_NAME}" -output "${INSTALL_DIR}/${PROJECT_NAME}"
#open "${DEVICE_DIR}"
#open "${SRCROOT}/Products"
fi
经过很多测试,下面这个修改后的Run Script会更加通用,不会出错,唯一报错的原因是对应目录下的包没有生成,这段shell代码其实很简单,前面一大部分是申明一些目录路径,最后使用lipo命令进行合并操作。
3、创建聚合Target生成的交叉包
1、创建target,选择Other下的Aggregate
2、嵌入脚本。选中刚刚创建的Aggregate,然后选中右侧的Build Phases,点击左下方加号,选择New Run Script Phase。
3、新建脚本:
# define output folder environment variable
CONFIGURATION=Release
UNIVERSAL_OUTPUTFOLDER_NATIVE=${SRCROOT}/${CONFIGURATION}-build/${PROJECT_NAME}.framework
#build support native API
# Step 1. Build Device and Simulator versions
cd "${SRCROOT}/.."
xcodebuild build -workspace ${PROJECT_NAME}.xcworkspace -scheme ${PROJECT_NAME} ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION_RN} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}"
xcodebuild build -workspace ${PROJECT_NAME}.xcworkspace -scheme ${PROJECT_NAME} -configuration ${CONFIGURATION} -sdk iphonesimulator VALID_ARCHS="i386 x86_64" BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}"
# make sure the output directory exists
rm -rf "${SRCROOT}/${CONFIGURATION}-build"
mkdir -p "${SRCROOT}/${CONFIGURATION}-build"
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/" "${UNIVERSAL_OUTPUTFOLDER_NATIVE}/"
# Step 2. Create universal binary file using lipo
lipo -create "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" -output "${UNIVERSAL_OUTPUTFOLDER_NATIVE}/${PROJECT_NAME}"
# Last touch. copy the header files. Just for convenience
补充更新,兼容xcode13:
# Type a script or drag a script file from your workspace to insert its path.
# 1、定义输出变量
### 定义workspace名称
WORKSPACE= xxx1
### 定义输出SDK名称
SDKTARGET=xxxSDK
### 定义输出资源包名称
BUNDLETARGET=xxxResources
### 定义编译模式
CONFIGURATION=Release
### 定义输出文件夹
PRODUCTS_OUTPUTFOLDER=${SRCROOT}/../Products
# 2、 先清理 TARGETS
#xcodebuild clean -target ${SDKTARGET}
#xcodebuild clean -target ${BUNDLETARGET}
cd "${SRCROOT}/.."
# 3、 执行编译SDK TARGETS
xcodebuild -workspace ${WORKSPACE}.xcworkspace -scheme ${SDKTARGET} ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" -UseModernBuildSystem=NO
xcodebuild -workspace ${WORKSPACE}.xcworkspace -scheme ${SDKTARGET} -configuration ${CONFIGURATION} -sdk iphonesimulator VALID_ARCHS="i386 x86_64" BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" -UseModernBuildSystem=NO
# 4、 执行编译资源 TARGETS
xcodebuild -workspace ${WORKSPACE}.xcworkspace -scheme ${BUNDLETARGET} ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" -UseModernBuildSystem=NO
xcodebuild -workspace ${WORKSPACE}.xcworkspace -scheme ${BUNDLETARGET} -configuration ${CONFIGURATION} -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" -UseModernBuildSystem=NO
# 5、 清空输出指定文件夹、创建新文件夹
rm -rf "${PRODUCTS_OUTPUTFOLDER}"
mkdir -p "${PRODUCTS_OUTPUTFOLDER}"
# 6、 将编译好的SDK和资源BUNDLE拷贝到指定输出文件夹
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${SDKTARGET}.framework" "${PRODUCTS_OUTPUTFOLDER}/"
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${BUNDLETARGET}.bundle" "${PRODUCTS_OUTPUTFOLDER}/"
rm "${PRODUCTS_OUTPUTFOLDER}/${BUNDLETARGET}.bundle/${BUNDLETARGET}"
rm "${PRODUCTS_OUTPUTFOLDER}/${BUNDLETARGET}.bundle/Info.plist"
# 7、 合成真机和摸机器指令包
lipo -create "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${SDKTARGET}.framework/${SDKTARGET}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${SDKTARGET}.framework/${SDKTARGET}" -output "${PRODUCTS_OUTPUTFOLDER}/${SDKTARGET}.framework/${SDKTARGET}"
# 8、 打开输出文件夹
open ${PRODUCTS_OUTPUTFOLDER}
4、验证脚本生成的交叉包
现在,当项目再次Build时,就会在项目目录下面出现Products目录,里面包含了合并后的Framework文件夹。使用lipo -info来检测下:
1、打开终端,进入到你的FrameWork,cd xxxx/xxx.framework
2、查看架构支持,lipo -info xxxx/xxx.framework/xxx (注意:xxx是你的FrameWork名称)
MiniPC:~ lijl$ lipo -info /Users/lijl/Documents/Self\ Project/Cocoa\ Touch\ Framework/SwiftCommon/Products/SwiftCommon.framework/SwiftCommon
运行之后:
Architectures in the fat file: /Users/lijl/Documents/Self Project/Cocoa Touch Framework/SwiftCommon/Products/SwiftCommon.framework/SwiftCommon are: x86_64 armv7 arm64
MiniPC:~ lijl$
信息显示成功的集成了 x86_64 armv7 arm64 ,这样就生成了真机与模拟器通用的交叉包
附指令集:
设备的CPU架构(指令集):
模拟器:
iPhone 4s~5: i386
iPhone 5s~最新: x86_64
真机(iOS设备):
armv6: iPhone、iPhone 2、iPhone 3G、iPod Touch(第一代)、iPod Touch(第二代)
armv7: iPhone 3Gs、iPhone 4、iPhone 4s、iPad、iPad 2
armv7s: iPhone 5、iPhone 5c
arm64: iPhone 5s、iPhone 6、iPhone 6 Plus、iPhone 6s、iPhone 6s Plus、iPad Air、iPad Air2、iPad mini2、iPad mini3,以及最新的机型。