iOS 组件化二进制化

背景

刚加入新的公司,接触到新公司的代码以后,心中是一篇翻江倒海,不是因为项目代码有多优秀,多牛逼,而是因为这是一个7年的老项目,期间经历过不知多少个程序员的手,项目简直是面目全非,各种重复的第三方库,代码耦合严重,不同时期的代码风格及开发模式完全不一样,造成项目过大,编译花费很多时间。现在的同事们正在想办法优化项目,在使用组件化的发开模式,减少与项目中老代码及第三方重复库的耦合。

因此,一些老的代码和一些已经不怎么更新且非常稳定的第三方库进行二进制处理,加快编译速度,同时在未来的开发中能更好进行整合和淘汰部分重复的代码。但是当错误发生在二进制库中的时候,我们不能有效定位具体代码,那么就需要切回源码,进行分析处理。为此,最近研究了源码与二进制平滑切换的方法,并分享一下心得,如有不足,请指出。

framework与.a的区别

  • .a:只把代码文件打包编译成二进制。
  • framework:把代码文件及其他资源,如图片,音频等文件,一起打包成二进制。

在选用何种二进制类型时,可以根据实际的项目情况进行打包。

二进制打包方式

  • 1.通过Xcode的官方打包方式,编译打包
  • 2.使用Aggregate打包
  • 3.使用脚本直接打包
  • 4.使用第三方工具打包,如cocoapods-packager

我这里选用的是Aggregate打包,因为Xcode的官方打包方式比较麻烦;使用脚本会因为不同组件,不用项目要去修改脚本,维护不方便;使用cocoapods-packager,虽然打包方便容易,但是在pod spec lint的时候出现了本地与远程仓库之间二进制文件路劲校验失败的情况的,具体原因还没找到,待后续补充。因此,最后选择了Aggregate打包方式,下面也以Aggregate打包的方式讲解。但是本人希望大家去尝试一下cocoapods-packager

源码和二进制切换方案

经过一周的调研和实践,发现网上主要是两种方案

  • 1.在podspec中使用if-else的条件语句去区分源码和二进制。但是在源码和二进制切换时,每次都需要pod cache clean一下,切换非常麻烦。虽然原作者给除了解决方案,但是需要一个静态服务器去存放二进制文件,切需要多个脚本去维护,开发及维护成本比较大。而且,在源码与二进制切换时,如果pod cache clean --all所有的二进制都会切换成源码,且pod时需要重新拉代码或者下载二进制,非常耗时。

  • 2.使用Carthagecocoapods结合的方式,由pod管理源码,Carthage管理二进制,由于我们项目一直是使用pod管理,且Carthage又要付出一定的学习成本,对于我们这种人数并不多的团队很不划算。

    参考:http://www.cocoachina.com/ios/20170512/19229.html?from=singlemessage&isappinstalled=0

subspec实现源码和二进制切换

在尝试了以上两种方案,发现他们的不足及不适应当前团队的情况下,和同时经过讨论,制定了使用cocoapodssubspec去实现源码和二进制切换。

subspec主要是在cocoapods中给私有库或第三方做目录分层使用。在pod的时候。在podfile中写入指定的subspec,可以只导入指定目录下的文件。根据这个功能,我们将源码和二进制一起做成私有库,分别放在两个subspec下。下面,我将会以BlocksKit的私有化为例子,讲解详细过程。

1.添加Framework类型的target

1、我这里使用的是用Xcode直接创建私有库,本人建议使用pod lib create XXX的方式去创建,两者项目只是创建方式不同,实际操作上是一样的。
当创建完项目后,把我们需要私有化或者组件化的代码拖到项目中,并在target中创建二进制的target
2、在组件库或私有库新建Framework类型的target

image.png
image.png

然后将需要二进制的文件引用到framework的target。注意:这里不需要copy文件过去

image.png

然后设置framework的build settingbuild phases
Xcode 12以上只需要添加x86_64,其他被废弃了,默认会添加arm64,具体看编辑完后缀带的内容

image.png

image.png
image.png
image.png
image.png

注意:做完上面的工作后,尝试编译这个framework的target时,会发现代码中引入的三方库提示找不到了。这是因为在Pod时没有针对该target 来关联三方库。所以你需要修改你的Podfile 文件, 让新加入的framework的target也导入三方库


use_frameworks!
platform :ios, '9.0'

target 'YourLib_Example' do
  pod '你需要的三方库'
end

target 'Framework名称' do
  pod '你需要的三方库'
end

修改完成后,再执行一次安装命令:

pod install

2.添加Aggregate 类型的target,并加入打包脚本

image.png
image.png

具体脚本:

#!/bin/sh
#要build的target名,需要替换成自己项目的名称
TARGET_NAME='CXBlocksKitFramework'
#${PROJECT_NAME}
if [[ $1 ]]
then
TARGET_NAME=$1
fi
UNIVERSAL_OUTPUT_FOLDER="${SRCROOT}/${PROJECT_NAME}_Products/"

