macOS Monterey 12.6.4
Xcode Version 14.1 (14B47b)
iPhone XR 16.4.1
1 相关知识
1.1 动态库和静态库的区别
动态库:
打包方式:.framework 或 .tbd(旧.dylib)
存在时期:编译时不会被拷贝到程序中,程序运行时由系统动态加载
优点:
- 可共享,节省资源
- 不需要重新编译,可以使用热部署
缺点:
- 不可上架App Store
- 仅 Apple 自带的 framework 是动态库
静态库:
打包方式:.a 或 .framework
打包方式:静态库编译时会被完整拷贝一份到目标程序中
优点:
- 可复用
缺点:
- 不可共享
- 少量改动就会大量的重复编译
1.2 指令集
2 新建工程
2.1 新建库工程
File -> New -> Project -> 选择Framework
2.2 库工程的编译配置
1. 设置兼容版本
Xcode -> Project -> Targets -> General -> Minimum Deployments -> 最低支持的兼容版本
2. 设置构建活动架构范围
Xcode -> Project -> Targets-> Build Settings -> Build Active Architecture Only -> NO
3. 移除模拟器中生成的arm64架构,也可以使用lipo -remove命令手动移除
Xcode -> Project -> Targets-> Build Settings -> Excluded Architecture -> Debug/Release -> Any iOS Simulator SDK -> arm64
4. 配置库类型,新增建的framework默认是动态库
Xcode -> Project -> Targets-> Build Settings -> Mach-O Type -> Static Library
5. 启用死代码剥离
Xcode -> Project -> Targets-> Build Settings -> Dead Code Stripping -> NO
6. 设置 Framework 为分发而构建,转换 xcframework 需要设置为YES
Xcode -> Project -> Targets-> Build Settings -> Build Libraries for Distribution -> NO
2.3 编写源代码
设置访问修饰符,以决定其他 framework 是否可以访问该类
open 任何模块可访问,可以被重写和继承
public 任何模块可访问,本模块内可以被重写和继承,其他模块不可以被重写和继承
internal 默认访问级别,只能在本模块内部访问
fileprivate 只能在当前文件中访问。如果当前文件有多个类,这些类中也可以访问
private 只能在当前类中访问。如果当前文件有多个类,这些类中不可以访问
2.4 文件访问级别
设置文件的访问级别,声明是否公开给其他 framework 使用
Public 供外界使用的头文件,放在最终产物的 Headers 目录中
Private 供外界使用的头文件,又没完全 Ready 的头文件,放在最终产物的 PrivateHeaders 目录中
Project 不供外界使用的头文件,也不会放在最终的产物中
3 生成 Framework
3.1 编译模拟器/真机包
修改编译配置
分别选择模拟器和真机进行编译(command+B),编译成功后会生成对应环境的 framework
3.2 构建通用架构
查看架构信息(静态库是文稿,动态库是可执行文件)
//<binary_file> 要查看信息的二进制文件的路径
lipo -info <binary_file>
移出无用架构,模拟器如果有 arm64 真机架构,需要移除掉,否则合并时会报错
//<architecture> 代表要从文件中移除的架构。架构通常是一些标识特定处理器类型的缩写,如 i386、x86_64、arm64等
//<input_file> 要处理的输入文件路径
//<output_file> 处理后生成的输出文件路径
lipo -remove <architecture> <input_file> -output <output_file>
合并为通用架构,方便支持真机调试和模拟器调试
//<file1>、<file2> 是要合并的二进制文件的路径,可以根据需要指定更多的文件路径
//<output_file> 是合并后输出的文件路径
lipo -create <file1> <file2> -output <output_file>
Swift Framework 仅仅合并仍然有问题,还需要做以下处理来支持真机和模拟器的环境
1. 修改通用架构的 Swift 头文件
-
xxx.framework -> Headers -> xxx-Swift.h
-
删除前两行源码
#if 0#elif defined(__arm64__) && __arm64__ -
替换为
#if defined(__x86_64__) && __x86_64__ || (__arm64__) && __arm64__
2. 合并真机包和模拟器包的 .swiftmodule 文件夹
- Release-iphonesimulator -> xxx.framework -> Modules -> xxx.swiftmodule
- 将模拟器的 .swiftmodule 文件夹内容合并到 iphoneos 的 .swiftmodule 文件夹中
3.3 脚本编译
选中Aggregate,直接编译command+B即可执行脚本
完整脚本内容
#!/bin/sh
#设置输出目录
OUTPUT_PATH="${PROJECT_DIR}/Products"
#设置项目工程名
XCODEPROJ_NAME="${PROJECT_NAME}.xcodeproj"
#创建或清空输出目录,-p 递归创建目录,会把子目录也创建出来
if [ -d "${OUTPUT_PATH}" ]; then
rm -rf ${OUTPUT_PATH}/*
else
mkdir -p "${OUTPUT_PATH}"
fi
#清空缓存 & 编译真机包 & 编译模拟器包
xcodebuild -project "${XCODEPROJ_NAME}" -target "${PROJECT_NAME}" clean
xcodebuild -project "${XCODEPROJ_NAME}" -target "${PROJECT_NAME}" -configuration ${CONFIGURATION} ONLY_ACTIVE_ARCH=NO -sdk iphoneos
xcodebuild -project "${XCODEPROJ_NAME}" -target "${PROJECT_NAME}" -configuration ${CONFIGURATION} ONLY_ACTIVE_ARCH=NO -sdk iphonesimulator
#拷贝真机包到指定目录
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${OUTPUT_PATH}"
#合并framework二进制文件
lipo -create -output "${OUTPUT_PATH}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}"
#合并.swiftmodule文件夹
SIMULATOR_MODULES_PATH="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/."
if [ -d "${SIMULATOR_MODULES_PATH}" ]; then
cp -R "${SIMULATOR_MODULES_PATH}" "${OUTPUT_PATH}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule"
fi
#修改头文件内容,项目名.framework/Headers/项目名-Swift.h
FILE_SWIFT="${OUTPUT_PATH}/${PROJECT_NAME}.framework/Headers/${PROJECT_NAME}-Swift.h"
NEW_LINE="#if defined(__x86_64__) && __x86_64__ || (__arm64__) && __arm64__"
#查找#if 0替换成空
sed -i '' 's/#if 0//g' $FILE_SWIFT
#查找#elif defined(__arm64__) && __arm64__替换成空
sed -i '' 's/#elif defined(__arm64__) && __arm64__//g' $FILE_SWIFT
#在1第一行添加指定字符串
sed -i '' "1 a\\
$NEW_LINE" $FILE_SWIFT
#删除无关的配置文件
FRAMEWORK_PATH="${OUTPUT_PATH}/${PROJECT_NAME}.framework"
for file in ls $FRAMEWORK_PATH
do
if [[ ${file} =~ ".xcconfig" ]]; then
rm -f "${FRAMEWORK_PATH}/${file}"
fi
done
#删除build文件夹
if [ -d "${PROJECT_DIR}/build" ]; then
rm -rf "${PROJECT_DIR}/build"
fi
#打开输出目录
open "${OUTPUT_PATH}"