基于CocoaPods实现私有库的组件化

emmmmm,打开博客、社区很多都是说组件化。。。所以现在参考着各路大神的博客&思路自己也跟着实现一下。

什么是组件化?

对于什么是组件化,我的概念其实是模糊的,不敢说大话,怕误人子弟,只能按照自己的理解说说:组件化跟模块化有着本质的相似,模块化又是什么呢?举个例子,例如微信,有微信通讯录发现四个业务大模块。那么模块化就是根据业务的不同一个一个来划分模块。每个程序员单独开发一个模块,其他模块不需要他管理维护甚至开发,提供个接口就可以了,这样就简单实现模块化了。模块也有大模块和小模块之分,不仅仅表现于大模块,每一个view或者每一个功能都是模块。

组件化的思想也是一样的,组件化也是根据业务或者功能来划分的,例如:工程上的请求库,如果使用是AFN,必然要封装多几层吧,这样既解耦又容易抽取第三方库。把封装的代码与第三方库集成一起作为请求网络数据的组件。上述例子是按照功能划分的,也可以往大的方向划分,例如苹果公司:Mac是一个组件、iPhone是一个组件、iPod是一个组件等等。

为什么要组件化?

  • 提高开发效率,快速编译
  • 功能业务划分清晰
  • 有效地针对性测试
  • 复用性好

想想,工程业务增多、开发人员增多,项目变得冗肿庞大,单单拿编译来讲,编译运行极其的费时,有没有想过仅仅是调整一下view的背景颜色,都需要编译整个项目?有没有想过开发的模块其实跟其他模块一点都没关系的,而我又需要编译其他模块?所以实现组件化还是必要的,这样即避免上述问题又有效解决代码合并的冲突。

这里可能会有一个疑问?组件化是根据业务功能划分的,还是在工程内还是需要编译的啊,怎么提高编译速度了呢?CocoaPods的相关好处就体现了,这篇文章就是基于CocoaPods来实现组件化的,使用过CocoaPods的都知道,项目需要引用哪些库,只要pod install相关的库就好了。因为是静态库,也不要重复编译,所以提高编译速度。

如何使用CocoaPods来组件化?

组件化分为:

  • 私有的(一般是公司内部自己构建的库,不对外开放)
  • 公开的(开源库,像GitHub上的都是公开的库)

文章围绕这两个方向来编写,私有库是基于码云,开源库是基于GitHub。

私有库组件化

创建索引库

为什么要创建索引库?作用是做索引,为真正代码库的做索引的。私有库使用国内的代码管理平台:码云

新建索引项目

image

image

image

把索引库添加到本地CocoaPods仓库
查看本地的CocoaPods仓库
  • 可以使用文档进入该路径
1
~/.cocoapods/repos

本地的CocoaPods仓库,如下图:

image

  • 可以在终端使用命令:
1
pod repo

本地的CocoaPods仓库,如下图:

image

添加远程索引库

把刚才创建的索引库添加到CocoaPods仓库中,在terminal执行命令:

1
2
// pod repo add 索引库名称 索引库地址 
pod repo add DDCategorySpecs https://gitee.com/DaisukeIT/DDCategorySpecs.git

期间会让你输入账号密码,是你的码云账号和密码,依次输入回车即可。

image

可以通过上述查看索引库是否被添加到本地的CocoaPods仓库中,如果看到命令完成或者CocoaPods仓库中有,就证明是添加成功了。到这里,先把索引库放在一边,开始真正的组件库,也就是存放你要存放代码的库中。

创建私有的组件库

过程是创建索引库一样的,如图显示:

image

image

创建模板库

首先在在你要存放这个私有库的地方新建一个文件夹,例如我在桌面上新建一个文件夹名称叫:DDCategory。这个名字你可以自定义,不一定是根据库名来,当然按照组件库的名称最好,方便管理。
使用命令进入刚创建的文件夹中:

1
cd /Users/fty/Desktop/DDCategory

然后在terminal执行命令:

1
2
// pod lib create 组件名 
pod lib create DDCategory

image

image

image

上述配置可自由选择,执行命令后会自动打开工程,如下图所示:

image

删除ReplaceMe文件

image

添加内容到模板库上

