【iOS】使用pod创建私有组件

创建组件工程

启动命令行,使用pod命令创建组件

pod lib create ZTTools_Swift // 名字自己取,会自动创建相应的工程和文件夹

之后会弹出一些选项,按需要填即可:

// 选择平台
What platform do you want to use?? [ iOS / macOS ]
 > iOS

// 选择语言
What language do you want to use?? [ Swift / ObjC ]
 > Swift

// 是否创建demo工程(一般都是需要的)
Would you like to include a demo application with your library? [ Yes / No ]
 > Yes

// 是否使用测试框架
Which testing frameworks will you use? [ Quick / None ]
 > None

// 是否创建UI单元测试
Would you like to do view based testing? [ Yes / No ]
 > No

至此,一个空的组件工程创建完毕。

清理单元测试

一般都用不上单元测试,使用可以把它给删了。

  • 项目里把Tests文件夹删了
  • 点击工程,在TARGETS里把单元测试的target删了
  • Podfile文件里,把单元测试的target全部内容删了

创建私有xcframework

如果不创建私有库,那你组件里的东西都会被看到,这不是我们想要的。
所以,我们需要创建一个库,把代码全放到这个库里面,然后再把这个库弄到组件里面。

xcframework与framework的对比

为什么我们要用.xcframework而不是.framework呢?
首先我们来看看这二者的区别:

  • .xcframework里面装载了多个平台的.framework,Xcode会自动选用正确指令集的.framework,也就是说编译后或者上传到App Store后只会包含单一平台,同时也省去了手动移除动态库中的模拟器指令集的工作
  • .framework虽然可以用lipo指令去合并多个.framework,但是,这是一个包含所有平台的,这会造成APP体积变大,并且App Store上架时不允许有模拟器版本,还得手动移除模拟器的指令,这显然很麻烦

创建私有framework工程

我们在组件工程里面再创建一个工程,选择Framework选项,并且把其添加到组件的xcworkspace工作空间里。
配置工程,在General里取消掉对Mac的支持,并调整支持的iOS系统版本

修改Podfile文件

默认的Podfile文件是不支持多项目的,需要我们修改里面的内容。

  • platform的平台和最低支持系统版本需要改为和项目的一致,同时项目里面的工程也全部保持一致
  • 添加workspace名字
  • 分别设置每个target的pod
  • target里面声明对应的project路径

project路径是一个相对路径,以Podfile文件所在的目录为根目录;一般来说,Podfile文件就在主工程那里,别的工程就以主工程做相对路径


以下为Podfile文件示例:

#use_frameworks! # 全局配,也可以每个项目单独配

platform :ios, '10.0'

# 工作空间名称
workspace 'ZTTools_Swift.xcworkspace' # 同一个工作空间,多个Project使用pod时,需要添加工作空间名称

# 主工程(带podfile的工程)
target 'ZTTools_Swift_Example' do # target的名字
  use_frameworks! # 项目单独配

  project 'ZTTools_Swift.xcodeproj' # 指明target的工程路径;使用相对路径,相对于Podfile文件

  pod 'ZTTools_Swift', :path => '../' # 组件的pod名
end

# 同一个工作空间里面别的项目依赖
target 'ZTTools' do

  use_frameworks! # 项目单独配
  project '../ZTTools/ZTTools.xcodeproj'

  pod 'Alamofire'
  pod 'SnapKit'
end

如果遇到pod报错,可尝试使用sudo gem install cocoapods更新pods解决

创建自动化脚本

我们选中SDK项目,点击File-New-Target,选中Other,然后选择Aggregate,命名为SDKBuildScript,点击完成。

旧版本的Xcode里,Aggregate是在Cross-platform里。

点击File-New-File,选择Shell Script,命名为SDKBuild,点击创建(不要把它添加到Targets,不然会被编译到Framework里的)。

