Pod私有库

1、创建私有Spec Repo

Spec Repo是什么?它是所有的Pods的一个索引,就是一个容器,所有公开的Pods都在这个里面,它实际是一个Git仓库remote端在GitHub上,所以想要创建pod私有库,需要创建类似于master的私有Spec Repo,先在远程仓库创建一个工程JCLiveIOSSpecs,然后执行创建私有Spec Repo命令:

1
2
#pod repo add [Private Repo Name][GitHub HTTPS clone URL]
$ pod repo add JCLiveIOSSpecs http://git.jc/elephant-ios-component/JCLiveIOSSpecs.git

完成之后,进入到~/.cocoapods/repos目录下就可以看到JCLiveIOSSpecs这个目录了。

2、创建Pod项目工程文件

从零开始创建一个组件库,可以使用Cocoapods提供的工具Using Pod Lib Create ,先cd到要创建项目的目录然后执行

1
$ pod lib create YXPlayerSDK

然后显示四个问题,1.是否需要一个例子工程;2.选择一个测试框架;3.是否基于View测试;4.类的前缀;4个问题的具体介绍可以去看官方文档。回答完4个问题他会自动执行pod install命令创建项目并生成依赖,项目结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
YXPlayerSDK
├── Example #demo APP
│ ├── YXPlayerSDK
│ ├── YXPlayerSDK.xcodeproj
│ ├── YXPlayerSDK.xcworkspace
│ ├── Podfile #demo APP 的依赖描述文件
│ ├── Podfile.lock
│ ├── Pods #demo APP 的依赖文件
│ └── Tests
├── LICENSE #开源协议 默认MIT
├── Pod #组件的目录
│ ├── Assets #资源文件
│ └── Classes #类文件
├── YXPlayerSDK.podspec #第三步要创建的podspec文件
└── README.md #markdown格式的README

3、创建私有库pod组件

所需要做的工作就是在相应的 Pods/Developemnt Pods/ 组件 /Classes 下编码,就是向Development Pods文件夹中添加库文件和资源,将编写的组件相关的class放入YXPlayerSDK/Classes中、资源图片文件放入YXPlayerSDK/Assets中,并配置podspec文件,然后进入Example文件夹执行pod install命令,再打开项目工程可以看到,刚刚添加的组件已经在Pods子工程下Development Pods/YXPlayerSDK中了。

