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.使用Carthage和cocoapods结合的方式,由pod管理源码,Carthage管理二进制,由于我们项目一直是使用pod管理,且Carthage又要付出一定的学习成本,对于我们这种人数并不多的团队很不划算。

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

 

subspec实现源码和二进制切换

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

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

首先是创建项目,我这里使用的是用Xcode直接创建,本人建议使用pod lib create XXX的方式去创建,两者项目只是创建方式不同,实际操作上是一样的。

当创建完项目后,把我们需要私有化或者组件化的代码拖到项目中,并在target中创建二进制的target,

iOS组件化——源码与二进制的平滑切换_第1张图片

iOS组件化——源码与二进制的平滑切换_第2张图片

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

iOS组件化——源码与二进制的平滑切换_第3张图片

然后设置framework的build setting和build phases

iOS组件化——源码与二进制的平滑切换_第4张图片

iOS组件化——源码与二进制的平滑切换_第5张图片

iOS组件化——源码与二进制的平滑切换_第6张图片

iOS组件化——源码与二进制的平滑切换_第7张图片

然后再target中创建Aggregate,并加入打包脚本

iOS组件化——源码与二进制的平滑切换_第8张图片

iOS组件化——源码与二进制的平滑切换_第9张图片

具体脚本:

#!/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了。打包完以后,编写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' => '[email protected]' }
  s.source           = { :git => 'https://gitee.com/NickQCX/CXBlocksKit.git', :tag => s.version.to_s }
  # s.social_media_url = 'https://twitter.com/'

  s.ios.deployment_target = '8.0'

  #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_framework = 'CXBlocksKit/CXBlocksKit_Products/*.framework'
  end
end

然后就是push代码,做成私有库,具体步骤请参考cocoapods的官方文档或者自行查找。

最后在使用时,podfile的写法

# 默认framework
pod 'CXBlocksKit'


# 切换成源码
pod 'CXBlocksKit/source'
# 或者
pod 'CXBlocksKit', :subspec => ['source']

 

总结

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

最后感谢一下同事,在这期间提的宝贵建议

你可能感兴趣的:(iOS)