把这段脚本复制到SDKBuild中,然后根据注释修改为你自己的。
完成脚本编写后,可以把Xcode里的SDKBuild文件删了(但不要移除到废纸篓

CONFIG="${CONFIGURATION}" # "Release" "${CONFIGURATION}" "Debug" 编译模式,使用Release即可
SCHEME_NAME="${PROJECT_NAME}" # 要build的scheme名,如果和scheme名不一致,需要修改为正确的scheme名
OUTPUT_SDK="${SCHEME_NAME}" # 产物名字
OUTPUT_SDKNAME="${OUTPUT_SDK}.framework"

# 项目里存放Framework的路径
TARGET_FOLDER="${SRCROOT}/../ZTTools_Swift/Classes"

# 工作空间路径
WORK_FOLDER="${SRCROOT}/../Example/ZTTools_Swift.xcworkspace"

# ---------- 以上配置是可以修改的,下面的配置则不需要改 ----------

# 编译时存放xcarchive的路径
SIMULATOR_ARCHIVE_PATH="${SRCROOT}/build/${OUTPUT_SDK}-iphonesimulator.xcarchive"
DEVICE_ARCHIVE_PATH="${SRCROOT}/build/${OUTPUT_SDK}-iphoneos.xcarchive"

# 编译时存放framework的路径
SIMULATOR_DIR_PATH="${SIMULATOR_ARCHIVE_PATH}/Products/Library/Frameworks/${OUTPUT_SDKNAME}"
DEVICE_DIR_PATH="${DEVICE_ARCHIVE_PATH}/Products/Library/Frameworks/${OUTPUT_SDKNAME}"

function removeBuild()
{
    if [ -d "${SRCROOT}/build" ]
    then
    rm -rf "${SRCROOT}/build/"
    fi
}

function removeBuildFile()
{
    for FILE in $(ls "${1}"|tr " " "?")
    do
    if [[ "${FILE}" =~ ".xcconfig" ]]
    then
    rm -f "${1}/${FILE}"
    fi
    done
}

removeBuild
rm -rf "${TARGET_FOLDER}/${OUTPUT_SDK}.xcframework"

# 分别clean模拟器和真机
xcodebuild clean -workspace "${WORK_FOLDER}" -scheme "${SCHEME_NAME}" -configuration "${CONFIG}" -derivedDataPath "./build" -archivePath "${DEVICE_ARCHIVE_PATH}" SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES -sdk iphoneos
xcodebuild clean -workspace "${WORK_FOLDER}" -scheme "${SCHEME_NAME}" -configuration "${CONFIG}" -derivedDataPath "./build" -archivePath "${SIMULATOR_ARCHIVE_PATH}" SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES -sdk iphonesimulator

# 编译真机的Framework
xcodebuild archive -workspace "${WORK_FOLDER}" -scheme "${SCHEME_NAME}" -configuration "${CONFIG}" -derivedDataPath "./build" -archivePath "${DEVICE_ARCHIVE_PATH}" SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES -sdk iphoneos

# 编译模拟器的Framework
xcodebuild archive -workspace "${WORK_FOLDER}" -scheme "${SCHEME_NAME}" -configuration "${CONFIG}" -derivedDataPath "./build" -archivePath "${SIMULATOR_ARCHIVE_PATH}" SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES -sdk iphonesimulator

removeBuildFile "${DEVICE_DIR_PATH}"
removeBuildFile "${SIMULATOR_DIR_PATH}"
    
# 合并framework,创建xcframework
xcodebuild -create-xcframework \
-framework "${SIMULATOR_DIR_PATH}" \
-framework "${DEVICE_DIR_PATH}" \
-output "${TARGET_FOLDER}/${OUTPUT_SDK}.xcframework"

open "${TARGET_FOLDER}" # 打开文件夹

removeBuild

点击SDK项目,然后在TARGETS里选中SDKBuildScript,上面选中Build Phases,点击左上角的“+”号,选择New Run Script Phase
在黑框里输入./SDKBuild.sh

  • 当然你也可以直接把脚本写在黑框里,这样子就不需要创建脚本文件了
    如果使用脚本文件的话,会报没权限的错误,所以需要使用命令行来打开权限(因为使用文件方便管理和编写代码,所以这里我选择了使用文件的方式):
    打开命令行,cd到SDKBuild.sh所在的目录,然后执行sudo chmod +x SDKBuild.sh即可解决权限问题
  • 在执行脚本前,需要配置好.podspec文件
  • 第一次运行脚本,编译好库后,需要自行执行一次pod install为demo工程安装该SDK。此后就不需要再执行该命令了,因为脚本会把新打包好的SDK替换掉旧的SDK,如果配置有变,还是需要用pod命令进行更新
  • 更多SDK开发知识请看这篇文章:iOS】使用workspace搭建SDK开发框架

配置SDK工程

  • 点击SDK项目,然后在TARGETS里选中SDK的target,点击上面的Build Settings,找到Build Active Architecture Only项设置为NO(意思就是当前打包的framework支持所有的设备,否则打包时只能用当前版本的模拟器或真机运行)
  • Build Settings,找到Excluded Architectures项,点击展开,在Release选项下点击+号,选择Any iOS Simulator SDK,值设置为arm64(处理arm64架构合并报错的问题;当然,也可以在脚本里处理)
  • 点击Edit Scheme..选中脚本的target,选中Run,把Build Configuration的值改为Release(设置生成的SDK为release版,当然也可以在脚本上设置,可以看脚本里的注释)
  • 对于Swift工程,需要在Build Settings里找到Build libraries for Distribution,设置YES,否则在合并.xcframework时会报No ‘swiftinterface’ files found within xx.swiftmodule的错(也可以在脚本设置)
  • 对于Swift工程,需要在Build Settings里找到Skip Install,设置NO,否则在归档的文件目录Products下会没有输出文件(也可以在脚本设置)
  • PROJECTTARGETSBuild Settings里,找到
    Allow non-modular includes in Framework Modules,并都设置为YES;当动态库需要引用第三方库的Framework,需要告诉编译器允许这种行为

如果工程报错no such module 'XXX',并且pods工程下的Products文件夹里的产物全是红的,说明了没生成对应的库。解决方法为:点击Pods工程,在PROJECTBuild Settings里,找到Build Active Architecture Only设置为NOBase SDK设置为iOS;每次pod install后可能会被重置

git管理

创建组件的时候,已经默认创建了git,但是,这个git是组件的,现在得把SDK的git和组件的git进行分开,使得SDK的git作为组件git的子模块进行管理。

创建远程私有库

因为SDK本身是没有git的,所以需要用git init命令给它创建一个本地git仓库。
创建好SDK工程的git后,需要编辑.gitignore文件,内容如下:

*~
.DS_Store
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xcbkptlist
*.xccheckout
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/

然后再编辑组件的.gitignore文件,同样是上面的内容。
随后创建2个远程仓库,并把它们和本地git仓库关联起来。
需要注意的是,远程仓库里,SDK的是私有的,pod组件可以是公开的。(因为代码都在SDK里,所以公开pod组件库也不会有什么问题,而且使用起来也更方便

子模块管理

我们为组件工程git添加子模块,这个时候的git得是主模块的git,即使用组件的git来执行添加命令。
添加子模块的命令为git submodule add <url> <path>,其中url可以是远程地址和本地地址,本地地址要用绝对对路径,path则是该子模块存储的目录路径(使用相对路径)。

  • 添加模块之前,组件和SDK的git都需要先提交到远程
  • 如果提交子模块提示The following paths are ignored by one of your .gitignore files,则用git submodule add -f来添加

配置pod的索引文件

项目名.podspec文件(我这里是ZTTools_Swift.podspec),这个文件是用来描述这个pod的说明信息的。当pod install安装库时,只会引入你在.podspec中配置的那些文件。

Pod::Spec.new do |s|
  s.name             = '组件名'
  s.version          = '版本号'
  s.summary          = '组件精简描述'

  s.description      = <<-DESC
组件详细描述
                       DESC

  s.homepage         = '组件主页'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { '用户名' => '邮箱' }
  s.source           = { :git => 'git地址', :tag => s.version.to_s }
  
  # 需要设置,不然项目引入库后会崩溃
  s.pod_target_xcconfig = {
  'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES'
 }
  s.user_target_xcconfig = { 'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES' }

  s.ios.deployment_target = '12.0' # 最低系统版本
  s.swift_versions = ['5.0'] # Swift版本
  s.vendored_frameworks = 'ZTTools_Swift/Classes/ZTToolsSDK.xcframework' # 使用私有库
  s.frameworks = 'UIKit', 'Foundation', 'Photos', 'UserNotifications', 'AVFoundation', 'CoreGraphics'
  s.dependency 'Alamofire' # 依赖库
  s.dependency 'SnapKit'

end

如果集成组件后,项目运行报One of the two will be used. Which one is undefined.,说明是符号冲突了,这个是pod的问题。实际上是因为你的组件包含了第三方pod,然后使用组件的工程也包含了这个第三方pod导致的,不喜欢这个提示是话,可以在使用组件的工程里,找到Pods文件夹,在该文件夹下所有的Pods-项目名.debug.xcconfigPods-项目名.release.xcconfig文件,找到文件里面的OTHER_LDFLAGS,把有提示重复的-l"第三方pod名"删除即可

发布组件

校验文件合法性

在发布之前,需要先转到组件所在的文件夹,使用命令校验.podspec文件。(可能需要翻墙
pod lib lint是基础校验命令,用来校验本地.podspec文件的,如果要校验远程,把lib改为spec即可。(spec会同时验证本地和远程是否通过

  • 如果使用了第三方库,需要在后面加上--use-libraries参数
  • 如果因为有警告导致报错的,可以加上--allow-warnings参数解决
  • 如果需要输出详细信息,可以加上--verbose参数
  • 如果是私有的repo库要就要加上--sources=“私有库的地址”
  • 提示passed validation即为校验通过
  • 提示[!] The spec did not pass validation即为校验失败
  • 只有校验通过了,才能进行下一步操作
  • 一般来说,只需要校验本地即可

发布

需要先转到组件所在的文件夹,使用命令pod trunk me查看是否注册trunk。
如果提示[!] Authentication token is invalid or unverified. Either verify it with the email that was sent or register a new session.说明还没注册过trunk或者登录已经过期了,需要执行pod trunk register 邮箱 '名字' --description='描述文本' --verbose。(后面2个参数是可选的

如果输出名字、邮箱和注册时间等信息,说明已经是注册并是登录状态。这个时候就可以提交组件到pod了。

使用命令pod trunk push xxx.podspec发布组件到pod,同样的可以加上--allow-warnings--verbose参数;如果要跳过验证pod是否导入,还可以加上--skip-import-validation参数。

  • 发布之前请先打上tag,不然会发布失败
  • tag必须和.podspec文件的s.version一致
  • 如果之前打了tag并发布了,更新了文件后请用新的tag,不然不生效(也就是说,你修了一个小bug,想同一个版本号,把本地和远程的tag删了,再打上同样的tag并推上远程,这种方法是不可行的
  • 提交成功后,并不一定能马上搜索到,需要等待一天左右

更新组件

  • 更新改动推送到远程仓库
  • 打tag,并推送到远程仓库
  • 执行发布命令即可

如果提示[!] You need to register a session first.,说明需要验证会话。使用pod trunk register "你之前注册的邮箱"后,去邮箱点击链接验证即可

其他

  • 使用命令pod trunk delete 组件名 版本号可以删除已发布的库的某一版本
  • 在组件文件夹里,有一个和组件名一样的文件夹,里面有2个文件夹,分别是放置资源的Assets和放置源码或者库的Classes
  • 在组件文件夹里,有一个叫Example的文件夹,里面就是demo工程
  • 打开demo的工作空间后,在Pods工程里,有一个叫ReplaceMe的文件,是创建组件时默认生成的,删除即可


欢迎来群139322447玩耍

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

推荐阅读更多精彩内容