进入组件库的路径:

image

添加内容:

image

image

修改Spec文件

在DDCategory.podspec文件中修改一下内容

image

1
2
3
4
s.summary          = 'DDCategory是分类工具组件'
s.homepage = 'https://gitee.com/DaisukeIT'
s.author = { '作者名称' => '联系方式' }
s.source = { :git => 'https://gitee.com/DaisukeIT/DDCategory.git', :tag => s.version.to_s }

为什么要修改呢,因为生成的可能跟你本地CocoaPods保存的账号绑定了,默认使用,例如我是GitHub的账号和地址,所以要改成对应的。

上传组件代码到码云上

提交代码

cd进入DDCategroty路径:

image

执行命令:

1
2
3
git add . 
git commit -m '第一次提交(自定义)'
git remote add origin https://gitee.com/DaisukeIT/DDCategory.git

image

1
2
3
// 第一次push如果报错的话可以加上-f 
// git push -f origin master
git push origin master

image

期间有可能出现输入码云的账号密码,依次输入回车即可。

打分支标签

执行命令:

1
2
git tag '0.1.0' 
git push --tags

0.1.0要跟DDCategory.podspec文件中的s.version对应。

image

提交podspec到私有索引库

验证spec文件
  • cd 进入DDCategory路径中
    image

  • 本地验证
    执行命令:

1
2
// 本地验证不会验证 s.source 中的tag 
pod lib lint
  • 远程验证
1
2
// 远程验证会验证 s.source中的tag,如果此时没有打上相应的标签则会报错 
pod spec lint

image

之所以出现警告是因为DDCategory.podspec没有配置好,当然你可以在命令后面加上--allow-warnings。但是这样不好,上面的配置是已经消除了警告的,按照上面的配置,这个是不会出现的。

提交podspec

执行命令:

1
2
// pod repo push 私有索引库名称 spec名称.podspec 
pod repo push DDCategorySpecs DDCategory.podspec

image

这样就提交成功了,现在去码云上看看是否真的提交成功了

image

看到这里证明是提交成功了。但是在我实践的过程经常遇到警告:

1
[!] The spec did not pass validation, due to 2 warnings (but you can use `--allow-warnings` to ignore them).

警告信息可以完善消除,也可以加上--allow-warnings来忽略:

1
pod repo push DDCategorySpecs DDCategory.podspec --allow-warnings

使用自己的组件库

创建工程&创建Profile文件
  • 创建工程
  • 创建Profile文件

进入工程路径:

1
cd /Users/fty/Desktop/Test

然后执行命令:

1
pod init

OMG,出现错误了。。。

1
[!] Oh no, an error occurred.

image

image

image

按照提示找到对应的issues,基本尝试过了,只有这个方法解决的上述问题:https://github.com/CocoaPods/CocoaPods/issues/7697

image

这样我pod init就成功了。

编辑Podfile文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
source 'https://github.com/CocoaPods/Specs.git'
source 'https://gitee.com/DaisukeIT/DDCategorySpecs.git'

# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'

target 'Test' do
# Uncomment the next line if you're using Swift or would like to use dynamic frameworks
# use_frameworks!

pod 'DDCategory', '~> 0.1.0'

target 'TestTests' do
inherit! :search_paths
# Pods for testing
end

target 'TestUITests' do
inherit! :search_paths
# Pods for testing
end

end

其中:

1
2
3
// 第一行是为了保证公有库的正常使用 source
source 'https://github.com/CocoaPods/Specs.git'
source 'https://gitee.com/DaisukeIT/DDCategorySpecs.git'
pod install

可能出现错误:

Test.xcodeproj Couldn’t load Test.xcodeproj because it is already opened from another project or workspace

退出xcode,重新打开工程即可。

image

image

到这里就完成了私有库的组件化。

更新远程私有库

作为一个好的组件,不可能一劳永逸,需要不断地修复优化。当业务增多或者功能拓展。也需要不断地进行升级维护。

  • cd进入本地库DDCategory路径中

image

  • 添加代码

这里以添加时间分类为例,添加一个时间分类到仓库中,如图所示:

image

  • 提交代码
    在terminal执行以下命令:
    1
    2
    3
    git add . 
    git commit -m '更新描述'
    git push origin master

