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

[中文教程]
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
查看查看297 回复回复2 收藏收藏 分享淘帖 转播转播 分享分享 微信
查看: 297|回复: 2
收起左侧

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

[复制链接]
代码买卖 发表于 2016-7-27 06:34:22 | 显示全部楼层 |阅读模式
快来登录
获取优质的苹果资讯内容
收藏热门的iOS等技术干货
拷贝下载Swift Demo源代码
订阅梳理好了的知识点专辑
这篇文章主要想介绍以下几个部分:

  • subspecs基本概念
  • 实际应用的好处
  • 如何对含有subspecs的CocoaPods库进行二进制化





1、什么是CocoaPods的subspecs

来一个直观点的。顺便为自己的YTXAnimations做个广告。

在Podfile中,它是这样的:

[Bash shell] 纯文本查看 复制代码
pod 'YTXAnimations', '~> 1.2.4', :subspecs => ["AnimateCSS", "Transformer"]  


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

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

iOS CocoaPods组件平滑二进制化解决方案及详细教程2之subspecs篇 - 敏捷大拇指 - iOS CocoaPods组件平滑二进制化解决方案及详细教程二之subspecs篇 1


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

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

iOS CocoaPods组件平滑二进制化解决方案及详细教程2之subspecs篇 - 敏捷大拇指 - iOS CocoaPods组件平滑二进制化解决方案及详细教程二之subspecs篇 2


在podspec里是这样的:

[Bash shell] 纯文本查看 复制代码
    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之间也可以有依赖关系,依赖其他第三方库等。

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

[Bash shell] 纯文本查看 复制代码
pod 'YTXAnimations', '~> 1.2.4'  


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




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

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

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

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

[Bash shell] 纯文本查看 复制代码
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中应该变成了这样:

[Bash shell] 纯文本查看 复制代码
target 'TestBusinessAExampleApp' do  
  pod 'BusinessA', :path => "../"
  pod 'BusinessB', '~>1.2.1' , :subspecs => ["SomeBusinessXXX"]
end  


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

[Bash shell] 纯文本查看 复制代码
target 'TestBusinessCExampleApp' do  
  pod 'BusinessC', :path => "../"
  pod 'BusinessB', '~>1.2.1' , :subspecs => ["SomeBusinessTTT"]
end  


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

[Bash shell] 纯文本查看 复制代码
target 'DaMeiTuanApp' do  
  pod 'BusinessA', '~>3.0.5'
  pod 'BusinessB', '~>1.2.1'
  pod 'BusinessC', '~>2.2.0'
end  





3、如何对含有subspecs的CocoaPods库进行二进制化

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

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

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

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

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


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

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

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

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

iOS CocoaPods组件平滑二进制化解决方案及详细教程2之subspecs篇 - 敏捷大拇指 - iOS CocoaPods组件平滑二进制化解决方案及详细教程二之subspecs篇 3


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

[Bash shell] 纯文本查看 复制代码
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
          end

      end
  end


  spec_names = _all_names[0...-1].join(", ") + " 和 " + _all_names[-1]

  s.description = "拆分了这些subspec:#{spec_names}"

end  


通过分析代码可以知道:

  • 有这些subspecs:GTMBase64, UIColor, UIView, UIImage, UIDevice, UITableView, UIViewController, UIButton, NSURL, NSDate, NSArray, NSString, NSDictionary, Util, FoundationAll, UIAll
  • NSDate依赖第三方DateTools。
  • NSString依赖兄弟GTMBase64。
  • FoundationAll依赖兄弟subspecs,自己没什么内容,或者说提供一个灵活的方式一口气纳入所有相关Foundation内容。
  • UIAll依赖兄弟subspecs,和FoundationAll想要做的是一样的。


相信各位读者看了这个podspec也就知道怎么创建自己的subspec了。或者看看ARAnalytics的podspec。

在App中的Podflie是这样用的:

[Bash shell] 纯文本查看 复制代码
pod 'YTXUtilCategory','~> 1.2.0'  




[Bash shell] 纯文本查看 复制代码
pod 'YTXUtilCategory','~> 1.2.0', :subspecs => ["UIColor", "FoundationALL"]  


从分析的结果来看我应该创建这些target:

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

iOS CocoaPods组件平滑二进制化解决方案及详细教程2之subspecs篇 - 敏捷大拇指 - iOS CocoaPods组件平滑二进制化解决方案及详细教程二之subspecs篇 4


