理論篇
什么是組件化
組件化開發就是將一個臃腫的、單一的項目,根據功能/業務/技術等等進行拆分,形成一個個獨立的功能組件,然后借助 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
是我們必須要掌握的,即使你不會使用命令,但是一定要熟悉相關的軟件。