image

image

版本更新

版本更新是作为当前仓库操作的一个标志,之前是0.1.0版本,只有NSString分类,现在要更新一个新的版本,作为NSDate、NSString两个分类的版本。当有人不需要NSDate分类这部分的代码,只需要指引到0.1.0版本即可。所以更新版本很重要。

执行命令:

1
2
git tag -a '新版本号' -m '注释' 
git push --tags

image

通过查看码云上的代码库看到有两个标签,说明更新完成:

image

修改Sepc文件

  • 修改podspec文件

打开DDCategory.podspec文件,修改版本号(版本号要与刚才添加的标签一致):

1
s.version          = '0.1.1'
  • 验证远程spec

前提一定要保证你的你的配置文件和代码正确,相关的库引用好,不好就GG了。我就经常碰到这种错误

image

1
[!] The spec did not pass validation, due to 3 errors and 2 warnings.

上面也出现这样的情况是因为存在警告而没有错误,可以忽略,但是现在的情况是出现error,是要解决相关的错误的。在我实践的过程大部分修复了相关的错误,甚至把代码都删除了,还是报同样错误,我的天啊,我都把代码删除了,你还不给我验证通过。于是各种百度、Google都是说解决相关的错误。直到找到说是重新发布release版本,OK,于是我重新提交代码,重新提交新的标签、重新验证,一路绿灯。所以要是你有什么错误解决了还是不能验证通过,那就:重新提交代码 -> 重新提交新的标签 -> 重新验证

1
pod spec lint

image

验证成功。

更新索引库

提交podspec文件执行命令:

1
2
// pod repo push 私有索引库名称 spec名称.podspec 
pod repo push DDCategorySpecs DDCategory.podspec

image

你会看到是0.1.3版本,因为之前没有注意到代码错误,就去验证以至于一直出错(上面的错误),所以重新提交新的版本,然后重新验证。

更新Example的使用

cd进入Example项目路径执行命令:

1
2
// --no-repo-update 不更新本地索引库 
pod update --no-repo-update

当然也可以使用:

1
pod install

image

依赖第三方库

在很多情况下,会引用到第三方的库,比如:一个请求组件需要依赖AFNetworking库。需要在spec文件中设置相关参数。

例如依赖AFNetworking:

1
s.dependency 'AFNetworking', '~> 2.3'

接着重复以上操作:提交代码 -> 远程验证 -> 打标签 -> 更新索引库

image

image

如图所示AFNetworking存在一些警告,使用--allow-warnings忽略。

Subspecs

什么是Subspecs?就是一个库中下面有N个子库,有时候我们只需要用到其中一个子库,而不需要把全部都要pod进来,这样不仅可以减少应用的体积,加快运行速度。

首先我们先用命令看看AFNetworking有什么Subspecs子库:

1
pod search 'AFNetworking'

image

从图上可以看到有5个Subspecs库,那么工程上只用到Reachability这个Subspecs,所以我们只需要在Podfile文件中这样写:

1
pod 'AFNetworking'  改为  pod 'AFNetworking/Reachability'

就可以单独使用Reachability功能。那么别人的库支持操作,是否我们也可以创建自己的Subspecs呢?

在创建Subspecs之前,因为例子需要,这里添加一个文件为Tool文件夹,存放工具类。如图所示:

image

  • Subspecs格式
1
2
3
s.subspec '子库名称' do |别名| 

end

因为需要分离出子库,需要在子库里分别指定,所以直接把原来的s.source_files和s.dependency都注释掉,然后这样写:

1
2
3
4
5
6
7
8
9
10
11
# s.source_files = 'DDCategory/Classes/**/*'
# s.dependency 'AFNetworking', '~> 2.3'

s.subspec 'Tool' do |t|
t.source_files = 'DDCategory/Classes/Tool/**/*'
t.dependency 'AFNetworking', '~> 2.3'
end

s.subspec 'CategoryTool' do |c|
c.source_files = 'DDCategory/Classes/CategoryTool/**/*'
end
  • 远程验证spec

验证的时候发现出现了一个错误:

