理论篇
什么是组件化
组件化开发就是将一个臃肿的、单一的项目,根据功能/业务/技术等等进行拆分,形成一个个独立的功能组件,然后借助 CocoaPods 管理工具将其任意组合,集成一个完整的项目。
你可以将 AFNetworking、SDWebImage 等等三方库理解为自己项目的一部分,属于基础组件部分,而我们要做的就是将项目划分成多个独立功能模块,再集成为一个完整的项目。这一过程看似多此一举,但是带来的优势却是非常大。
为什么需要组件化
项目初期,功能相对简单,普通的MVC+模块文件分割就可以满足绝大部分的需求。但是随着功能需求越来越多,业务越来越复杂多样,现有的架构已经不太适用了,即使使用了 Git 分支管理,依然经常发生合并冲突等等问题,另外后期的维护成本也大大增加,业务逻辑变得复杂、模块之间耦合度很大、查找问题效率变低、项目编译过程过慢…… 而且伴随着开发人员的增多(多个小组之间协作开发),这些问题尤为突出,优化开发结构变得非常重要。
中间层
针对上面的问题,第一个想到的优化就是新增一个中间层来协调各个模块之间的调用,所有的模块都通过这个中间层去实现调用和交互,但是这样虽然一定程度上降低了模块与模块的之间的耦合度,但是耦合都转嫁到了中间层上了,并且中间层的改动只能由一个人操作,否则非常容易发生冲突,本质上并没有发生多少变化。另外一点,查找问题的效率低下、编译过慢等问题依旧没有得到有效的解决。
这是传统的中介者模式,这个中间层会依赖其他组件,其他组件也会依赖中间层完成服务。
组件化
组件化能够帮助我们将过大的项目拆解成数个小组件,开发者只需要关注于组件所依赖的其他组件,而无需关心完整项目的其他部分,每个组件可以自己采取所习惯的架构模式:MVC、MVVM、MVCS等等,就像开发一款个人独立的app那样自由。
项目组件化之后所带来的好处是非常多的,我们先总结一下非组件化所造成的问题:
非组件化:
- 代码高耦合度、高依赖
- 项目复杂、臃肿、编译过长(影响调试)
- 难以融合/集成其他产品
- 需要统一架构
……
组件化:
- 代码复用性提高,可方便的集成到其他项目
- 项目可配置,方便集成和功能回退(指定版本)
- 化整为零,将项目细小化
- 方便组件的并行开发
- 可方便做单元测试
- 组件自由度高,即插即用
……
当然组件化也有着它的缺点,对已有的项目实施组件化架构比较困难,耗费时间长,项目组成员需要一定学习成本;组件化并没有相应的标准,拆分的粒度要适中,拆分粒度过高,则让项目变得复杂,起到了反作用效果,反之,粒度过低,体现不了组件化的优势,在项目业务不断地添加的过程中,进行不断的尝试调整,找到适合自己项目的才是最好的。
组件化的分层
项目组件化中,最难把握的就是粒度问题,这需要开发的自己的经验去把控。这里只给出个人认为的层次的划分。
【基础组件】:宏定义/常量/自定义工具类,如常用的自定义分类
【功能组件】:项目中所用到的功能,如地图定位/消息推送/分享等
【业务组件】:项目中的模块/业务,如聊天室/直播间/个人中心等
【中间组件】:负责项目中的路由/消息通知/传参/回调等
【宿主工程】:项目容器,用来集成组件,调整各个组件之间的消息传递的容器。
中间层的几种方案
在组件化过程中,中间层是各个组件的通信的桥梁,中间层在组件化过程中扮演着非常重要的角色。目前关于中间层的设计笔者已知的有以下三种方式:基于URL Scheme
的路由、基于Runtime
的target-action
、面向接口。
- 路由
iOS 中支持的 URL Scheme
让我们能够在应用之间、应用内部传递消息。日常开发过程中经常用到的就是调用系统服务、唤起三方app等等,这些属于应用之间的消息传递,而我们这里借助 URL Scheme
完成应用内部的消息传递。这里的路由 URL 遵循网上通用的资源标识符合 URI,如:appscheme://home/scan?param=value,我们通过 URL
来传递信息,下层服务方通过 URL 获取参数提供服务,上层消费者通过 URL 获取到服务,完成调用。
基于 URL Scheme
的三方库
JLRoutes 是一种基于 URL Scheme 的路由框架,它全局会保存一个Map,key 是路由协议 url,value 则是对 url 解析后 block 回调,你可以在该回调中处理具体的业务。
实例:
例如我们的路由协议定义如下:
scheme://描述/打开方式/保留字段/功能标识?参数1=值1&参数2=值2
||
myroute://market/1/route/cjpm?stockcode=600212.ss
首先配置路由 url 和 对应的回调处理:
/// 默认下都会进入这里,这里填写路由匹配规则
[JLRoutes.globalRoutes addRoute:@"/market/:operate/route/:code" handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
NSLog(@"%@", parameters);
// 接下来的业务逻辑
return YES; // 返回YES,表示处理截止,后面的路由规则不再启用
}];
然后在需要路由的地方传入相应的路由 url :
// 某地方获取到的url
NSURL* url = [NSURL URLWithString:@"myroute://market/1/route/cjpm?stockcode=600212.ss"];
// 处理路由
[JLRoutes routeURL:url];
基于Runtime
的target-action
相比 url scheme
的提前注册、实现服务,CTMediator
借助 OC 运行时的特性,现实组件之间服务的自动发现,无需提前注册即可实现组件间的调用,因此,这种方案的可维护性、可读性、扩展性相对较高。
官方的 Demo 中,结构是这样的:
CTMediator 的使用流程大体是这样的:
底层组件
创建
Target_
开头的目标类,如Target_A
(该类是为了让中间件CTMedator
通过NSClassFromString
生成类),类中定义Action_
开头的可调用的方法(为了让中间件CTMedator
通过NSSelectorFromString
生成方法器),并且这些方法都有一个字典类型参数接收调用者传递过来的信息。创建
CTMedator
的分类(方便扩展、分块),此分类对应着Target_A
,分类中定义该组件对外(调用者)开放的 API 方法,该组件的开发者需要使用CTMedator
的核心方法performTarget:action:params:shouldcacheTarget:
完成方法调用。
上层组件
导入对应 CTMedator
的分类,完成方法调用。
相比传统的中介者模式,这种 target-action
方案解放了中间件对其他组件的依赖,因为它是通过 NSClassFromString
和 NSSelectorFromString
来生成类的实例和方法器SEL
的,然后介入消息的分发机制完成消息分发的,即所谓的主动发现服务。传统的中介者模式中,中间件和其他组件是双向依赖的:
target-action
方式则是单向依赖,这样做的一个好处就是降低了一定的耦合,在我们移除某个组件时,中间件无需进行改动。
那么,由于没有引入具体的类,而是通过字符生成对应的类和方法,那么关于 CTMedator
的分类要清楚的知道 Target_
类以及其中的内容。
CTMedator
的分类可以划分为一个组件,必要时,集成到项目中进行调用。
- 面向接口
Protocol
在路由和 target-action
方案中,都存在硬编码问题、参数不明确问题:URL
、Target_
、 Action_
的硬编码,参数都是通过字典的形式传递,类型不明确。
面向接口 的方式能够很好的解决这两个问题。面向接口的方案通常由两部分组成,一个是用来管理接口协议的类(ModuleManager
),一个是具体的接口协议(ComponentProtocol
)。
ModuleManager
负责消息的调用和转发,它内部需要存储一张映射表,完成 Protocol -> Class
的工作。ComponentProtocol
文件定义了业务组件可以提供的功能服务,可以将所有服务都定义到其中,也可以按组件划分。这样所有调用方只需要依赖中间件,不需要依赖其他的组件,而中间件通过接口协议绑定可以用于服务的类,即每个组件有一个用于实现对外提供的接口协议的类。在编译时,将对应的类注册到ModuleManager
中,Protocol 的名称即为查找的 key。
注册绑定:
[ModuleManager registerClass:User forProtocol:@protocol(UserProtocol)];
调用时通过接口协议从 ModuleManager 中映射出注册的 Class,将获取到的 Class 实例化,并调用协议方法完成服务调用。
Class cls = [[ModuleManager sharedInstance] classForProtocol:@protocol(UserProtocol)];
NSObject <UserProtocol> *user = [[cls alloc] init];
NSString *userName = [user getUserName];
接口协议的方式虽然可以很好的解决参数类型的不确定性,硬编码问题(实现部分可以任意替换),但是它不是前面两种的替代品,因为他们都有自己的侧重点,如 路由URL 可以在应用之间实现消息传递,面向接口可以用来为某类添加功能或者对类进行功能约束等。
一些注入框架是支持面向接口的注入的,可使用这些库取代 ModuleManager
类。
小结
三种方式都分为底层服务方和上层使用方,服务方都对外提供 了服务媒介,CTMediator
中是 Target_A
文件,面向接口就是 Protocol
,路由 URL Scheme
则是回调 block
。
在三种方式中,个人觉得最不推荐的是 CTMediator
方案,感觉很是臃肿,虽然可以通过多个分类去定义组件,但是实际上对底层组件的调用逻辑都耦合在了中间件中,这意味着中间件需要频繁的进行更新,另外存在太多的硬编码地方,target
、action
以及参数名都是硬编码在中间件中的,这样的方式并不灵活。但是 CTMediator
中通过运行时解耦了中间件对底层组件的依赖,以及去 model
化的想法还是非常好的。
面向接口 Protocol
的方案贯穿了底层组件、中间件以及上层组件,一方面解耦了中间件对底层组件的耦合,底层组件变得透明,可以根据接口协议任意替换,另一方面接口协议还确定了参数类型。但是该方案面向的是应用内部的功能通信,外部调用应用时,还是需要路由或者硬编码的形式完成。
路由定义了一套用于信息传递的标准,通过路由,服务方可以注册并实现符合某种特定条件的服务,使用方则通过中间件传递 一条URL
来调用该服务。服务方和使用方彼此透明,可以任意替换。和接口协议比起来,路由的可以处理本地内部和远程外部的两种类型的调用,缺点是 url
需要硬编码,而且参数类型都是字符。路由 URL 和接口协议都需要提前注册才能使用,路由需要 block
,接口协议需要 class
。
路由和接口协议并不冲突,可以使用路由 + 协议的方式来实现中间件,路由实现外部的调用,应用的降级处理等,组件之间通过接口协议来定义功能服务,这样组件内部可以在迭代中方便的替换实现类。
核心工具 CocoaPods
组件化架构,需要一个宿主工程,负责集成所有的组件。每个组件都是一个单独的工程,通过 Git 私有仓库来管理。这样拆分工程项目,开发人员只需要关注与组件相关的部分,而不用考虑其他组件,新人上手更容易。
所有的组件都上传到 Git
仓库并支持 Cocoapods
集成。主工程通过配置 Podfile
文件,然后一键 pod update
即可。使用 Cocoapods
来管理组件主要因为其本身功能强大,方便的集成整个项目,解放对依赖库的管理。使用组件化的集成方式,可以很好的避免传统项目中的代码冲突问题。
核心命令:
# 安装命令
sudo gem install cocoapods
# 配置
pod setup
# 通过Podfile安装三方库
pod install
# 通过Podfile更新安装三方库
pod update
- Git 常用操作命令 以及开发工具
Git 是一个分布式版本控制系统,能够快速高效地处理从小型到大型项目的所有内容。Git 官方文献资料。
当然,如果不想记住这些命令,你可以借助市场上的热门开发工具,这里推荐 Git 官方桌面端、Sourcetree。
Xcode 本身就支持项目的 Git 仓库管理,在 Source control
中就可以创建管理你的项目。
在你创建项目时,Xcode 就提示你是否创建 Git 仓库:
这里需要注意的就是 podspec
索引文件的编写。
Pod::Spec.new do |s|
s.name = '组件工程名'
s.version = '0.0.1'
s.summary = '简介'
s.homepage = '远程仓库地址'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { '作者' => '作者' }
s.source = { :git => '远程仓库地址', :tag => s.version }
s.ios.deployment_target = '8.0'
s.source_files = 'Classes/**/*.{swift,h,m,c}'
s.resources = 'Assets/*'
s.dependency 'AFNetworking', '~> 2.3'
s.dependency 'Reachability','~> 3.2
end
source_files:是你要共享的文件
resources:是一些资源文件,比如图片资源
dependency:是该组件所需要依赖的其他组件、三方组件等。
关于创建子模块/子文件夹
//简单:
subspec 'Twitter' do |sp|
sp.source_files = 'Classes/Twitter' //指定子模块路径
end
subspec 'Pinboard' do |sp|
sp.source_files = 'Classes/Pinboard'
end
//复杂:
Pod::Spec.new do |s|
s.name = 'RestKit'
s.subspec 'Core' do |cs|
cs.dependency 'RestKit/ObjectMapping'
cs.dependency 'RestKit/Network'
cs.dependency 'RestKit/CoreData'
end
s.subspec 'ObjectMapping' do |os|
end
end
更多内容参考 基础-podSpec使用。
典型的产品
- 滴滴
滴滴的组件化是将项目拆分为业务部分和技术部分,业务部分包括专车、拼车、巴士等组件,使用一个 pods 管理,技术部分则分为登陆分享、网络、缓存等基础组件,分别使用不同的 pods 管理。
组件间的通信通过 ONERouter
中间件进行通信,中间件担负协调和调用各个组件的责任。组件间通信通过 OpenURL
方法来进行对应的调用。ONERouter
内部保存一份 Class - URL 的映射表,通过 URL 找到 Class 并发起调用, Class 的注册放在 +load
中进行。
- 淘宝
淘宝架构的核心思想是一切皆组件,将工程中所有代码都抽象为组件。在 CocoaPods 中可以通过 podfile 很好的配置各个组件,包括组件的增加和删除,以及控制某个组件的版本。
淘宝架构的主要分为四层,从上到下依次是:业务组件 -> 核心层 /容器-> 中间件/功能封装 -> 底层库。容器是整个架构的核心,负责组件间的调度和消息派发。
总线设计:URL 路由 + 服务 + 消息。统一所有组件的通信标准,各个业务间通过总线进行通信。
URL 路由
路由 URL 统一对三端的行为进行了统一,一套 URL 就可以调起 iOS、Android、前端三个平台的对应组件。
URL 路由请求可以被解析就直接调起相应的组件,如果不能被解析(没有对应的组件)就跳转 H5 页面,这称为降级处理。
服务
服务提供一些公共服务,是面向接口的,通过接口协议 Protocol
进行调用。
消息
URL 路由通常都是一对一进行通信,那么针对一对多的消息派发和调度就可以通过消息完成,这类似于 iOS 的通知机制。例如应用的前后台切换、Socket的推送消息等,都可以通过消息机制分发到接收消息的组件。
小结
我们可以看到,滴滴和淘宝的组件化上有很多的相似之处,组件化的核心工具 CocoaPods,URL 路由进行页面的路由跳转,其他的如接口协议、消息通知等,应该都有类似的解决方法。除了管理组件的核心 CocoaPods 工具,URL 路由、接口协议服务、消息通知等都是我们在组件化过程中使用到的利器。
总结
组件化开发就是将项目进行拆分成一个个独立的功能组件,然后将其组合成一个完整的项目。那么,如何拆分?组件如何通信?如何组合?都是我们要考虑的问题。关于分层和拆分的粒度都没有标准化的,需要开发者根据以往已经合理的进行规范。组件间的通信有多种方式,这里比较推崇淘宝的架构,路由 + 服务 + 消息的形式实现多种方式的通信。组件化的核心工具就是 CocoaPods
,我们要做的就是将组件项目上传到 Git 或者码云,编写项目的 podSpec
文件让组件支持 CocoaPods
集成 即可。CocoaPods
的功能十分强大,即使非组件化项目,我们同样使用它来管理依赖库,安装、卸载、升级、降级等,只需要一个命令即可完成,作为开发者,这个工具是必定要掌握的。
参考
实践篇
上一章节中,我们简单介绍了以下组件化的概念、使用到的工具等,这一章节中我们来演示一个组件如何制作。
组件的创建
首先我们来为项目创建一个关于网络请求的功能组件 LLNetworking
。
- 拉取模版
我们将创建在桌面上的一个名为 Demo
文件夹中。通过终端进入到该文件夹下,然后输入命令:
pod lib create LLNetworking
这个命令会为了拉取 Pod 的 基础模板。拉取之后,还会通过询问的形式为你配置一些东西:
// 作用的平台
What platform do you want to use?? [ iOS / macOS ]
> iOS
// 语言环境
What language do you want to use?? [ Swift / ObjC ]
> ObjC
// 是否需要一个 demo 用来测试组件
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 ]
> Yes
// 组件中,文件的前缀
What is your class prefix?
> LL
确认之后,系统会为你自动配置组件项目,创建好的项目如下:
- Example 工程
项目文件目录中存在一个名为 Example
的工程,这个工程是你选择 Would you like to include a demo application with your library?
中选择 Yes
时为你添加的,这个还是很有用的,在你开发过程中可以通过它来集成测试组件功能的正确性、完整性。 我们先打开这个 Example
来看下:
这个 Example
已经为你的组件创建了索引文件 podspec
,并且集成了该组件。我们来看下 Example
的 Podfile
的内容:
use_frameworks!
platform :ios, '8.0'
target 'LLNetworking_Example' do
pod 'LLNetworking', :path => '../'
target 'LLNetworking_Tests' do
inherit! :search_paths
pod 'FBSnapshotTestCase'
end
end
其中为你集成了一个测试用例 pod 'FBSnapshotTestCase'
,目前可以忽略。
我们可以看到: pod 'LLNetworking', :path => '../'
这一句,path
路径指向了本地路径,对应 LLNetworking
主目录下:
这个文件夹下,一个存放你的各种类文件,一个存放图片资源等。
- podspec 文件
在你回答之前问题之后,pod 为你自动创建了该文件,并执行了 pod install
命令,该命令会找到组件的索引文件(也在本地) LLNetworking.podspec
:
#
# Be sure to run `pod lib lint LLNetworking.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'LLNetworking'
s.version = '0.1.0'
s.summary = 'A short description of LLNetworking.'
# 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/LOLITA0164/LLNetworking'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'LOLITA0164' => '476512340@qq.com' }
s.source = { :git => 'https://github.com/LOLITA0164/LLNetworking.git', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
s.ios.deployment_target = '8.0'
s.source_files = 'LLNetworking/Classes/**/*'
# s.resource_bundles = {
# 'LLNetworking' => ['LLNetworking/Assets/*.png']
# }
# s.public_header_files = 'Pod/Classes/**/*.h'
# s.frameworks = 'UIKit', 'MapKit'
# s.dependency 'AFNetworking', '~> 2.3'
end
该文件为你的组件自动配置了一些基本的信息,因为我之前使用过 trunk 登陆过,所以这里有的的账号信息。当然这些信息是需要你根据情况修改的,更多的配置你可以搜索相关文档。
注意:这里的 Git 地址目前是找不到的,后期需要自己关联。
设置共享文件
podspec
文件中 s.source_files = 'LLNetworking/Classes/**/*'
指代共享的资源路径,我们需要将共享的文件放到这里来。
我们打开组件的目录查看,可以看到这里已经有了名为 ReplaceMe
的文件了,这暗示你用共享文件替换它。
podspec
文件中还有一个被注释掉的:
# s.resource_bundles = {
# 'LLNetworking' => ['LLNetworking/Assets/*.png']
# }
这个目录中存放一些图片等资源,当你需要的时候可以开启来。
我们来创建一个 LLNetworking 类:
@interface LLNetworking : NSObject
-(NSString*)getSomething;
@end
@implementation LLNetworking
-(NSString *)getSomething{
return @"test method.";
}
@end
将其移动到组件的共享目录下并删除掉空文件ReplaceMe
:
这样,我们就设置好了共享的内容,即组件开发好了。接下来,我们使用 Example
工程来使用这个组件的内容。
终端进入 Example
工程目录下,执行 pod install
命令来安装组件。
我们发现,Example
项目中 Pods/Development Pods/LLNetworking
下,多出来最新添加的文件。
使用组件
我们安装好组件之后来使用一下组件的功能,就像使用三方库那样:
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
LLNetworking * networking = LLNetworking.new;
NSLog(@"%@",networking.getSomething);
}
控制台输出:
2019-11-08 17:14:47.455341+0800 LLNetworking_Example[7038:1682304] test method.
这表示功能正常。
在组件开发过程中,使用 pod 'LLNetworking', :path => '../' 将路径指向本地是很有必要的,方便测试你的组件配置是否正确,功能是否完善,相比推到远程、发布再集成,这方便太多了。
三方依赖库
有时候,我们的组件还依赖其他的组件,又或者是三方库。我们通过 s.dependency 字段去设置,多个库可以分开写多次。
在 Podfiles 模版里最后一条已经为我们添加好了,所依赖的是 AFNetworking ,正好是我们网络请求组件所依赖的,我们把它开启,重新 pod install :
Analyzing dependencies
Fetching podspec for `LLNetworking` from `../`
Downloading dependencies
Installing AFNetworking (2.7.0)
……
我们发现,Example
自动拉取了组件 LLNetworking
所依赖的其他组件。CocoaPods
工具的另外一个优点就是,多个组件依赖同一个组件时,它会自动帮你检测安装,而不会重复导入。
我们发现 Example
工程的 Pods
中,本地开发的组件和远程发布的组件被分别放在了不同的目录下。
有了 AFNetworking
之后,你就可以修改你的网络请求组件了:
#import <AFNetworking/AFNetworking.h>
@interface LLNetworking : NSObject
@property(strong,nonatomic)NSURLSessionDataTask *task;
- (NSURLSessionDataTask *)POSTWithURLString:(NSString *)URLString parameters:(id)parameters success:(void (^)(id responseObject))success failure:(void (^)(id error))failure;
@end
@implementation LLNetworking
- (NSURLSessionDataTask *)POSTWithURLString:(NSString *)URLString parameters:(id)parameters success:(void (^)(id responseObject))success failure:(void (^)(id error))failure{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.requestSerializer.timeoutInterval = 20;
_task = [manager POST:URLString parameters:parameters success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (success) {
success(@{@"status":@"success"});
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (failure) {
failure(@{@"status":@"failure"});
}
}];
return _task;
}
@end
修改好之后,还不能直接在 Example 中使用,需要卸载组件再重新安装。注释掉 pod 'LLNetworking', :path => '../' 之后执行 pod install 即可完成卸载。
至此,你完成了组件的创建、文件共享、本地化测试使用和更新。但是,我们的组件毕竟是要服务于宿主工程的,如果仅仅只能是通过本地集成,那意义不大,我们要将其关联到远程服务器,推送到本地搭建的 GitLab,又或者是 GitHub、码云、Coding 等平台。
关联远程仓库
在模版 podspec 文件中,已经帮我们指定了一个 GitHub 的仓库地址,
s.homepage = 'https://github.com/LOLITA0164/LLNetworking'
你可以使用它或者进行修改它。我们这里选择使用它,先去 GitHub
创建对应的仓库。
在最初创建组件时,系统已经帮我们创建好了本地 Git 仓库,进入到项目中,显示出隐藏文件夹就可以看到(command+shift+. 显隐):
如果没有,你可以使用命令 git init
创建一个。现在,我们要将之前的修改进行提交(本地提交)。
git commit -am "第一次提交"
然后我们要把本地的 Git 仓库和刚刚创建的远程仓库进行关联。如何关联呢?你在网站上创建项目后有了这样的提示:
这里有三种:创建一个新的仓库,推送一个已存在的仓库以及从其他仓库导入。我们这里使用第二种即可。
添加远程仓库:
git remote add origin https://github.com/LOLITA0164/LLNetworking.git
将本地内容推送到远程仓库:
git push -u origin master
可能会出现让你登陆验证,输入你的用户名和密码即可。出现以下信息即表示推送成功。
remote: Resolving deltas: 100% (49/49), done.
To https://github.com/LOLITA0164/LLNetworking.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
回到 GitHub 刷新一下即可看到你的提交记录。
上述是通过终端命令进行 git 操作,如果你并不熟悉 git 命令,你大可以使用便捷的可视化工具(上一章节有所提及),仅需简单的点击操作即可完成项目的管理。
打 tag 并发布到 Cocoapods
打标签
至此,我们已经成功的将本地仓库关联并推送到远程仓库,现在我们要发布一个可用的组件。
首先我们要给当前项目打一个 tag 版本号,在 podspec 中:
s.version = '0.1.0'
指定的版本号是 0.1.0,那么我们就同样打个 0.1.0 的 tag:
$ git tag 0.1.0 // 打 tag
$ git push --tags // 推送到远程
打 tag
默认在当前分支上,因为这里只有 master
,所以不用切换分支,如果后期有其他分支,注意别弄错了。
刷新页面,项目的 release
选项中会出现刚刚打的版本。
你也可以直接在页面的 release
下添加新的 tag
,点击 release
可以看到编辑页面:
发布到 CocoaPods
由于我们创建的项目以及标签的版本号都是沿用了 podspec
文件中的信息,因此可以直接验证 podspec
文件信息是否可以通过验证,如果需要调整,调整之后最好同样先验证:
pod spec lint
podspec
文件的版本号一定要和tag
保持一致。
如果通过验证,那么你会看到类似下面的提示,绿色的 passed validation
:
现在可以将 podspec
文件提交到 CocoaPods
上了:
首先要通过 trunk
注册生成一条会话:
// pod trunk register 邮箱 用户名 描述
pod trunk register 476512340@qq.com LOLITA0164 --description=组件化demo
然后去邮箱进行验证,验证成功会出现下面页面:
现在,就可以将 podspec
提交给 CocoaPods
了。这个文件将是别人搜索你的组件的索引。
pod trunk push LLNetworking.podspec --allow-warnings
上传完成之后,接可以通过 pod search LLNetworking 搜索到自己的组件了,如果搜索不到,删除本地的搜索文件,命令 :
rm ~/Library/Caches/CocoaPods/search_index.json
重新 search
产生新的搜索文件。
发布新版本则需要打新的
tag
,重新编辑podspec
文件,然后再次提交给CocoaPods
。
集成到宿主工程
我们已经完成了网络组件的创建和发布,也支持了 CocoaPods
的集成。现在我们需要将该组件集成到宿主工程中去,这部分没什么好提的,因为使用方式和集成三方库是一样的,可以说三方库只不过是他人编写的功能组件而已,我们的组件同样可以提供给小组成员使用,相比于纯粹的三方库,我们的许多组件都关联了业务部分,或者基于私人的其他组件,因此适用范围较小。
小结
本章节先介绍了如何通过 pod 的模版工程创建组件,组件的配置,集成本地组件,然后介绍了远程仓库的关联,支持 CocoaPods
的集成等内容,学会了这些,你就可以将自己得意的功能库提供给他人使用。在组件化的过程中,Git
是我们必须要掌握的,即使你不会使用命令,但是一定要熟悉相关的软件。