背景
这篇文章默认读者是对Carthage有了一定的了解, 如果不了解, 请查看我的前一篇文章 Carthage安装、使用及常见问题解决教程
为什么选择静态库
Carthage默认只能编译成动态库, 但是我们还是选择用静态库,因为如果注入太多动态库,App的启动时间就会变慢,也就是main函数之前动态链接的过程变久了。因此我们这里用Carthage来把动态库编译成静态库的方式解决这个问题。
如果全部是静态库,那么都会被链接到App包里面,这样看起来包会增大,还有一个方案就是,把多个静态库通过一个动态库的工程包裹,包成一个动态库进行链接,这样静态库不会被打入包里面,而且也不会有太多的启动耗时。这里需要注意的是,也不是和无脑合并使用动态库,Apple给我们建议是最多6个动态库是最合适的。
关于静态库合并成动态库需要注意以下:
直接起一个动态库的project,然后把我们需要组合的静态库都拖入Link Binary with Libraries中,为了保证所有的静态库都会被合并,需要在Build Settings的OTHER_LDFLAGS中加入-all-load参数,强制链接所有静态库的全部内容,而不是有时候只链接合并其中一部分而已。
xcframework和framework的区别
xcframework是苹果官⽅推荐的、⽀持的,可以更⽅便的表示⼀个多个平台和架构的分发⼆进制库的格式。需要Xcode11以上⽀持。是为更好的⽀持Mac Catalyst(用于 iPad)和ARM芯⽚的macOS。专⻔在2019年提出的framework的另⼀种先进格式。
XCFramework和传统的framework相⽐的三个优点
1 可以⽤单个.xcframework⽂件提供多个平台的分发⼆进制⽂件。
2 与Fat Header相⽐,可以按照平台划分,可以包含相同架构的不同平台的⽂件。
3 在使⽤时,不需要再通过脚本去剥离不需要的架构体系。
xcframework的目录如下, 里面包含了各个架构对应使用的framework
操作流程
1 创建一个App类型的项目
快捷键command + shift + n, 选择App类型
2 引入第三方库
cd 到项目根目录, 执行下面命令生成Cartfile文件
touch Cartfile
然后执行下面命令打开Cartfile文件
open Cartfile
在Cartfile文件中添加依赖的第三方库
github "SnapKit/SnapKit" ~> 5.0.0
github "scalessec/Toast-Swift" ~> 5.0.1
github "Moya/Moya" ~> 15.0
然后执行下面命令checkout下第三方库的源码
carthage update --no-use-binaries --platform iOS --no-build
该脚本和之前的不同,跟了三个参数,分别代表不用远程编译的预编译二进制,只用本地的源码编译,第二个参数代表架构,第三个代表不进行编译,只要把源码check out即可,编译我们等下用自己的脚本进行编译,这里如果直接编译,那就是默认的动态库
3 编写编译脚本, 生成framework
在项目根目录下执行下面命令, 生成一个脚本文件
touch build-static-carthage.sh
然后选择用文本编辑器或者Xcode打开, 添加以下代码
#!/bin/sh -e
# 用法:
#
# chmod +x ./build-static-carthage
# ./build-static-carthage -d SnapKit Moya -p ios
XCCONFIG=""
# Platform
PLATFORM_NAME=""
# framework search path
FMWK_SEARCH_PATHS="$(inherited) "
FMWK_SEARCH_PATHS+="./Carthage/Build/iOS/**"
# Dependencies
DEPENDENCIES=""
# Current flag cursor
CURRENT_FLAG=""
function ValidatePlatformName() {
case "$1" in
"ios" | "macos" | "tvos" | "watchos")
echo "$1"
;;
*)
echo "$1"
# 1代表脚本执行错误
return 1
;;
esac
}
function initConfig() {
set -euo pipefail
# mktemp /tmp下创建唯一的临时xcconfig
XCCONFIG=$(mktemp /tmp/static.xcconfig.XXXXXX)
# 清理临时xcconfig当 Interrupt, Hang Up, Terminate, Exit signals
trap 'rm -f "$XCCONFIG"' INT TERM HUP EXIT
}
function chekcArgvs() {
# 遍历参数数量 shift移除 -d SnapKit Moya -p ios $# = 6
while [ ! $# -eq 0 ]; do
case "$1" in
--platform | -p)
CURRENT_FLAG="p"
;;
--dependencies | -d)
CURRENT_FLAG="d"
;;
*)
if [[ $CURRENT_FLAG == "p" ]]; then
if [[ $PLATFORM_NAME == "" ]]; then
PLATFORM_NAME+=$(ValidatePlatformName $1)
# 如果返回1 代表platform无效
if [ $? -eq 1 ]; then
echo "'$PLATFORM_NAME' ==> 无效的Platform" >&2
exit $?
fi
# 有值 只能指定单个platform
else
echo "Platform参数过多,只能指定单个之一 ios、macos、watchos、tvos" >&2
exit 1
fi
elif [[ $CURRENT_FLAG == "d" ]]; then
DEPENDENCIES+="$1 "
else
echo "'$1' 参数错误,请重新指定" >&2
exit 1
fi
;;
esac
shift
done
# check dependencies
if [[ $DEPENDENCIES == "" ]]; then
echo "请传入指定的依赖进行编译" >&2
exit 1
fi
# check platform
if [[ $PLATFORM_NAME == "" ]]; then
echo "缺少指定平台参数" >&2
exit 1
fi
}
function writeConfig() {
# 把以下属性写入临时的xcconfig文件进行临时依赖
echo "MACH_O_TYPE = staticlib" >>$XCCONFIG
echo "DEBUG_INFORMATION_FORMAT = dwarf" >>$XCCONFIG
echo "FRAMEWORK_SEARCH_PATHS = $FMWK_SEARCH_PATHS" >>$XCCONFIG
echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_12B45b = arm64 arm64e armv7 armv7s armv6 armv8' >>$XCCONFIG
echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200 = $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_$(XCODE_PRODUCT_BUILD_VERSION))' >>$XCCONFIG
echo 'EXCLUDED_ARCHS = $(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT)__XCODE_$(XCODE_VERSION_MAJOR))' >>$XCCONFIG
echo 'ONLY_ACTIVE_ARCH=NO' >>$XCCONFIG
echo 'VALID_ARCHS = $(inherited) x86_64' >>$XCCONFIG
# 排除xcode12生成的模拟器含有arm64架构,不然合成模拟器和真机版本会报错
echo 'EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64' >>$XCCONFIG
}
function exportConfig() {
# 导出XCODE_XCCONFIG_FILE 共享给其他shell使用
export XCODE_XCCONFIG_FILE="$XCCONFIG"
}
function build() {
echo
echo "Building static frameworks with Xcode temporary xconfig file:"
echo $XCCONFIG
echo
echo "With contents:"
while read line; do
echo "$line"
done <$XCCONFIG
echo
echo "Building with command:"
echo "carthage build --no-use-binaries --platform $PLATFORM_NAME $DEPENDENCIES"
echo
# Build Carthage
carthage build --no-use-binaries --platform $PLATFORM_NAME $DEPENDENCIES
}
initConfig
chekcArgvs $@
writeConfig
exportConfig
build
注: 此脚本同时处理了Xcode12+在编译模拟器架构时, 生成了arm64架构, 导致framework合成错误的问题
然后执行以下命令修复权限, 不然会报错 zsh: permission denied: ./build-static-carthage.sh
chmod +x ./build-static-carthage.sh
然后执行以下命令, 生成静态库framework
./build-static-carthage.sh -p ios -d SnapKit Toast-Swift Moya
生成的静态库framework目录如下
4 编写编译脚本, 生成xcframework
脚本跟生成framework的脚本类似, 不过最后调用的是carthage build --no-use-binaries --use-xcframeworks, 具体脚本如下
#!/bin/sh -e
# 用法:
#
# chmod +x ./build-static-carthage-xcframework
# ./build-static-carthage-xcframework -d SnapKit Moya -p ios
XCCONFIG=""
# Platform
PLATFORM_NAME=""
# framework search path
FMWK_SEARCH_PATHS="$(inherited) "
FMWK_SEARCH_PATHS+="./Carthage/Build/iOS/**"
# Dependencies
DEPENDENCIES=""
# Current flag cursor
CURRENT_FLAG=""
function ValidatePlatformName() {
case "$1" in
"ios" | "macos" | "tvos" | "watchos")
echo "$1"
;;
*)
echo "$1"
# 1代表脚本执行错误
return 1
;;
esac
}
function initConfig() {
set -euo pipefail
# mktemp /tmp下创建唯一的临时xcconfig
XCCONFIG=$(mktemp /tmp/static.xcconfig.XXXXXX)
# 清理临时xcconfig当 Interrupt, Hang Up, Terminate, Exit signals
trap 'rm -f "$XCCONFIG"' INT TERM HUP EXIT
}
function chekcArgvs() {
# 遍历参数数量 shift移除 -d SnapKit Moya -p ios $# = 6
while [ ! $# -eq 0 ]; do
case "$1" in
--platform | -p)
CURRENT_FLAG="p"
;;
--dependencies | -d)
CURRENT_FLAG="d"
;;
*)
if [[ $CURRENT_FLAG == "p" ]]; then
if [[ $PLATFORM_NAME == "" ]]; then
PLATFORM_NAME+=$(ValidatePlatformName $1)
# 如果返回1 代表platform无效
if [ $? -eq 1 ]; then
echo "'$PLATFORM_NAME' ==> 无效的Platform" >&2
exit $?
fi
# 有值 只能指定单个platform
else
echo "Platform参数过多,只能指定单个之一 ios、macos、watchos、tvos" >&2
exit 1
fi
elif [[ $CURRENT_FLAG == "d" ]]; then
DEPENDENCIES+="$1 "
else
echo "'$1' 参数错误,请重新指定" >&2
exit 1
fi
;;
esac
shift
done
# check dependencies
if [[ $DEPENDENCIES == "" ]]; then
echo "请传入指定的依赖进行编译" >&2
exit 1
fi
# check platform
if [[ $PLATFORM_NAME == "" ]]; then
echo "缺少指定平台参数" >&2
exit 1
fi
}
function writeConfig() {
# 把以下属性写入临时的xcconfig文件进行临时依赖
echo "MACH_O_TYPE = staticlib" >>$XCCONFIG
echo "DEBUG_INFORMATION_FORMAT = dwarf" >>$XCCONFIG
echo "FRAMEWORK_SEARCH_PATHS = $FMWK_SEARCH_PATHS" >>$XCCONFIG
echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_12B45b = arm64 arm64e armv7 armv7s armv6 armv8' >>$XCCONFIG
echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200 = $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_$(XCODE_PRODUCT_BUILD_VERSION))' >>$XCCONFIG
echo 'EXCLUDED_ARCHS = $(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT)__XCODE_$(XCODE_VERSION_MAJOR))' >>$XCCONFIG
echo 'ONLY_ACTIVE_ARCH=NO' >>$XCCONFIG
echo 'VALID_ARCHS = $(inherited) x86_64' >>$XCCONFIG
# 排除xcode12生成的模拟器含有arm64架构,不然合成模拟器和真机版本会报错
echo 'EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64' >>$XCCONFIG
}
function exportConfig() {
# 导出XCODE_XCCONFIG_FILE 共享给其他shell使用
export XCODE_XCCONFIG_FILE="$XCCONFIG"
}
function build() {
echo
echo "Building static frameworks with Xcode temporary xconfig file:"
echo $XCCONFIG
echo
echo "With contents:"
while read line; do
echo "$line"
done <$XCCONFIG
echo
echo "Building with command:"
echo "carthage build --no-use-binaries --use-xcframeworks --platform $PLATFORM_NAME $DEPENDENCIES"
echo
# Build Carthage framework
# carthage build --no-use-binaries --platform $PLATFORM_NAME $DEPENDENCIES
# Build Carthage xcframework
carthage build --no-use-binaries --use-xcframeworks --platform $PLATFORM_NAME $DEPENDENCIES
}
initConfig
chekcArgvs $@
writeConfig
exportConfig
build
注: 此脚本同时处理了Xcode12+在编译模拟器架构时, 生成了arm64架构, 导致framework合成错误的问题
将此脚本命名为build-static-carthage-xcframework.sh, 然后执行
./build-static-carthage-xcframework.sh -p ios -d SnapKit Toast-Swift Moya
生成的静态库xcframework目录如下
5 校验framework/xcframework是否为静态库
通过下面的命令查看framework是否为静态库
file framework/xcframework下的二进制文件, 比如
file /Users/ysb/Desktop/FrameworkDemo/GPKitDemo/Carthage/Build/iOS/Static/Moya.framework/Moya
输出结果如下, 证明生成静态库framework成功
file /Users/darren/Desktop/FrameworkDemo/GPKitDemo/Carthage/Build/Moya.xcframework/ios-arm64_armv7/Moya.framework/Moya
输出结果如下, 证明生成静态库xcframework成功
如果含有 current ar archive random library字样, 则是静态库
如果含有 dynamically linked shared library字样, 则是动态库
6 framework/xcframework使用
将要使用的framew/xcframework拖进工程, 点击copy
然后需要使用的地方import xxx就可以, 下面以导入SnapKit.xcframework和ToastSwiftFramework.framework为例测试
注, 如果要用模拟器运行, 还需要设置测试工程, 去除模拟器编译的arm64架构, 不然会报错Could not find module 'SnapKit' for target 'arm64-apple-ios-simulator'
最后模拟器和真机都能成功运行