1
- ERROR | [DDCategory/Tool,DDCategory/CategoryTool] file patterns: The `source_files` pattern did not match any file.

说找不到找到匹配的文件,是路径错误了。然后我就仔细分析一下,名称有没有写错,结果是写错了😯😯,好吧修正回来再次验证还是同样的错误,什么鬼?因为没有打标签,就算你改了podspec文件,服务器也会认为是上一个版本,所以发布新的版本,打个0.1.4版本,验证通过😆😆。

  • 查看构建Subspecs是否成功
1
pod search 'DDCategory'

image

很遗憾没有像AFNetworking那样可以看到相关的Subspecs,是不是没有成功呢?是不是因为是私有库而看不到呢?

测试:
在Profile文件中修改成:

1
pod 'DDCategory/Tool'

然后在终端执行:

1
pod install

image

发现是成功的。所以是因为私有库的原因?不好意思,我也不知道,如果有人知道请告诉我,谢谢。

资源文件处理

添加资源文件

在创建模板库的时候发现有两个文件夹:Assets、Classes。在之前也展示过吧代码是存放在Classes中,那么Assets就是存放资源文件的,如图所示添加资源到Assets中:

image

修改spec文件
1
2
3
s.resource_bundles = {
'DDCategory' => ['DDCategory/Assets/*']
}

这里把.png后缀去掉,因为图片可能是jpg或者其他,去掉表示任何文件类型都可以

本地安装测试
  • cd进入Example工程路径,pod intasll

image

  • 显示图片

我在Test工程中pod installDDCategory组件,在ViewController中添加UIImageView来显示图片资源:

1
2
3
4
5
6
7
8
9
10
11
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

UIImageView *view = [[UIImageView alloc] init];
UIImage *image = [UIImage imageNamed:@"share_88_qq_friend_normal"];
view.image = image;
view.frame = CGRectMake(100, 100, image.size.width, image.size.height);
[self.view addSubview:view];
}

image

结果不显示图片,肯定不显示了。因为imageNamed:方法加载路径不对。官方注释着imageNamed:加载的是main bundle中的资源。所以要加载组件库的bundle才行。而组件的bundle路径是在这里:

image

image

image

image

新建一个DDBundleTool工具类继承NSObject,来获取组件下的bundle,以及bundle下的图片资源,这里可能有人想说为什么不使用NSBundle分类就搞定的,我也尝试过,发现一个问题,因为底层需要self.class,而self.class指向的是NSBundle的库Foundation,并不是DDDCategory库,DDBundleTool的self.class则指向DDCategory库

.h文件:

1
2
3
4
5
6
7
8
9
10
@interface DDBundleTool : NSObject

/// 获取组件下的bundle
+ (NSBundle *)dd_currentMainBundle;
/// 获取组件下图片路径
+ (NSString *)dd_imagePathWithImageName:(NSString *)imageName;
/// 获取组件下图片
+ (UIImage *)dd_imageWithImageName:(NSString *)imageName;

@end

.m文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@implementation DDBundleTool
/// 获取组件下的bundle
+ (NSBundle *)dd_currentMainBundle{
// 获取的是DDCategory组件下的bundle
return [NSBundle bundleForClass:self.class];
}

/// 获取组件下图片路径
+ (NSString *)dd_imagePathWithImageName:(NSString *)imageName{
// 根据系统的比例获取2x、3x图
NSInteger scale = [[UIScreen mainScreen] scale];
NSBundle *currentBundle = [NSBundle bundleForClass:self.class];
NSString *name = [NSString stringWithFormat:@"%@@%zdx",imageName,scale];
NSString *dir = @"DDCategory.bundle";
NSString *path = [currentBundle pathForResource:name ofType:@"png" inDirectory:dir];
// 当scale == 3时,且没有提供3x倍图时,获取2x倍图
if (path == nil && scale != 2) {
name = [NSString stringWithFormat:@"%@@2x",imageName];
path = [currentBundle pathForResource:name ofType:@"png" inDirectory:dir];
}
return path;
}

@end

得到路径后新添加一个UIImage分类(UIImage+Bundle)来获取图片

1
2
3
4
5
6
7
8
9
10
11
12
#import "UIImage+Bundle.h"
#import "DDBundleTool.h"