向私有库中添加资源(图片、音视频等)方法共有三种:

  • 第一种

    1
    spec.resources = ["Images/*.png", "Sounds/*"]

    但是这些资源会在打包的时候直接拷贝的app的Bundle中,这样说不定会和其它资源产生命名冲突

  • 第二种

    1
    spec.resource = "Resources/MYLibrary.bundle"

    把资源都放在bundle中,然后打包时候这个bundle会直接拷贝进app的mainBundle中。使用的时候在mainBundle中查找这个bundle然后再搜索具体资源

    1
    2
    3
    NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:@"JZShare" withExtension:@"bundle"];
    NSBundle *bundle = [NSBundle bundleWithURL:bundleURL];
    UIImage *img = [UIImage imageNamed:icon inBundle:bundle compatibleWithTraitCollection:nil];
  • 第三种

    1
    2
    3
    4
    spec.resource_bundles = {
    'MyLibrary' => ['Resources/*.png'],
    'OtherResources' => ['OtherResources/*.png']
    }

    这种方法利用 framework 的命名空间,有效防止了资源冲突。pod会把添加的资源文件编译成bundle,使用方法是先拿到最外面的 bundle,然后再去找下面指定名字 的 bundle 对象,再搜索具体资源,其中需要注意的方法 [NSBundle bundleForClass:<#ClassFromPodspec#>] 返回某个 class 对应的 bundle 对象。具体的:

    • 如果你的 pod 以 framework 形式被链接,那么返回这个 framework 的 bundle。
    • 如果以静态库(.a)的形式被链接,那么返回 client target 的 bundle,即 mainBundle。

    以下是如何访问:

    1
    2
    3
    4
    NSBundle *bundle = [NSBundle bundleForClass:[MYSomeClass class]];
    NSURL *bundleURL = [bundle URLForResource:@"YXPlayerSDK" withExtension:@"bundle"];
    NSBundle *resourceBundle = [NSBundle bundleWithURL: bundleURL];
    UIImage *img = [UIImage imageNamed:icon inBundle:bundle compatibleWithTraitCollection:nil];

    具体分析见 Pod 添加资源文件

然后提交整个项目代码push到远程分支,并打个tag推送到远程分支,这个tag必须和podspec文件的s.source = { :git => “http://git.jc/elephant-ios-component/YXPlayerSDK.git“, :tag => “0.0.2” }中tag一致。

4、编辑podspec文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Pod::Spec.new do |s|
#名称
s.name = "YXPlayerSDK"
#版本号
s.version = "0.1.0"
#简短介绍
s.summary = "Just Testing."
#详细介绍
s.description = <<-DESC
YXPlayerSDK description
DESC
#主页,这里要填写可以访问到的地址,不然验证不通过
s.homepage = "http://git.jc/elephant-ios-component/YXPlayerSDK.git"
#截图
# s.screenshots = "www.example.com/screenshots_1", "www.example.com/screenshots_2"
#开源协议
s.license = 'MIT'
#作者信息
s.author = { 'muhuashanjin' => 'tanghy@gold-finance.com.cn' }
#项目地址,这里不支持ssh的地址,验证不通过,只支持HTTP和HTTPS,最好使用HTTPS
s.source = { :git => "http://git.jc/elephant-ios-component/YXPlayerSDK.git", :tag => "0.0.2" }
#多媒体介绍地址
# s.social_media_url = 'https://twitter.com/<twitter_username>'
#支持的平台及版本
s.platform = :ios, '8.0'
#是否使用ARC,如果指定具体文件,则具体的问题使用ARC
s.requires_arc = true
#代码源文件地址,**/*表示Classes目录及其子目录下所有文件,如果有多个目录下则用逗号分开,如果需要在项目中分组显示,这里也要做相应的设置
s.source_files = 'YXPlayerSDK/Classes/**/*'
#资源文件地址
s.resource_bundles = {
'YXPlayerSDK' => ['YXPlayerSDK/Assets/*.{storyboard,xib,xcassets,json,imageset,png}']
}
#公开头文件地址
s.public_header_files = 'YXPlayerSDK/Classes/**/*.h'
#该pod依赖的系统framework,多个用逗号隔开
s.frameworks = 'UIKit','CoreGraphics'
#该pod依赖的系统library,多个用逗号隔开
s.libraries = 'iconv','sqlite3','stdc++','z'
#第三方.a文件
s.vendored_libraries = 'YXPlayerSDK/Classes/ThirdParty/*.{a}'
#第三方frameworks文件
s.vendored_frameworks = ['YXPlayerSDK/Classes/BaiduMap_IOSSDK_v3.0.0_Lib/BaiduMapAPI_Base.framework',
'YXPlayerSDK/Classes/BaiduMap_IOSSDK_v3.0.0_Lib/BaiduMapAPI_Location.framework']
#依赖关系,该项目所依赖的其他库,如果有多个需要填写多个s.dependency
s.dependency 'AFNetworking', '~> 2.3'
end

这里强调一下 s.summary 必填,s.description 不是必填,但如果填写了 s.description,它的长度必须比 s.summary 长且字符串不能一样。

如果要添加更多的模块到YXPlayerSDK之中,包括工具类,底层Model及类目扩展等,就需要subspec功能,给YXPlayerSDK创建了多个子分支。

具体做法是先将源文件添加到YXPlayerSDK/Classes中,然后按照不同的模块对文件目录进行整理,假如我有2个模块,所以在YXPlayerSDK/Classes下会创建了2个子目录,注释podspec文件的s.source_files和s.dependency,修改如下:

1
2
3
4
5
6
7
8
9
10
11
s.subspec 'TestOne' do |testOne|
testOne.source_files = 'YXPlayerSDK/Classes/testOne/*'
testOne.public_header_files = 'YXPlayerSDK/Classes/testOne/**/*.h'
testOne.dependency 'YXPlayerSDK/testTwo'
end
s.subspec 'TestTwo' do |testTwo|
testTwo.source_files = 'YXPlayerSDK/Classes/testTwo/*'
testTwo.public_header_files = 'YXPlayerSDK/Classes/testTwo/**/*.h'
testTwo.dependency 'YXPlayerSDK/TestOne'
end

完成后,进入Example文件夹执行pod install命令,会看到目录Development Pods/YXPlayerSDK下多了两个文件夹TestOne和TestTwo,目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
YXPlayerSDK
├── YXPlayerSDK
│ ├── Classes
│ ├── TestOne
│ └── TestTwo
├── TestTwo
├── Resources
└── Support Files
||
pod install 之后,目录结构变化
||
YXPlayerSDK
├── TestOne
│ ├── YXPlayerSDK
│ ├── Classes
│ ├── TestOne
├── TestTwo
├── Resources
└── Support Files

由此创建了subspec所以项目整体的依赖dependency,源文件source_files,头文件public_header_files,资源文件resource等都移动到了各自的subspec中,每个subspec之间也可以有相互的依赖关系(只能单向依赖)如:

1
testTwo.dependency 'YXPlayerSDK/TestOne'

这样分为多个subspec,在项目pod就可以使用其中的某个subspec了,如下:

1
2
3
pod 'YXPlayerSDK/TestOne'
或者
pod 'YXPlayerSDK',:subspecs=>['TestOne','TestTwo']

如果已经有了现成的项目,那么就需要给这个项目创建一个podspec文件,创建它需要执行Cocoapods的另外一个命令:

1
$ pod spec create PodTestLibrary git@coding.net:wtlucky/podTestLibrary.git

5、提交pod组件到Spec Repo中

先验证编辑好的podsepc文件,如果因为有无关紧要的警告而未通过检查,则输入以下命令加上--allow-warnings ,如果想要输出编译过程,加上--verbose

1
$ pod lib lint --allow-warnings --verbose

如果私有库里面引用静态库会导致验证是无法通过的,报错 include of non-modular header inside framework module ··· [-Werror,-Wnon-modular-include-in-framework-module] ,加上--use-libraries

然后还可以本地测试podspec文件,创建一个新的项目,在这个项目的Podfile文件中直接指定刚才创建编辑好的podspec文件,看是否可用, 在Podfile中可以这样编辑:

1
pod 'YXPlayerSDK', :path => '~/Desktop/git/JC_LivePod/YXPlayerSDK' #指定路径

编辑完成后,执行pod install命令安装依赖,可以看到YXPlayerSDK跟测试项目一样存在于Development Pods/PodTestLibrary中。

没有自身的WARNING或者ERROR之后,就可以再次提交到Spec Repo中,命令如下:

1
$ pod repo push JCLiveIOSSpecs YXPlayerSDK.podspec --allow-warnings

之后到~/.cocoapods/repos/JCLiveIOSSpecs目录下查看,也使用pod search命令查看自己库:

1
2
3
4
5
6
7
8
$ pod search YXPlayerSDK
-> YXPlayerSDK (0.0.4)
YXPlayerSDK
pod 'YXPlayerSDK', '~> 0.0.4'
- Homepage: http://git.jc/elephant-ios-component/YXPlayerSDK.git
- Source: http://git.jc/elephant-ios-component/YXPlayerSDK.git
- Versions: 0.0.4, 0.0.2, 0.0.1 [JCLiveIOSSpecs repo]
(END)

这里特别强调一下,如果 s.dependency 中依赖的是 pod 私有库的话,验证和提交命令都需要指定 source :

1
2
3
4
5
#验证
pod lib lint --sources=http://git.jc/elephant-ios-component/JCLiveIOSSpecs.git,https://github.com/CocoaPods/Specs.git --allow-warnings
#提交
pod repo push JCLiveIOSSpecs YXPlayerSDK.podspec --sources=http://git.jc/elephant-ios-component/JCLiveIOSSpecs.git,https://github.com/CocoaPods/Specs.git --allow-warnings

6、项目中导入pod私有库组件

当你执行 pod install 的时候,CocoaPods 默认只会在master下搜索,而我们的spec是存在我们私有的JCLiveIOSSpecs目录下的,所以需要在Podfile中指定搜索路径,在文件顶部中如下两行代码:

1
2
source "https://github.com/CocoaPods/Specs.git" #官方仓库地址
source "http://git.jc/elephant-ios-component/JCLiveIOSSpecs.git" #私有仓库地址

在指定pod的私有库组件时有一个坑,如pod ‘YXBase’, ‘~> a.b.1’,cocoaPods实际pod组件的版本为a.b.x(x为当前版本库中最大值)。但如何pod指定版本,这就需要修改Podfile.lock文件中PODS:YXBase的版本号和Podfile文件中YXBase版本号一致

7、更新本地cocoapods的spec资源配置信息

pod install 时报错:

1
None of your spec sources contain a spec satisfying the dependencies: AFNetworking (~> 3.1.0), AFNetworking (= 3.1.0)

这句话的意思是说:你spec资源中不包含AFNetworking的3.1.0的配置信息。这里面有个关键词,spec资源和配置信息。所以需要更新本地cocoapods的spec资源配置信息,使用命令:

1
$ pod repo update

pod repo update实际是更新整个.cocoapods下的所有库,其实我们可以只更新其中某个库来达到快速可用的目的。下面提供两个方法解决:

(1)正规方法:
指定更新单独库pod repo update /Users/<user>/.cocoapods/repos/master/Specs/<lib name>

(2)野路子:
如果方法1仍然无法解决问题,而又着急使用。可以直接到相应目录下手动增加缺少的版本目录和spec文件,/Users/<user>/.cocoapods/repos/master/Specs/<lib name>/3.2.0/<lib name>.spec。spec文件参考git上相应库的版本。

8、Podfile文件中use_frameworks!

  • 在Podfile文件里不使用 use_frameworks! 则是会生成相应的 .a文件(静态链接库),通过 static libraries 这个方式来管理pod的代码。Linked:libPods-xxx.a包含了其它用pod导入的第三方框架的.a文件
  • 在Podfile文件里使用了use_frameworks! 则cocoapods 会生成相应的 .frameworks文件(动态链接库:实际内容为 Header + 动态链接库 + 资源文件),使用 dynamic frameworks 来取代 static libraries 方式。Linked:Pods_xxx.framework包含了其它用pod导入的第三方框架的.framework文件。

  • 用cocoapods 导入swift 框架 到 swift项目和OC项目都必须要 use_frameworks!

  • 使用 dynamic frameworks,必须要在Podfile文件中添加 use_frameworks!

    具体分析地址:Podfile中的 use_frameworks!

9、Podfile和target

Podfile本质上是用来描述Xcode工程中的targets用的。如果不显式指定Podfile对应的target,CocoaPods会创建一个名称为default的隐式target,会和工程中的第一个target相对应。换句话说,如果在Podfile中没有指定target,那么只有工程里的第一个target能够使用Podfile中描述的Pods依赖库。

如果想在一个Podfile中同时描述project中的多个target,根据需求的不同,可以有不同的实现方式:

  • 多个target中使用相同的Pods依赖库

    1
    2
    3
    4
    5
    6
    7
    link_with 'target1','target2'
    platform :ios
    pod 'SDWebimage', '~> 1.1.1'
    platform :ios ,'7.0'
    pod 'AFNetworking', '~> 2.3.1'
  • 不同的target使用完全不同的Pods依赖库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    target :'target1' do
    platform :ios
    pod 'SDWebimage', '~> 1.1.1'
    end
    target :'target2' do
    platform :ios ,'7.0'
    pod 'AFNetworking', '~> 2.3.1'
    end
    其中,do/end作为开始和结束标识符。

参考

Cocoapods使用私有库中遇到的坑