iOS使用Swift制作Framework(开发、调试、打包)

前言

已经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工程调用如下


XXSDK代码


Demo使用SDK的api

写在最后

在天朝有个现象,当遇到问题去尝试搜索答案时,会看到很多一模一样的文章,但是作者不是同一个人,更离谱的是明明答案是错误的,大家却都还如此一致,所以便有了这篇文章,本篇是专题“iOS回忆录”的第一篇,以后可能会有第二篇、三篇......只希望可以给新手一点点借鉴,如有错误,欢迎指正,各位大神轻喷。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容