@implementation UIImage (Bundle)

+ (instancetype)dd_imageWithName:(NSString *)imageName{

NSString *path = [DDBundleTool dd_imagePathWithImageName:imageName];
return path ? [UIImage imageWithContentsOfFile:path] : nil;
}

@end

那么在DDViewController中显示图片:

1
2
3
4
5
6
7
8
9
10
- (void)viewDidLoad {
[super viewDidLoad];

UIImage *image = [UIImage dd_imageWithName:@"share_88_qq_friend_normal"];
UIImageView *view = [[UIImageView alloc] init];
view.image = image;
view.frame = CGRectMake(100, 100, image.size.width, image.size.height);
[self.view addSubview:view];
}
@end

image

可能有人会问为什么加载的目录DDCategory.bundle写死,要加载每一个组件的图片,尽可能自己实现加载步骤,毕竟减少依赖性。

注意:记得每次修改组件中的代码,都是需要提交代码 -> 打标签 -> 修改spec对应的版本 -> 验证 -> 绑定spec这几个步骤的。测试工程中使用重新 pod install 对应的版本。

加载xib

添加xib

image

如果出现:Safe Area Layout Guide before iOS 9.0错误,修改xib这里:
image

本地加载xib测试
1
2
3
4
5
6
7
8
9
- (void)viewDidLoad {
[super viewDidLoad];

NSBundle *curBundle = [DDBundleTool dd_currentMainBundle];
TestView *testView = (TestView *)[curBundle loadNibNamed:@"TestView" owner:nil options:nil].firstObject;
testView.frame = CGRectMake(100, CGRectGetMaxY(view.frame), 100, 100);
[self.view addSubview:testView];

}

打印错误信息是:

1
Could not load the "share_88_qq_friend_normal.png" image referenced from a nib in the bundle with identifier "org.cocoapods.DDCategory"

发现图片在xib中引用的路径不对,所以修改路径:要在图片名称前加上DDCategory.bundle/

image

再次运行

image

修改spec文件

在pod中,xib不能当成源文件(即s.source_files),因为xib文件和bundle文件一样被视为资源文件,否则pod install之后会报错”Unable to run command ‘StripNIB xxx.nib’ - this target might include its own product”.所以必须要将xib放入资源文件中(即s.resources)

使用此方式后,虽然可以在不改变原xib任何代码的情况下直接使用,但是需要将xib中使用到的图片文件拷贝到当前项目的Assets.xcassets中

下图就是亲身经历:

image

因为新建的xib是放在Classes目录下的,所以在podSpec文件中添加:

1
s.resources = 'DDCategory/Classes/**/*.xib'
提交代码&打标签

提交代码:

1
2
3
git add . 
git commit -m '更新描述'
git push origin master

打标签:

1
2
git tag -a '新版本号' -m '注释' 
git push --tags

再次提醒:版本号要与spec的版本号对应。

远程验证
1
pod spec lint

如果验证中出现以下错误:

1
ERROR | [iOS] xcodebuild: Returned an unsuccessful exit code. You can use `--verbose` for more information.

根据提示找到真正的错误:

1
- NOTE  | xcodebuild:  DDCategory/DDCategory/Classes/TestView.xib:vUN-kp-3ea: error: Safe Area Layout Guide before iOS 9.0

因为Safe Area Layout Guide before的原因

image

所以在xib中把Safe Area Layout Guide before iOS 9.0取消即可。

安装测试

cd到Test工程,重新pod install,在ViewController中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)viewDidLoad {
[super viewDidLoad];

UIImage *image = [UIImage dd_imagePathWithName:@"share_88_qq_friend_normal" targetClass:self.class];
UIImageView *view = [[UIImageView alloc] init];
view.image = image;
view.frame = CGRectMake(100, 100, image.size.width, image.size.height);
[self.view addSubview:view];


NSBundle *curBundle = [NSBundle bundleForClass:self.class];
TestView *testView = (TestView *)[curBundle loadNibNamed:@"TestView" owner:nil options:nil].firstObject;
testView.frame = CGRectMake(100, CGRectGetMaxY(view.frame), 100, 100);
[self.view addSubview:testView];

}

image