在Example/Podfile中根据target的名字增加以下内容并pod install:

[Bash shell] 纯文本查看 复制代码
target 'YTXUtilCategoryGTMBase64Binary' do

end

.........省略

target 'YTXUtilCategoryNSDateBinary' do  
  pod 'DateTools', '~> 1.0'
end

target 'YTXUtilCategoryUtilBinary' do

end  


注意YTXUtilCategoryNSDateBinary,把它的第三方依赖加上。版本和podspec里描写的一致。

在根目录增加两个shell脚本。

buildbinary.sh和教程一的基本一致,只是改了第一行:

[Bash shell] 纯文本查看 复制代码
#获得第一个参数
PROJECT_NAME=$1  
# 编译工程
BINARY_NAME="${PROJECT_NAME}Binary"  


buildallbinary.sh

[Bash shell] 纯文本查看 复制代码
pushd "$(dirname "$0")" > /dev/null  
SCRIPT_DIR=$(pwd -L)  
popd > /dev/null  
#也可以写个for循环
./buildbinary.sh YTXUtilCategoryGTMBase64
......省略
./buildbinary.sh YTXUtilCategoryUtil


执行

[Bash shell] 纯文本查看 复制代码
./buildallbinary.sh


得到结果:

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

iOS CocoaPods组件平滑二进制化解决方案及详细教程2之subspecs篇 - 敏捷大拇指 - iOS CocoaPods组件平滑二进制化解决方案及详细教程二之subspecs篇 6


更改podspec内容为:

[Bash shell] 纯文本查看 复制代码
Pod::Spec.new do |s|  
  ......省略
  _all_names = []

  _GTMBase64         = { :spec_name => "GTMBase64"}

  _UIColor           = { :spec_name => "UIColor"}
  _UIView            = { :spec_name => "UIView"}
  _UIImage           = { :spec_name => "UIImage"}
  _UIDevice          = { :spec_name => "UIDevice"}
  _UITableView       = { :spec_name => "UITableView"}
  _UIViewController  = { :spec_name => "UIViewController"}
  _UIButton          = { :spec_name => "UIButton"}

  _NSURL             = { :spec_name => "NSURL"}
  _NSArray           = { :spec_name => "NSArray"}
  _NSDictionary      = { :spec_name => "NSDictionary"}
  _NSDate            = { :spec_name => "NSDate", :dependency => [{:name => "DateTools",    :version => "~> 1.0"    }] }

  _NSString          = { :spec_name => "NSString", :sub_dependency => [_GTMBase64] }

  _Util              = { :spec_name => "Util"}

  _temp = [_GTMBase64, _UIColor, _UIView, _UIImage, _UIDevice, _UITableView, _UIViewController, _UIButton, _NSURL, _NSArray, _NSDictionary, _NSDate, _NSString, _Util]

  puts '-------------------------------------------------------------------'
  if ENV['IS_SOURCE']
    puts '-------------------------------------------------------------------'
    puts "Notice:#{s.name} is source now"
    puts '-------------------------------------------------------------------'

    _temp.each do |spec|
      spec[:source_files]=["Pod/Classes/#{spec[:spec_name]}/*.{h,m}"]
    end

  else
    puts '-------------------------------------------------------------------'
    puts "Notice:#{s.name} is binary now"
    puts '-------------------------------------------------------------------'

    _temp.each do |spec|
        spec[:source_files]=["Pod/Products/#{s.name}#{spec[:spec_name]}/include/**"]
        spec[:public_header_files]=["Pod/Products/#{s.name}#{spec[:spec_name]}/include/*.h"]
        spec[:vendored_libraries]=["Pod/Products/#{s.name}#{spec[:spec_name]}/lib/*.a"]
    end
  end

  _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[:public_header_files]
              ss.public_header_files = spec[:public_header_files]
          end

          if spec[:vendored_libraries]
              ss.ios.vendored_libraries = spec[:vendored_libraries]
          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
          end

      end
  end

  spec_names = _all_names[0...-1].join(", ") + " 和 " + _all_names[-1]

  s.description = "拆分了这些subspec:#{spec_names}"

end 


难点其实在于podspec如何写,如何描述subspec之间的关系,如何拆分subspec。

假如a.h和b.h都用到了c.h,而a.h隶属于subspec A,而b.h隶属于subspec B。那你应该做一个subspec C其中包含c.h。而A和B都依赖C。

