美文网首页开发配置
基于cocoapods的私有库搭建

基于cocoapods的私有库搭建

作者: 生光 | 来源:发表于2017-12-25 18:27 被阅读76次

    最近想为公司搭建cocoapods私有库框架,老早之前做过,踩过不少坑,想不到又一次掉坑里。果真是好记性不如烂笔头,这次得记下来。

    1. 前言

    cocoapods早已不是新鲜技术,作为iOS平台上的依赖管理工具,方便我们日常开发过程中管理和更新第三方库。基本使用方式不再赘述,这里说一下需求。项目规模大了之后,往往需要将各个业务功能拆分封装,一方面解耦,另一方面可作为第三方库供其他项目组使用。
    所以产生一个需求:公司各开发团队,将自己的业务,封装为私有库,上传到公司私有git上,利用cocoapods统一管理。

    分解需求,我们需要:
    • 创建私有spec repo(相当于cocoapods私有库资源中心https://github.com/CocoaPods/Specs.git),所有的私有库上传记录在私有spec中
    • 工程封装为私有库,上传到私有spec repo中
    • 项目工程集成私有库

    2. 搭建

    1. 创建私有spec repo

    什么是spec repo

    在Podfile中,我们通过

    pod 'repoName'
    

    来配置要加载的第三方库。repoName对应的repo地址并没有写出来,因为我们会到https://github.com/CocoaPods/Specs.git中去查找。它相当于一个容器,将所有私有库的podspec文件放到这个spec repo中,建立索引,提供私有库配置信息。
    通过pod repo list命令,查看mac 上的repo列表:

    HotacooldeMacBook-Pro:0-Venus hotacool$ pod repo list
    
    master
    - Type: git (master)
    - URL:  https://github.com/CocoaPods/Specs.git
    - Path: /Users/hotacool/.cocoapods/repos/master
    

    本地spec repo的大致结构:

    .
    ├── Specs
        └── [SPEC_NAME]
            └── [VERSION]
                └── [SPEC_NAME].podspec
    

    进入/Users/hotacool/.cocoapods/repos/master,可以看到目录结构:


    1514169190670.jpg
    创建remote&本地clone

    理解了上面这个知识点,我们知道了需要创建一个自己的spec repo。

    • 在代码托管平台(github,Bitbucket,码云等),创建spec repo的托管空间
      例如在码云上创建一个spec repo:https://gitee.com/hotacool/HACSpec.git
    • 利用cocoapods提供command,在terminal上运行:
    // Clones `URL` in the local spec-repos directory at `~/.cocoapods/repos/`. The
          remote can later be referred to by `NAME`.
    pod repo add HACSpec https://gitee.com/hotacool/HACSpec.git
    

    会看到从remote地址git clone spec repo到本地,再次查看本地repo列表,可以看到我们创建的spec repo:

    HotacooldeMacBook-Pro:0-Venus hotacool$ pod repo list
    
    HACSpec
    - Type: git (master)
    - URL:  https://gitee.com/hotacool/HACSpec.git
    - Path: /Users/hotacool/.cocoapods/repos/HACSpec
    
    master
    - Type: git (master)
    - URL:  https://github.com/CocoaPods/Specs.git
    - Path: /Users/hotacool/.cocoapods/repos/master
    

    这一步是在本地添加私有库source,其他开发人员Podfile需要集成私有spec上的私有库,都需要首先通过pod repo add命令,本地clone spec repo。
    另外一些命令也可能会用到:

    // 查看本地spec repo列表
    pod repo list
    // 更新本地specName的spec repo。私有库podspec文件更改并push到spec repo后,往往需要update
    pod repo update specName
    // 本地删除spec repo
    pod repo remove specName
    

    2. 私有库制作

    创建一个基本(不包含代码)的私有库

    创建私有库之前,首先需要解耦独立出单独的组件项目。如果已有独立的组件项目,可以通过pod spec create直接创建podspec文件,然后配置,生成私有库。这里首先讲一下,如何完整的创建一个新的私有库,并push到我么自己的spec repo中。
    官网上有详细的命令解释文档。下面以创建一个名为HStockCharts的私有库为例:

    • 创建新的lib库
    pod lib create HStockCharts
    

    cocoapods会clone pod-template模板来生成HStockCharts,创建过程中会问几个问题:

    What language do you want to use?? [ Swift / ObjC ]
     > ObjC
    
    Would you like to include a demo application with your library? [ Yes / No ]
     > Yes
    
    Which testing frameworks will you use? [ Specta / Kiwi / None ]
     > None
    
    Would you like to do view based testing? [ Yes / No ]
     > No
    
    What is your class prefix?
     > H
    
    Running pod install on your new library.
    

    一般我会按上面代码来生成一个带example的例子工程,这样生成的example相对干净。也可以选择自动集成testing framework,或者view based testing,这样cocoapods会自动帮你集成一些额外的三方库。
    这样生成的目录结构:

    HotacooldeMacBook-Pro:HStockCharts hotacool$ tree -L 2
    .
    ├── Example
    │   ├── HStockCharts
    │   ├── HStockCharts.xcodeproj
    │   ├── HStockCharts.xcworkspace
    │   ├── Podfile
    │   ├── Podfile.lock
    │   ├── Pods
    │   └── Tests
    ├── HStockCharts
    │   ├── Assets
    │   └── Classes
    ├── HStockCharts.podspec
    ├── LICENSE
    ├── README.md
    └── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj
    
    10 directories, 5 files
    

    HStockCharts.podspec是私有库的配置文件;HStockCharts文件夹下是私有库代码文件,Assets是放资源,Classes下是编译的源文件;Example是自动生成的测试工程。

    • 编辑.podspec文件
      .podspec文件是私有库的配置文件。新创建的lib中,自动生成了HStockCharts.podspec,填充了一些默认配置,我们需要手动去编辑。cocoapods是基于Ruby语音写的,所以可以设置为Ruby语法高亮。
    Pod::Spec.new do |s|
      s.name             = 'HStockCharts'
      s.version          = '0.1.0'
      s.summary          = 'A short description of HStockCharts.'
    
    # This description is used to generate tags and improve search results.
    #   * Think: What does it do? Why did you write it? What is the focus?
    #   * Try to keep it short, snappy and to the point.
    #   * Write the description between the DESC delimiters below.
    #   * Finally, don't worry about the indent, CocoaPods strips it!
    
      s.description      = <<-DESC
    TODO: Add long description of the pod here.
                           DESC
    
      s.homepage         = 'https://github.com/shisosen@163.com/HStockCharts'
      # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
      s.license          = { :type => 'MIT', :file => 'LICENSE' }
      s.author           = { 'shisosen@163.com' => 'shisosen@163.com' }
      s.source           = { :git => 'https://github.com/shisosen@163.com/HStockCharts.git', :tag => s.version.to_s }
      # s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
    
      s.ios.deployment_target = '8.0'
    
      s.source_files = 'HStockCharts/Classes/**/*'
      
      # s.resource_bundles = {
      #   'HStockCharts' => ['HStockCharts/Assets/*.png']
      # }
    
      # s.public_header_files = 'Pod/Classes/**/*.h'
      # s.frameworks = 'UIKit', 'MapKit'
      # s.dependency 'AFNetworking', '~> 2.3'
    end
    

    各标签(s.name等)的具体意义可以参照官方文档。这里先列举几个重要的地方:

    s.name //名称,pod search 查找
    s.version //版本号,私有库git tag。如果s.source里设置了s.version.to_s,需要注意与私有库git的tag相匹配。
    s.source //重要,私有库的remote 地址和tag。也可以:branch => 'master'设置分支。
    s.source_files //#代码源文件地址,**/*表示Classes目录及其子目录下所有文件;如果有多个目录下则用逗号分开如['','','']形式
    s.resource_bundles //资源文件地址
    s.public_header_files //公开的头文件地址
    s.frameworks //所需的framework,多个用逗号隔开
    s.dependency //依赖关系,该项目所依赖的其他库,如果有多个需要填写多个s.dependency。cocoapods会自动将依赖的其他库集成到工程中。
    

    现在只简单的对source等配置(s.source,s.description)做一下修改:

    Pod::Spec.new do |s|
      s.name             = 'HStockCharts'
      s.version          = '0.1.0'
      s.summary          = 'HStockCharts.'
    
      s.description      = <<-DESC
    HStockCharts is a library for stock charts.
                           DESC
    
      s.homepage         = 'https://gitee.com/hotacool'
      s.license          = { :type => 'MIT', :file => 'LICENSE' }
      s.author           = { 'hotacool' => 'shisosen@163.com' }
      s.source           = { :git => 'https://gitee.com/hotacool/HStockCharts.git', :tag => s.version.to_s }
    
      s.ios.deployment_target = '8.0'
    
      s.source_files = 'HStockCharts/Classes/**/*'
      
      # s.resource_bundles = {
      #   'HStockCharts' => ['HStockCharts/Assets/*.png']
      # }
    
      # s.public_header_files = 'Pod/Classes/**/*.h'
      # s.frameworks = 'UIKit', 'MapKit'
      # s.dependency 'AFNetworking', '~> 2.3'
    end
    
    • push 私有库到remote,validation
      现在在本地已完成私有库的创建,需要将私有库push到远程仓库,这样其他人才能到远程仓库clone。这里将本地HStockCharts push到https://gitee.com/hotacool/HStockCharts.git,即podspec设置的source地址。
      因为我们podspec s.source设置了:tag => s.version.to_s,version是0.1.0,所以需要打tag,注意tag应与version保持一致:
    git tag -m "release v.0.1.0 "0.1.0"
    git push --tags     //推送tag到远端仓库
    

    然后开始通过pod lib lint 对私有库进行validation:

    HotacooldeMacBook-Pro:HStockCharts hotacool$ pod lib lint
    
     -> HStockCharts (0.1.0)
    
    HStockCharts passed validation.
    

    验证通过。默认情况下,如果验证出现warning,是不能通过验证的。可以通过添加--allow-warnings来允许warnings。

    • push私有库到spec repo
      我们已经在本地搭建好了一个私有库,在Example中,Podfile通过相对地址,引入HStockCharts。
    pod 'HStockCharts', :path => '../'
    

    现在需要将私有库推送到我们的spec repo中,从而可以从远程clone。
    之前已经在本地通过pod repo add,新加入了我们自己的spec repo: HACSpec。通过命令:

    pod repo push HACSpec HStockCharts.podspec  //前面是本地Repo名字 后面是podspec名字
    

    将HStockCharts推送到spec repo。执行pod repo update HACSpec,再cd 到本地repo文件夹/Users/hotacool/.cocoapods/repos/HACSpec,我们可以看到:

    .
    ├── HStockCharts
    │   └── 0.1.0
    ├── README.md
    

    HStockCharts已从remote加入本地spec,版本号0.1.0。

    • 集成私有库
      现在我们可以在其他项目中,集成我们自己的私有库。Podfile加入:
    pod 'HStockCharts'
    

    运行pod install,报错[!] Unable to find a specification for HStockCharts
    因为HStockCharts在我们自己的spec repo中,默认的source地址是cocoapods repo。Podfile头部加入:

    source 'https://gitee.com/hotacool/HACSpec.git' #private spec repo
    

    再次pod install,成功。
    同样的,因为我们的spec是自建的,没有fork cocoapods的,所以不包含其他公共的私有库,如果你用到pod 'AFNetworking',Podfile也需要添加:

    source 'https://github.com/CocoaPods/Specs.git'
    

    至此,我们已经完整的建立了私有库,并加入私有的spec repo。但这个库什么都干不了,如果加入我们自己的代码,需要做更多的配置和处理。

    私有库设置

    上面的介绍,没有涉及功能代码,实际的私有库配置,往往会遇到各种问题。下面对遇到的具体问题,做一些对应。

    • 私有库依赖其他framework、library设置
      解耦出的私有库工程,往往有对某些系统或者第三方framework、.a静态库的依赖。所以需要对私有库的podspec进行设置,使其他工程Podfile集成私有库时,能够自动引入相关依赖。
      我们会对下面几个标签进行设置:
    s.framework = 'SystemConfiguration','CoreData' //对系统framework的依赖
    s.libraries   = 'c++','stdc++.6.0.9' //对系统.a的依赖,注意去掉lib前缀
    
    s.ios.vendored_libraries = 'libXXX.a' //对第三方.a的依赖,注意路径,libXXX.a在.podspec目录下
    s.ios.vendored_frameworks = 'XXX.framework' //对第三方framework的依赖
    

    在项目的Pods工程的私有库target的build setting中,查看Other linker Flags,可以看到依赖的framework和.a:


    1514193548251.jpg
    • 包含.a的私有库
      上面说了对第三方.a的依赖,可以通过s.ios.vendored_libraries来配置。公开的header files,按照官方说明,可以再s.public_header_files中设置。但貌似没达不到效果。目前我是通过s.source_files,将.h头文件统一放在s.source_files目录下输出。
      另一个问题,直接对包含.a的私有库使用pod lib lint来validation是可以通过的,但pod repo push验证会出错会报错。报错内容类似:
    - ERROR | [iOS] unknown: Encountered an unknown error (The 'Pods-App' target has transitive dependencies that include static binaries: (/private/var/folders/vx/466sl6v913z1xvbssfgpnjb00000gn/T/CocoaPods-Lint-20171220-2701-11qtxh8-MarketModule/Pods/XXXLib/libXXX.a)) during validation.
    

    虽然这并不影响Pod的使用,但是验证是无法通过的。可以通过 --use-libraries 来让验证通过:

    pod repo push HACSpec MarketModule.podspec --allow-warnings --use-libraries
    

    还有一个类似问题,如果在工程Podfile中使用use_frameworks!,会报错:

    [!] The 'Pods-Venus' target has transitive dependencies that include static binaries: (/Users/hotacool/work/0-Venus/0-Venus/Pods/SZYDataSource/libMApi.a)
    

    这里一个可行的办法,只能注释掉'use_frameworks!'。参考链接

    • 通配符地址匹配报错
      可能会出现类似报错:
    - ERROR | [iOS] file patterns: The `source_files` pattern did not match any file.
    

    一般是文件路径无法匹配造成。例如:

    s.source_files = ['HStockCharts/Classes/**/*.{c,h,hh,m,mm,cpp}' //可以匹配HStockCharts/Classes/A/A.m,但无法匹配HStockCharts/Classes/A.m。
    s.source_files = ['HStockCharts/Classes/**/*.{c,h,hh,m,mm,cpp}', 'HStockCharts/Classes/*.{c,h,hh,m,mm,cpp}'] //可以实现上述匹配
    
    • 私有库对私有库的依赖
      私有库B需要依赖私有库A,可以在B.podspec中设置:
    s.dependency 'A'
    

    如果执行pod lib lint 或者pod repo push 会报错找不到A。这是肯定的,默认的spec repo是cocoapods的公共库,所以需要对source单独设置:

    pod lib lint --sources='https://gitee.com/hotacool/HACSpec.git'
    pod repo push HACSpec B.podspec --sources='https://gitee.com/hotacool/HACSpec.git'
    
    • 添加资源文件
      有以下几种添加资源方法:
    // 1.默认方式,cocoapods会将匹配路径'HStockCharts/Assets/*.png’下资源打包为名称HStockCharts的bundle,代码中通过bundle名来查找资源。
      s.resource_bundles = {
        'HStockCharts' => ['HStockCharts/Assets/*.png']
      }
    // 调用方式:
    NSBundle *bundle = [NSBundle bundleForClass:[MYSomeClass class]];
    NSURL *bundleURL = [bundle URLForResource:@"HStockCharts" withExtension:@"bundle"];
    NSBundle *resourceBundle = [NSBundle bundleWithURL: bundleURL];
    UIImage *img = [UIImage imageNamed:icon inBundle:bundle compatibleWithTraitCollection:nil];
    // 或者
    UIImage *image = [UIImage imageNamed:@"HStockCharts.bundle/imagename"];
    
    // 2.资源会在打包的时候直接拷贝的main bundle中,可能会和其它资源产生命名冲突
    spec.resources = ["Images/*.png", "Sounds/*"]
    
    // 3.把资源都放在bundle中,然后打包时候这个bundle会直接拷贝进app的mainBundle中。使用的时候在mainBundle中查找这个bundle然后再搜索具体资源
    spec.resource = "Resources/MYLibrary.bundle"
    //调用方式:
    NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:@"JZShare" withExtension:@"bundle"];
        NSBundle *bundle = [NSBundle bundleWithURL:bundleURL];
        UIImage *img = [UIImage imageNamed:icon inBundle:bundle compatibleWithTraitCollection:nil];
    
    • pod lib lint 和 pod spec lint
      两者都是对私有库进行validation。不过区别是:
    pod lib lint是只从本地验证你的pod能否通过验证
    pod spec lint是从本地和远程验证你的pod能否通过验证
    

    所以如果你使用pod spec lint,需要保证remote及时更新,否则会发现本地已经做了修改,但一直无法validation成功...

    • 更新维护podspec
      在开发初始阶段,可能会频繁对podspec进行更改。在pod repo push后,记得及时pod repo update specName,否则可能拉取不到最新的私有库配置。

    • mrc文件设置
      某些文件需要设置mrc,podspec可以如下设置:

      non_arc_files = 'MyLib/Classes/xxx.m'
      s.exclude_files = non_arc_files
    
      s.subspec 'no-arc' do |sp|
        sp.source_files = non_arc_files
        sp.requires_arc = false
      end
    

    but,不知是否是cocoapods的版本原因(1.3.1),采用这种方式我仍旧无法通过验证。通过--no-clean,进入验证的APP工程,发现私有库下的编译文件只有non_arc_files文件。
    怀疑是否默认使用no-arc 的subspec,之后参照链接,设置default_subspec和subspec。但仍旧无法验证通过。
    最后,只能通过将requires_arc设为mrc,单独添加arc配置,来实现对mrc文件的编译。如下:

    #source_files需要包含mrc和arc全部文件路径
    s.source_files = ["xxx/MRC/*.{c,h,hh,m,mm,cpp}", 'xxx/Classes/**/*.{c,h,hh,m,mm,cpp}', 'xxx/Classes/*.{c,h,hh,m,mm,cpp}'] 
    s.requires_arc = false
    s.requires_arc = ['xxx/Classes/**/*.{c,h,hh,m,mm,cpp}', 'xxx/Classes/*.{c,h,hh,m,mm,cpp}']
    
    • pod 本地repo缓存清理
      私有库开发阶段,会经常修改文件,这时候极易出现remote上已更新,但本地pod install/update 的私有库还是老版本的情况。这往往是因为pod本地缓存导致,需要更新本地缓存:
    //1. 更新本地spec
    pod repo update xxxSpec
    //2. 去本地pods缓存文件夹(~/Library/Caches/CocoaPods/Pods/Release),删除需要更新的库
    open ~/Library/Caches/CocoaPods/Pods/
    

    3. 项目集成私有库

    私有库已搭建完成,私有库的集成方式与cocoapods公共库集成方式无异,只需按照上述,在Podfile的source加入私有spec repo地址。无需赘述。
    这里提一下本地相对路径的集成方式。我们可以通过pod xxx,直接从remote clone私有库到项目中。也可以通过pod 'HStockCharts', :path => '../'的方式,将本地库链到工程中。对于需要开发的各个私有库,可以将其clone到本地,然后本地相对路径的方式链接到工程中,对私有库进行修改,然后push到对应私有库。或许可以以此来解耦复杂工程,结合脚本,搭建环境,完成业务的独立开发,这也是目前我做的事情。

    4. 总结

    这里总结了一下基于cocoapods的私有库搭建,归纳了一些基本的方法,记录了一些遇到的问题。因为需求不大,涉及深度有限。如在后续改造优化过程中有更多的问题出现,也在此同步更新。

    参考资料:
    使用Cocoapods创建私有podspec
    Cocoapods使用私有库中遇到的坑

    相关文章

      网友评论

        本文标题:基于cocoapods的私有库搭建

        本文链接:https://www.haomeiwen.com/subject/udhnwxtx.html