#创建输出目录,并删除之前的framework文件
mkdir -p "${UNIVERSAL_OUTPUT_FOLDER}"
rm -rf "${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework"

#分别编译模拟器和真机的Framework
xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build

#拷贝framework到univer目录
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework" "${UNIVERSAL_OUTPUT_FOLDER}"

#合并framework,输出最终的framework到build目录
lipo -create -output "${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework/${TARGET_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/${TARGET_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${TARGET_NAME}.framework/${TARGET_NAME}"

#删除编译之后生成的无关的配置文件
dir_path="${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework/"
for file in ls $dir_path
do
if [[ ${file} =~ ".xcconfig" ]]
then
rm -f "${dir_path}/${file}"
fi
done

#判断build文件夹是否存在,存在则删除
if [ -d "${SRCROOT}/build" ]
then
rm -rf "${SRCROOT}/build"
fi

rm -rf "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator" "${BUILD_DIR}/${CONFIGURATION}-iphoneos"

#打开合并后的文件夹
open "${UNIVERSAL_OUTPUT_FOLDER}"

然后就可以打包framework了。
切换scheme 到-> aggregate创建的target,运行common+B后会自动执行生成脚本,生成新的framework库

打包结果

注意事项

Xcode 12以上虽然运行不会报错,其实已经打包失败了,查看编译记录可以发现Build/Products/Debug-iphonesimulator/路径不存在,最终打包的结果文件缺失,所以需要修改编译路径。

image.png

image.png

CONFIGURATION替换成CONFIGURATIONRELEASE指定编译release模式

#!/bin/sh
#要build的target名,需要替换成自己项目的名称
TARGET_NAME='YCDatasModule'
CONFIGURATIONRELEASE='Release'

真机模拟器库无法合并,报错:have the same architectures (arm64) and can't be in the same fat output file
XCode12之前:
编译模拟器静态库支持i386 x86_64两架构
编译真机静态库支持armv7 arm64两架构
使用lipo -create -output命令可以将两个库合并成一个支持模拟器和真机i386 x86_64 armv7 arm64四种架构的胖子库。
XCode12编译的模拟器静态库也支持了arm64,导致出现真机库和模拟器库不能合并的问题。

按如下配置:

image.png

3.编写podspec

在编写subpsec时,我们团队规定了source是源码,framework是二进制,用于pod时进行区分,这里我们默认使用二进制的subspec。这里的source和framework的命名可以根据项目具体情况做出调整。

Pod::Spec.new do |s|
  s.name             = 'CXBlocksKit'
  s.version          = '0.1.1'
  s.summary          = 'A short description of TPBlocksKit.'

  s.description      = <<-DESC
TODO: Add long description of the pod here.
                       DESC

  s.homepage         = 'https://gitee.com/NickQCX/CXBlocksKit'
  # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'Nick' => 'nick.qiu@cootek.cn' }
  s.source           = { :git => 'https://gitee.com/NickQCX/CXBlocksKit.git', :tag => s.version.to_s }
  # s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'

  s.ios.deployment_target = '8.0'
  s.pod_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' }
  s.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' }

  #s.source_files = 'TPBlocksKit/Classes/**/*’

  s.default_subspec = ‘framework'

  s.subspec 'source' do |ss|
    ss.source_files = 'CXBlocksKit/CXBlocksKit/BlocksKit/**/*'
  end

  s.subspec 'framework' do |ss|
    ss.ios.vendored_frameworks = 'Example/CXBlocksKit_Products/*.framework'
  end
end

使用

默认framework

pod ‘CXBlocksKit'

切换成源码

pod 'CXBlocksKit/source'

或者

pod 'CXBlocksKit', :subspec => ['source']

修改步骤

当lib库被修改,重新打包需要处理的步骤

  • 1.修改.podSpec文件,将tag版本号改成新的版本
  • 2.将在lib工程中新添加的文件,在framework库中添加好引用 (lib工程与framework工程文件同步)
  • 3.切换scheme 到-> aggregate创建的target,运行并执行生成脚本,生成新的framework库(新生成的库包含了这次的修改及新增的文件)
  • 4.连同.podSpec文件及修改或添加的文件一并提交到远程私有库
  • 5.将修改后的.podspec文件push到远程索引库 pod repo push …
  • 6.使用时,需要本地更新一下索引库 pod repo update 来获取到新的版本

总结

通过subspec的方式实现源码和二进制的切换,降低了学习成本和维护成本,且切换平滑。虽然需要修改podfile,但是与团队约定好以后,使用起来还是很方便的,并且一目了然,通过podfile可以清晰的知道哪个是源码,哪个是二进制。

原文链接:iOS 组件化的二进制化

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

推荐阅读更多精彩内容