前言
已经3年没碰过iOS了,前段时间从朋友那接手了一个快要开发完成的SDK项目,一开始我的内心其实是拒绝的,主要原因是:项目是用Swift开发的,我本身对Swift不熟悉,之前做iOS开发还是用的Objective-C,很久不做了,担心自己难堪重任。除此之外我还有点手痒,甚至还有一丝丝期待完工后朋友的赞赏,再加上朋友公司比较急,也实在找不到合适的人,天意如此。没想到一顿操作下来,遇到的坑不计其数,看样子是真的老了。拿到项目,项目目录是这样的,项目中没有引用的第三方库,也没有使用CocoaPods管理,项目根本跑不起来,我的难受瞬间开启了......为了他人避坑也方便自己日后查阅,本文记录了在开发、调试、打包的工程配置及遇到的问题,编码和架构层面就不过多赘述了,谨以此文献给那些年我们一起撸过代码的同学、朋友和同事,如有错误,欢迎指正。
一、创建Framework工程
1.选择iOS -> Framework,输入SDK名称(XXSDK,下文所出现XXSDK都是指输入的SDK名称,在拷贝代码时注意需要将XXSDK替换为实际SDK名称)
2.修改Framework工程配置
设置Framework最低支持的iOS版本
General -> XXSDK -> Deployment Info,选择iOS版本
设置Build Configuration(编译环境)
Target选择框 -> Edit Scheme... -> Run -> Info -> Build Configuration -> Release
上架应用时,app中引用的Framework必须要是Release环境,Debug环境只能调试使用,Release环境的Framework也可调试,为了方便,这里直接选择Release
设置Build Active Architecture Only(仅构建当前选择设备的架构)
General -> XXSDK -> Build Settings -> Build Active Architecture Only,选择No,表示不仅仅构建当前选择设备的架构
设置Excluded Architecture(排除架构)
General -> XXSDK -> Build Settings -> Excluded Architectures -> Release -> 添加选择Any iOS Simulator SDK -> 添加arm64
因为Xcode12以后,构建模拟器的库也默认支持arm64架构,这将会导致真机和模拟器的库合并时失败(fatal error: xx1/XLTestFrame and xx2/XLTestFrame have the same architectures (arm64) and can't be in the same fat output file),报错原因:真机库和模拟器库都包含arm64架构
设置Mach-O Type(库类型)
General -> XXSDK -> Build Settings -> Mach-O Type -> Static Library
Static Library表示静态库
3.使用CocoaPods管理第三方库
打开XXSDK根目录 -> pod init,打开Podfile文件,添加XXSDK用到的第三方库,然后pod install,打开XXSDK.xcworkspace,开始编码。注意:此时只是单纯引用第三方库,打包时并不会把第三方库添加到XXSDK中
4.创建代码文件,引入资源文件
右键New Group,输入Support.bundle,然后将资源文件直接拖到该文件夹下,这样一来,该资源文件会自动在XXSDK -> Build Phases -> Copy Bundle Resources中添加上,使用时也比较方便,例如:
if let main = Bundle.main.path(forResource: "XXSDK", ofType: "framework") { let bundle = Bundle.init(path: main) let Image = UIImage.init(named: "ic_success.png", in: bundle, compatibleWith: nil) }
除此之外,也可使用New File... -> Settings Bundle的方式引入资源文件,不过在使用时需要多加一级bundle路径才可找到对应的资源文件,此处不多赘述
二、添加编译脚本,自动合并Framework
在使用脚本自动打包、合并之前,先介绍下手动打包、合并的过程,以便对整个流程有深刻印象。
使用Swift制作的Framework合并分3步
1.利用lipo命令合并XXSDK.framework/XXSDK
2.合并XXSDK.framework/Modules/XXSDK.swiftmodule下的文件以及XXSDK.framework/Modules/XXSDK.swiftmodule/Project下的文件
3.合并XXSDK.framework/Headers/XXSDK-Swift.h头文件中的代码
在我能查到的资料中,都只提到了第1步,血的教训告诉我,2、3步必须要做,否则当把SDK集成到主工程时,会有各种奇怪报错,关于报错事项和原因以后再说。
lipo命令(简单介绍下,都能查到):
1.lipo -info XXSDK:查看静态库支持的架构
2.lipo -create XXSDK1 XXSDK2 -output XXSDK:合并静态库,输出一个新的库支持前者在一起的所有架构
3.lipo XXSDK -thin x86_64 -output XXSDK1:静态库拆分,输出一个新的库只支持x86_64架构
4.lipo XXSDK -remove arm64 -output XXSDK1:静态库移除架构:输出一个新的库支持前者除了arm64以外的架构
1.添加编译脚本的Target
TARGETS -> + -> Aggregate -> 输入名称,然后选中新增的TARGET -> Build Phases -> + -> New Run Script Phase
2.添加自动打包、合并的脚本文件
在项目的根目录下,新增脚本文件frameworkAggregate.sh,编辑frameworkAggregate.sh内容,将脚本文件添加上
3.运行SDKAggregate,在脚本执行结束后,会自动在项目的根目录下创建Products文件夹并打开
脚本代码参考:Swift+第三方库的framework制作流程详解,如有侵权,请联系删除
并对脚本代码作了3处调整,主要原因:
1.Xcode版本问题,在我本机的Xcode(13.3)上,会报这个错:
error: unable to attach DB: error: accessing build database
代码调整如下:
在xcodebuild命令下删除clean并且加上OBJROOT="${OBJROOT}/DependentBuilds"参数
xcodebuild -workspace "${WORKSPACE_NAME}" -scheme "${SDK_NAME}" -configuration ${CONFIGURATION} -sdk iphoneos ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
改成:
xcodebuild -workspace "${WORKSPACE_NAME}" -scheme "${SDK_NAME}" -configuration ${CONFIGURATION} OBJROOT="${OBJROOT}/DependentBuilds" -sdk iphoneos ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" build
xcodebuild -workspace "${WORKSPACE_NAME}" -scheme "${SDK_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
改为:
xcodebuild -workspace "${WORKSPACE_NAME}" -scheme "${SDK_NAME}" -configuration ${CONFIGURATION} OBJROOT="${OBJROOT}/DependentBuilds" -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" build
2.关于上面介绍的Swift制作的Framework合并分3步的第三步
swift_header="$dir_path/Headers/${SDK_NAME}-Swift.h"
newline1="#if defined(__x86_64__) && __x86_64__ || (__arm64__) && __arm64__ || (__i386__) && __i386__"
sed -i '' 's/#if 0//g' $swift_header
sed -i '' 's/#elif defined(__arm64__) && __arm64__//g' $swift_header
sed -i '' "1 a\\ $newline1" $swift_header
完整脚本代码如下:
#!/bin/sh # SDK名字, 改成自己的SDK名字即可
SDK_NAME=XXSDK
# framework最后输出的路径的文件夹
UNIVERSAL_OUTPUTFOLDER="${SRCROOT}/Products/"
WORKSPACE_NAME=${PROJECT_NAME}.xcworkspace
# 创建输出路径文件夹
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
# 移除上次编译生成的framework
rm -rf "${UNIVERSAL_OUTPUTFOLDER}/${SDK_NAME}.framework"
# 编译真机版framework
xcodebuild -workspace "${WORKSPACE_NAME}" -scheme "${SDK_NAME}" -configuration ${CONFIGURATION} OBJROOT="${OBJROOT}/DependentBuilds" -sdk iphoneos ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" build
# 编译模拟器版framework
xcodebuild -workspace "${WORKSPACE_NAME}" -scheme "${SDK_NAME}" -configuration ${CONFIGURATION} OBJROOT="${OBJROOT}/DependentBuilds" -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" build
# 拷贝编译生成的真机版framework到最终输出的路径
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${SDK_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}"
# 将模拟器框架的swift模块复制到最终输出的路径
SIMULATOR_SWIFT_MODULES_DIR="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${SDK_NAME}.framework/Modules/${SDK_NAME}.swiftmodule/."
if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then
cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${SDK_NAME}.framework/Modules/${SDK_NAME}.swiftmodule"
fi
# 合并模拟器和真机framework, 生成通用framework
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${SDK_NAME}.framework/${SDK_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${SDK_NAME}.framework/${SDK_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${SDK_NAME}.framework/${SDK_NAME}"
# 删除编译之后生成的无关的配置文件
dir_path="${UNIVERSAL_OUTPUTFOLDER}/${SDK_NAME}.framework/"
for file in ls $dir_path
do
if [[ ${file} =~ ".xcconfig" ]] then
rm -f "${dir_path}/${file}"
fi
done
# 拼接项目名.framework/Headers/项目名-Swift.h 文件名 swift_header="$dir_path/Headers/${SDK_NAME}-Swift.h"
# 在项目名.framework/Headers/项目名-Swift.h里面修改内容
newline1="#if defined(__x86_64__) && __x86_64__ || (__arm64__) && __arm64__ || (__i386__) && __i386__"
#查找#if 0替换成空
sed -i '' 's/#if 0//g' $swift_header
#查找#elif defined(__arm64__) && __arm64__替换成空
sed -i '' 's/#elif defined(__arm64__) && __arm64__//g' $swift_header
#在1第一行添加字符串newline1
sed -i '' "1 a\\ $newline1" $swift_header
# 打开合并后的文件夹 open "${UNIVERSAL_OUTPUTFOLDER}"
三、创建开发调试Demo工程
1.创建Project
选择App,输入Demo工程名称,选择SDK根目录,加入指定workspace
2.编辑Podfile文件,保证SDK和Demo工程依赖相同的第三方库,然后在SDK根目录下执行pod install
platform :ios, '9.0'
workspace 'XLTestFrame.xcworkspace'
#公用pods
def commonPods
use_frameworks!
pod 'Alamofire'
end
# sdk项目
target 'XLTestFrame' do
project 'XLTestFrame.xcodeproj'
commonPods
end
# demo项目
target 'XLTestFrameDemo' do
project 'XLTestFrameDemo/XLTestFrameDemo.xcodeproj'
commonPods
end
此时,文件夹、项目结构分别如下
2.Demo工程关联Framework及资源文件
这里将framework和资源文件引入到Demo工程中,保证Demo中可以访问到framework的api及资源文件,同时也保证编译Demo时也编译framework,使Demo可以调用到framework里刚加的代码。
在XXSDK中写一些调试代码,在Demo工程调用如下
写在最后
在天朝有个现象,当遇到问题去尝试搜索答案时,会看到很多一模一样的文章,但是作者不是同一个人,更离谱的是明明答案是错误的,大家却都还如此一致,所以便有了这篇文章,本篇是专题“iOS回忆录”的第一篇,以后可能会有第二篇、三篇......只希望可以给新手一点点借鉴,如有错误,欢迎指正,各位大神轻喷。