补充subspec目录分层

上面说到subspec来实现目录分层,并没有很好地分类归纳,可以看看AFNetworking是怎样分层目录的:

image

现在整理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
48
49
50
51
52
53
54
55
56
57
58
59

Pod::Spec.new do |s|
# 库名称
s.name = 'DDCategory'
# 库版本
s.version = '0.1.7.7'
# 库摘要
s.summary = 'DDCategory是分类工具组件'
# 库描述
s.description = <<-DESC
TODO: Add long description of the pod here.
DESC
# 库主页
s.homepage = 'https://gitee.com/DaisukeIT'
# 库截图
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
# 库许可证
s.license = { :type => 'MIT', :file => 'LICENSE' }
# 库作者
s.author = { 'DaiSuke' => '1123047669@qq.com' }
# 库源路径
s.source = { :git => 'https://gitee.com/DaisukeIT/DDCategory.git', :tag => s.version.to_s }
# 库社会分享
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
# 库使用最低版本
s.ios.deployment_target = '8.0'

# 设置 源文件路径 => 不是整个工程的文件,而是自己封装的代码,以后别的工程引入,就会引入这里的代码。
s.source_files = 'DDCategory/Classes/DDCategory.h'
# 设置资源
#s.resources = 'DDCategory/Classes/TestView/*.xib'

# 库的资源
s.resource_bundles = {
'DDCategory' => ['DDCategory/Assets/*']
}
# 库公共头文件
s.public_header_files = 'DDCategory/Classes/DDCategory.h'
# 库需要的系统库
# s.frameworks = 'UIKit', 'MapKit'
# 库的需要依赖的第三方库
# s.dependency 'AFNetworking', '~> 2.3'

# 库的字库
s.subspec 'TestView' do |v|
v.source_files = 'DDCategory/Classes/TestView/*.{h,m}'
v.resources = 'DDCategory/Classes/TestView/*.xib'
end

s.subspec 'Tool' do |t|
t.source_files = 'DDCategory/Classes/Tool/*.{h,m}'
t.dependency 'AFNetworking', '~> 2.3'
end

s.subspec 'CategoryTool' do |c|
c.source_files = 'DDCategory/Classes/CategoryTool/*.{h,m}'
end

end

最后目录变成:

image

再次提醒:出现”Unable to run command ‘StripNIB xxx.nib’ - this target might include its own product”是因为找不到xib路径运行,所以要在podspec文件中指定资源路径。

use_frameworks!

不使用use_frameworks!

image

使用use_frameworks!

image

区别
  1. 静态库:(静态链接库)(.a)在编译时会将库copy一份到目标程序中,编译完成之后,目标程序不依赖外部的库,也可以运行。缺点是会使应用程序变大
  2. framework:实际上是一种打包方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发。

具体可参考:podfile中 use_frameworks! 和 #use_frameworks!区别

公开库组件化(Trunk方式)

Trunk注册

出于安全性等方面的考虑,CocoaPods团队把提交代码的方式改为trunk。
如果没有安装cocoapods请在终端执行以下命令:

1
sudo gem install cocoapods

注册trunk

1
2
3
// feng.daisuke@gmail.com是GitHub账号
// DaiSuke是GitHub名称
pod trunk register feng.daisuke@gmail.com 'DaiSuke' --verbose

image

–verbose参数是为了便于输出注册过程中的调试信息。执行上面的语句后,邮箱将会收到一封带有验证链接的邮件,如果没有请去垃圾箱找找,有可能被屏蔽了。点击邮件的链接就完成了trunk注册流程,因为电脑端没有翻墙我收不到Gmail的邮件,借助QQ邮箱客户端绑定来收取邮件:

image

到此已经验证完邮箱信息,按照提示返回terminal使用下面的命令可以向trunk服务器查询自己的注册信息:

1
pod trunk me

image

看到以上信息说明已经注册成功。

索引库

因为CocoaPods已经为我们创建好了,在Profile文件中,你经常看到这个:

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

没错,GitHub上实现CocoaPods都是基于这个就是索引库。

创建GitHub上的代码库

其实跟码云上基本一样
image

image

克隆代码到本地

在本地新建一个文件夹命名为DDCategoryGitHub

