iOS 组件化二进制化

2024-08-27  本文已影响0人  人生若只如初见丶_a4e8

背景

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

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

framework与.a的区别

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

二进制打包方式

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

源码和二进制切换方案

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

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库被修改,重新打包需要处理的步骤

总结

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

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

上一篇下一篇

猜你喜欢

热点阅读