iOS CocoaPods组件平滑二进制化解决方案及详细教程二之subspecs篇

JerryXia 发表于 , 阅读 (0)

在App中Pods/YTXAnimations文件目录下它是这样的:subspecsdemodict

在CocoaPods项目开发时是这样的:subspecsdemodev

在podspec里是这样的:

    YTXAnimateCSS   = { :spec_name => "AnimateCSS" }    YTXCSShake   = { :spec_name => "CSShake" }    YTXMagicCSS   = { :spec_name => "MagicCSS" }    $animations = [YTXAnimateCSS, YTXCSShake, YTXMagicCSS]    $animations.each do |sync_spec|        s.subspec sync_spec[:spec_name] do |ss|            specname = sync_spec[:spec_name]            sources = ["Pod/Classes/UIView+YTX#{specname}.*", "Pod/Classes/YTXAnimationsUtil.{h,m}"]            ss.source_files = sources            if sync_spec[:dependency]                sync_spec[:dependency].each do |dep|                    ss.dependency dep[:name], dep[:version]                end            end        end    end    s.subspec "Transformer" do |ss|      ss.source_files = ["Pod/Classes/YTXGooeyCircleLayer.{h,m}", "Pod/Classes/YTXCountDownShowLayer.{h,m}"]    end

在一个podspec里我可以定义它的subspecs,给使用方提供了一种灵活的方式去获取相关源码,而不是全部源码。subspec之间也可以有依赖关系,依赖其他第三方库等。

如果是这样的用的话,就是全量。

pod 'YTXAnimations', '~> 1.2.4'  

也有不少在github第三方库用了subspecs。比如:ARAnalytics

谈谈作用:subspecs这种模式特别适合组件化开发。

比如有两个业务team A和B。他们各自维护一个业务组件A和业务组件B。原则上业务组件A和业务组件B之间不能相互依赖。但是很多时候组件A需要调用组件B的功能,接受组件B的回调。架构的时候我们会使用依赖协议或者依赖下沉等等方式去除他们之间的耦合。

但问题是我们还是需要集成在一起调试的。

一般做法就是在业务组件A的Example项目的Podfile中,加上依赖业务组件B:

target 'TestBusinessAExampleApp' do    pod 'BusinessA', :path => "../"  pod 'BusinessB', '~>1.2.1'end  

然后在Example App中串联起A和B,以达到调试的目的。

组件B作为一个业务肯定是很庞大的,所以编译慢。二进制化可以解决这个问题。作为Team A的人,我不需要关注组件B是否太大编译慢,依赖等问题。

举个例子,比如外卖和电影,外卖会送电影票。

很容易想到!业务组件A只依赖业务组件B的部分。组件B应该把这部分其实对外的内容尽量做成一个subspec或者正常结构划分,划分成依赖其中几个subspec。这样业务组件A需要关心的事就更少了。当发生问题,Team A不得已想要查看业务组件B的源代码以查看是否问题出在了业务组件B的时候,Team A的人员面对的不是整个业务组件B的业务源代码,而是部分其实对外的源代码。缩小依赖的二进制文件大小或源代码数量也是有显而易见的好处的。

嗯,调试源代码,我们应该:

  • 删除Pods目录
  • pod cache clean --all
  • IS_SOURCE=1 pod install

嗯,这已经在公司内部达成了一致,使用IS_SOURCE。

现在的业务组件A的Example项目的Podfile中应该变成了这样:

target 'TestBusinessAExampleApp' do    pod 'BusinessA', :path => "../"  pod 'BusinessB', '~>1.2.1' , :subspecs => ["SomeBusinessXXX"]end  

进一步的如果有个业务组件C要和业务组件B打交道,它的Example项目的Podfile应该这样写:

target 'TestBusinessCExampleApp' do    pod 'BusinessC', :path => "../"  pod 'BusinessB', '~>1.2.1' , :subspecs => ["SomeBusinessTTT"]end  

在主项目App中的Podfile是这样写:

target 'DaMeiTuanApp' do    pod 'BusinessA', '~>3.0.5'  pod 'BusinessB', '~>1.2.1'  pod 'BusinessC', '~>2.2.0'end  

下面开始说说subspec如何二进制化,如何在podspec中定义