使用命令
1
2
3
cd 进入DDCategoryGitHub目录
// https://github.com/DaisukeZJY/DDCategory.git就是刚才创建DDCategory的路径
git clone https://github.com/DaisukeZJY/DDCategory.git

image

使用SourceTree等工具

image

创建podspec文件

在终端执行命令:

1
2
3
cd 进入DDCategory目录
// DDCategory是podspec名称,最好跟库名保持一致。类似:DDCategory.podspec
pod spec create DDCategory

image

当然你也可以拷贝别人的podspec文件到该目录下。

创建主要代码文件夹&demo

image

添加代码

为了跟码云的目录上一致在DDCategory下新建Classes目录,也方便管理。

image

配置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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#
# Be sure to run `pod spec lint DDCategory.podspec' to ensure this is a
# valid spec and to remove all comments including this before submitting the spec.
#
# To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html
# To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
#

Pod::Spec.new do |s|

# ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# These will help people to find your library, and whilst it
# can feel like a chore to fill in it's definitely to your advantage. The
# summary should be tweet-length, and the description more in depth.
#

s.name = "DDCategory"
s.version = "0.1.1"
s.summary = "A short description of DDCategory."

# 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
分类工具组件
DESC

s.homepage = "https://github.com/DaisukeZJY/DDCategory.git"
# s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif"


# ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# Licensing your code is important. See http://choosealicense.com for more info.
# CocoaPods will detect a license file if there is a named LICENSE*
# Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'.
#

#s.license = "MIT (example)"
s.license = "MIT"


# ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# Specify the authors of the library, with email addresses. Email addresses
# of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also
# accepts just a name if you'd rather not provide an email address.
#
# Specify a social_media_url where others can refer to, for example a twitter
# profile URL.
#

s.author = { "Daisuke" => "feng.daisuke@gmail.com" }
# Or just: s.author = "Daisuke"
# s.authors = { "Daisuke" => "feng.daisuke@gmail.com" }
# s.social_media_url = "http://twitter.com/Daisuke"

# ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# If this Pod runs only on iOS or OS X, then specify the platform and
# the deployment target. You can optionally include the target after the platform.
#

s.platform = :ios
# s.platform = :ios, "5.0"

# When using multiple platforms
s.ios.deployment_target = "8.0"
# s.osx.deployment_target = "10.7"
# s.watchos.deployment_target = "2.0"
# s.tvos.deployment_target = "9.0"


# ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# Specify the location from where the source should be retrieved.
# Supports git, hg, bzr, svn and HTTP.
#

s.source = { :git => "https://github.com/DaisukeZJY/DDCategory.git", :tag => "#{s.version}" }


# ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# CocoaPods is smart about how it includes source code. For source files
# giving a folder will include any swift, h, m, mm, c & cpp files.
# For header files it will include any header in the folder.
# Not including the public_header_files will make all headers public.
#

s.source_files = "DDCategory/Classes/*.{h,m}"
#s.exclude_files = "Classes/Exclude"

# 这个设置subspec才需要用到
#s.public_header_files = "DDCategory/Classes/DDCategory.h"


# ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# A list of resources included with the Pod. These are copied into the
# target bundle with a build phase script. Anything else will be cleaned.
# You can preserve files from being cleaned, please don't preserve
# non-essential files like tests, examples and documentation.
#

# s.resource = "icon.png"
# s.resources = "Resources/*.png"

# s.preserve_paths = "FilesToSave", "MoreFilesToSave"


# ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# Link your library with frameworks, or libraries. Libraries do not include
# the lib prefix of their name.
#

#s.framework = "Foundation"
s.frameworks = "Foundation", "UIKit"

# s.library = "iconv"
# s.libraries = "iconv", "xml2"


# ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# If your library depends on compiler flags you can set them in the xcconfig hash
# where they will only apply to your library. If you depend on other Podspecs
# you can include multiple dependencies to ensure it works.

s.requires_arc = true

# s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
# s.dependency "JSONKit", "~> 1.4"

end

提交代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 提交到本地缓存区 
git add .

// 提交到本地仓库
git commit -m '自定义描述'