要避免a.h依赖b.h,b.h依赖a.h这种循环依赖的问题。

到此为止,含有subspec的CocoaPods库就这么简单的完成了。




4、后记

在做subspec二进制化遇到的问题:

[Bash shell] 纯文本查看 复制代码
_all_sync.each do |sync_spec|  
  ...
  ss.prefix_header_contents = "#define YTX_#{specname.upcase}_EXISTS 1"
  ... 
end  


注意ss. ss.prefixheadercontents这段。加了一个宏。然后在这里会用到:

[Bash shell] 纯文本查看 复制代码
#import "YTXRestfulModel.h"

#ifdef YTX_USERDEFAULTSTORAGESYNC_EXISTS
#import "YTXRestfulModelUserDefaultStorageSync.h"
#endif

#ifdef YTX_AFNETWORKINGREMOTESYNC_EXISTS
#import "AFNetworkingRemoteSync.h"
#endif

#ifdef YTX_YTXREQUESTREMOTESYNC_EXISTS
#import "YTXRestfulModelYTXRequestRemoteSync.h"
#endif

#ifdef YTX_FMDBSYNC_EXISTS
#import "YTXRestfulModelFMDBSync.h"
#import "NSValue+YTXRestfulModelFMDBSync.h"
#endif


在源码的情况下,如果我想要用YTXRequestRemote这个subspec,那么引入YTXRequestRemote时会自带宏YTXYTXREQUESTREMOTESYNCEXISTS,YTXRestfulModel在编译时会根据宏引入相关头文件。

问题来了,宏都是预编译的。当我编译出二进制时,内容已经决定了。这样就丧失了subspec的动态性了。所以关键的问题在于当初设计的时候没有考虑好。

希望大家看到这个例子后,避免将来遇到相思的问题。目前没有想到好的解决方案,所以这个库并没有二进制化。


欢迎订阅全球最大的Swift开发者社区敏捷大拇指Swifthumb.com)上的淘帖“iOS App 组件化架构”。




相关内容

滴滴/淘宝/微信/蘑菇街/casatwy等 iOS App的组件化架构漫谈

滴滴 iOS App的组件化实践与优化

纳斯达克上市公司 iOS App组件化开发实践

纳斯达克上市公司 CocoaPods组件平滑二进制化解决方案

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

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




作者: 曹俊

都看到这里了,就把这篇资料推荐给您的好朋友吧,让他们也感受一下。

回帖是一种美德,也是对楼主发帖的尊重和支持。

*声明:敏捷大拇指是全球最大的Swift开发者社区、苹果粉丝家园、智能移动门户,所载内容仅限于传递更多最新信息,并不意味赞同其观点或证实其描述;内容仅供参考,并非绝对正确的建议。本站不对上述信息的真实性、合法性、完整性做出保证;转载请注明来源并加上本站链接,敏捷大拇指将保留所有法律权益。如有疑问或建议,邮件至marketing@swifthumb.com

*联系:微信公众平台:“swifthumb” / 腾讯微博:@swifthumb / 新浪微博:@swifthumb / 官方QQ一群:343549891(满) / 官方QQ二群:245285613 ,需要报上用户名才会被同意进群,请先注册敏捷大拇指

嗯,不错!期待更多好内容,支持一把:
支持敏捷大拇指,用支付宝支付10.24元 支持敏捷大拇指,用微信支付10.24元

评分

参与人数 1金钱 +10 贡献 +10 专家分 +10 收起 理由
Anewczs + 10 + 10 + 10 32个赞!专家给力!

查看全部评分

本帖被以下淘专辑推荐:

手表哥 发表于 2016-7-29 22:39:00 | 显示全部楼层
Mark~~~~
cocoaswift 发表于 2016-11-3 16:14:36 | 显示全部楼层
经典帖子!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

做任务,领红包。
我要发帖

分享扩散

都看到这里了,就把这资料推荐给您的好朋友吧,让他们也感受一下。
您的每一位朋友访问此永久链接后,您都将获得相应的金钱积分奖励
关闭

站长推荐 上一条 /3 下一条

热门推荐

合作伙伴

Swift小苹果

  • 北京治世天下科技有限公司
  • ©2014-2016 敏捷大拇指
  • 京ICP备14029482号
  • Powered by Discuz! X3.1 Licensed
  • swifthumb Wechat Code
  •   
快速回复 返回顶部 返回列表