如果没有特别说明,没有讲到细节的内容或方式都应该在教程一里

在教程一里面提到有subspecs的CocoaPods的组件二进制化方案,说了两个方案。最后选择的方案:是对每一个subspec都做份二进制并保持它们之间依赖的相互关系。

为什么不使用全集?也就是把所有源码都变成.a呢?

  • 使用方的Podfile就需要改写了。
  • 使用方本来希望只用AnimateCSS和Transformer,现在不得不把CSShake和MagicCSS也包含进来了。

接下来以实际项目YTXUtilCategory作为例子来讲解。

方案就是:是对每一个subspec都做份二进制并保持它们之间依赖的相互关系。

YTXUtilCategory是我们的一个提供公共通用方法和Category的类。在二进制化之前它大概是长这个样子的:
subspecsutilcategorydemo1

二进制化之前它的podspec是这样的:

Pod::Spec.new do |s|    .......  _all_names = []  _GTMBase64         = { :spec_name => "GTMBase64",        :source_files => ['Pod/Classes/GTMBase64/GTM*.{h,m}'              ] }  _UIColor           = { :spec_name => "UIColor",          :source_files => ['Pod/Classes/UIColor/UIColor+*.{h,m}'          ] }  _UIView            = { :spec_name => "UIView",           :source_files => ['Pod/Classes/UIView/UIView+*.{h,m}'           ] }  _UIImage           = { :spec_name => "UIImage",          :source_files => ['Pod/Classes/UIImage/UIImage+*.{h,m}'          ] }  _UIDevice          = { :spec_name => "UIDevice",         :source_files => ['Pod/Classes/UIDevice/UIDevice+*.{h,m}'         ] }  _UITableView       = { :spec_name => "UITableView",      :source_files => ['Pod/Classes/UITableView/UITableView+*.{h,m}'      ] }  _UIViewController  = { :spec_name => "UIViewController", :source_files => ['Pod/Classes/UIViewController/UIViewController+*.{h,m}' ] }  _UIButton          = { :spec_name => "UIButton",         :source_files => ['Pod/Classes/UIButton/UIButton+*.{h,m}'           ] }  _NSURL             = { :spec_name => "NSURL",            :source_files => ['Pod/Classes/NSUR/NSURL+*.{h,m}'            ] }  _NSArray           = { :spec_name => "NSArray",          :source_files => ['Pod/Classes/NSArray/NSArray+*.{h,m}'          ] }  _NSDictionary      = { :spec_name => "NSDictionary",     :source_files => ['Pod/Classes/NSDictionary/NSDictionary+*.{h,m}'     ] }  _NSDate            = { :spec_name => "NSDate",           :source_files => ['Pod/Classes/NSDate/NSDate+*.{h,m}'           ] ,     :dependency => [{:name => "DateTools",    :version => "~> 1.0"    }] }  _NSString          = { :spec_name => "NSString",         :source_files => ['Pod/Classes/NSString/NSString+*.{h,m}'         ],    :sub_dependency => [_GTMBase64] }  _Util              = { :spec_name => "Util",             :source_files => ['Pod/Classes/Util/*.{h,m}'         ]}  _FoundationAll     = { :spec_name => "FoundationAll",    :sub_dependency => [_NSString, _NSURL, _NSDate, _NSArray, _NSDictionary    ] }  _UIAll             = { :spec_name => "UIAll",            :sub_dependency => [_UIColor, _UIView, _UIImage, _UIButton, _UIDevice, _UITableView, _UIViewController        ] }  _all_subspec = [_GTMBase64, _UIColor, _UIView, _UIImage, _UIDevice, _UITableView, _UIViewController, _UIButton, _NSURL, _NSDate, _NSArray, _NSString, _NSDictionary, _Util, _FoundationAll, _UIAll]  _all_subspec.each do |spec|      s.subspec spec[:spec_name] do |ss|          specname = spec[:spec_name]          _all_names << specname          if spec[:source_files]              ss.source_files = spec[:source_files]          end          if spec[:sub_dependency]              spec[:sub_dependency].each do |dep|                  ss.dependency "YTXUtilCategory/#{dep[:spec_name]}"              end          end          if spec[:dependency]              spec[:dependency].each do |dep|                  ss.dependency dep[:name], dep[:version]              end