// 查看远程仓库地址 (如果打印origin说明远程仓库已存在,下一步绑定远程地址就可以忽略)
// git remote

// 绑定远程地址
git remote add origin https://github.com/DaisukeZJY/DDCategory.git

// 推送自己代码到远程仓库
git push origin master

image

image

说明已经提交成功。

打标签

操作之前要注意的点是:

  • podspec文件中的s.version版本号要跟最新Tag一致
  • podspec文件中的s.source仓库地址也不能写错
1
2
3
4
// 0.1.0版本号一定要对应podspec文件中的s.version版本号
git tag -a '0.1.0' -m '这是0.1.0版本'
// 推送标签
git push --tags

验证spec

  • 本地验证
1
pod lib lint
  • 远程验证
1
pod spec lint

可在命令后添加--verbose来查看剧透报错信息。

image

验证通过,可能有疑惑为什么是0.1.1版本,因为我调整了目录结构,所以要重新打标签。

至于警告信息:

1
2
3
4
5
summary: The summary is not meaningful.
说是这个库的摘要描述的不是很有意义

description: The description is shorter than the summary.
描述比摘要的文字还要短

上传podspec文件

操作之前要注意的点是:

  • 必须cd 进入到podspec目录下,才能执行这个代码
1
pod trunk push DDCategory.podspec --allow-warnings

上面的命令实际做了三件事:
1、验证你的podspec文件是否合法,在trunk方式之前我们已经用了“pod lib lint”以及“pod spec lint”命令进行验证;
2、上传podspec文件到trunk服务器,最终还是会上传到https://github.com/CocoaPods/Specs中;
3、将上传的podspec文件转成json格式文件。

😯😯😯

1
[!] You (feng.daisuke@gmail.com) are not allowed to push new versions for this pod. The owners of this pod are dongjia_9251@126.com.

很遗憾这个库重命名了,DDCategory的拥有者是ongjia_9251@126.com。所以我查看了一下GitHub确实是的。
image

怎么办呢?

  • 重新创建新库(建议,因为能跟库名保持一致)
  • 修改名称

查询了一下DDDCategory名字可用,所以建议在创建一个新的pods组件时先看看有没有重命名。

经过修改名字,重新走流程下来:
image

😯😯😯又是别人的。

image

image

好吧,那我就DDDDCategory,这里我就不新建库,直接更改podspec文件名称:DDDDCategory.podspec。s.name要跟库名保持一致。

1
s.name         = "DDDDCategory"

再次上传:

image

🎉🎉🎉🎉🎉🎉

搜索库名

1
2
// pod search 自己仓库
pod search DDDDCategory

发现索引不到,其实已经上传到cocoapods上了,只不过需要重新更新索引文件。怎么更新pod索引文件?原理:

  1. pod setup成功后会生成~/Library/Caches/CocoaPods/search_index.json文件
  2. 把search_index.json文件文件删除
  3. 重新执行pod search,就会重新更新索引

重复上面的操作还是找不到,可能是代码还没审核过吧。

image

等待…..

podspec文件更新方法

有时你可能会遇到这种情况:执行pod trunk push操作后发现podspec文件的某个地方写错了,想更新一下。对于这种情况,我们可能会先尝试着在把podspec文件push一次。但是如果你的代码版本号没变(podspec里的version自然也没变)就会提示push失败,即使你更改了podspec的其他地方,pod也会认为这两个文件是同一个。 我目前为止找不到trunk的相关update接口,所以只能顺水推舟,更新源代码版本号(如:1.1.1->1.1.2),重新push version tag,然后再执行pod trunk push操作。

如何划分组件?

划分组件没有一个明确的标准,基本都是依靠着业务需求、功能划分等划分。业务上一般来说是根据不同业务线来划分,比如:发现个人中心两个业务线没有必然关系,可以划分两个不同的大组件;功能上一般来说请求库工具库基础SDK地图埋点加解密数据库缓存等划分。粒度性可以跟团队商量结合,粒度可大可小,按实际开发情况来。

参考文档

以上内容是我参考着以下文章来记录学习实际操作的过程,虽然结果也不尽人意,出现莫名其妙的错误,但是谢谢,对我非常有帮助。如有侵权请联系删除!
参考文章: