第二篇主要講以下幾點:
1、創(chuàng)建私有庫。
2、對主工程進行組件化改造(主工程的邏輯有viewController->A->B)
3、將主工程的A頁面做成A組件,解耦主工程和A頁面
4、使用CTMediator方案(target-action)進行組件化搭建(核心在于每個組件的category可以對組件和主功能之間的解耦操作)
5、最終的效果是完成主工程與A組件的通信,A組件和主工程中B頁面的通信
tips:當前組件化方案純oc版本,swift還多幾個工程文件,這一期不涉及;另外github使用ssh的話,需要設(shè)置一個sshkey,這一期不涉及,直接百度就好,或者我有時間了也寫一下;
一、準備工作
1、首先要搞清楚為什么創(chuàng)建私有庫?
創(chuàng)建私有庫的目的是為了讓團隊成員可以共享私有庫組件,項目用到該組件的時候可以直接pod下來組件及其category,下面以github為例,創(chuàng)建私有庫,并且對不同工程間的關(guān)系進行說明
2、如何創(chuàng)建私有庫

然后點擊下面的CreateRepository即可。
3、打開終端,本地建立一個私有倉庫并關(guān)聯(lián)github上的私有倉庫LSDRepo,
pod repo add [私有Pod源倉庫名字] [私有Pod源的repo地址]

添加完成之后不會有任何返回,我們可以查看添加好的私有庫

說明已經(jīng)添加完成。
4、創(chuàng)立一個文件夾,例如Project。
(1)把我們的主工程文件夾放到Project下:~/Project/MainProject
(2)在~/Project下clone快速配置私有源的腳本repo:git clone git@github.com:casatwy/ConfigPrivatePod.git

(3) 將ConfigPrivatePod的templates_objc文件夾下Podfile中source '[git@pkg.poizon.com:duapp/iOS/DUSpecs.git]'改成第一步里面你自己的私有Pod源倉庫的repo地址git@github.com:DemoComponentization/LSDRepo.git(tips:因為我本機的cocoaPods源是清華的,所以我把source 'https://github.com/CocoaPods/Specs.git'改成了source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git'),此時podfile文件內(nèi)容大致如下
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git'
source 'git@github.com:DemoComponentization/LSDRepo.git'
use_frameworks!
use_modular_headers!
target '__ProjectName__' do
pod "CTMediator"
# private pods
pod "HandyFrame"
end
(4)將ConfigPrivatePod的templates_objc文件夾下upload.sh中所有DUSpecs改成第二步里面你自己的私有Pod源倉庫的名字(LSDRepo),我當時修改的位置為第13,14,62行,三處。
至此,文件目錄結(jié)構(gòu)應(yīng)當如下
Project
├── ConfigPrivatePod
└── MainProject
如果沒有問題,那說明準備工作已經(jīng)完成。
二、明確業(yè)務(wù)拆分
MainProject是一個非常簡單的應(yīng)用,一共就三個頁面。首頁push了AViewController,AViewController里又push了BViewController。我們可以理解成這個工程由三個業(yè)務(wù)組成:首頁、A業(yè)務(wù)、B業(yè)務(wù),我們這一次組件化的實施目標就是把A業(yè)務(wù)組件化出來,首頁和B業(yè)務(wù)都還放在主工程。所以,我們需要為此做兩個私有Pod:A業(yè)務(wù)Pod(以后簡稱A Pod)、方便其他人調(diào)用A業(yè)務(wù)的CTMediator category的Pod(以后簡稱A_Category Pod)。所有的組件都有自己的Category,工程和組件之間,組件和工程之間通信都是通過Category。
1、新建Xcode工程,命名為A,放到Projects下
此時文件目錄結(jié)構(gòu)如下:
Project
├── ConfigPrivatePod
├── MainProject
└── A
2、github上新建Repository,命名也為A,新建好了之后網(wǎng)頁不要關(guān)掉
然后cd到ConfigPrivatePod下,執(zhí)行./config_objc.sh腳本來配置A這個私有Pod。腳本會問你要一些信息,Project Name就是A,要跟你的A工程的目錄名一致。HTTPS Repo、SSH Repository網(wǎng)頁上都有,Home Page URL就填你A Repository網(wǎng)頁的URL。


說明創(chuàng)建成功,并且關(guān)聯(lián)好了私有庫的一些參數(shù)。
3、修改podspec文件
在A工程中,將主工程中的AViewController的.h和.m文件移動到Source文件夾下,修改A.podspec文件中s.source_files的內(nèi)容為s.source_files = "A/Source/**/*.{h,m}",大概在98行。這樣做的目的是將來pod組件的時候會把Source文件夾下的所有文件pod到你的項目里,所以你可以把將來需要pod的文件都放到Source文件夾下。
現(xiàn)在A工程的文件目錄結(jié)構(gòu)大致如下:
A
|── A
| ├── Category
| ├── Source
| │ ├── AViewController.h
| │ └── AViewController.m
| ├── AppDelegate.h
| ├── AppDelegate.m
| ├── ViewController.h
| ├── ViewController.m
| └── main.m
└── A.xcodeproj
4、重復(fù)步驟1,2,3,完成A_Category的準備工作
創(chuàng)建A的Category=>A_Category,在Project文件夾下創(chuàng)建工程A_Category,github上創(chuàng)建相同名字的Repository,然后cd到ConfigPrivatePod文件夾下,執(zhí)行./config_objc.sh腳本來配置A_Category這個私有Pod,上述步驟準備就緒之后,A_Category的目錄結(jié)構(gòu)大致如下:
A_Category
├── A_Category
│ ├── Category
│ ├── Source
│ │ └── Target
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Info.plist
│ ├── ViewController.h
│ ├── ViewController.m
│ └── main.m
├── A_Category.podspec
├── A_Category.xcodeproj
├── FILE_LICENSE
├── Podfile
├── readme.md
└── upload.sh
然后在A_Category文件夾下,在Podfile中添加一行pod "CTMediator"(如果有,就不用管了),在A_Category.podspec文件的后面打開或者添加s.dependency "CTMediator",修改s.source_files = "A_Category/Source/*.{h,m}",然后執(zhí)行pod install --verbose。完成之后打開A_Category工程,在Source文件夾下創(chuàng)建基于CTMediator的Category:CTMediator+A,刪除無用文件,最終目錄結(jié)構(gòu)大致如下:
A_Category
├── A_Category
│ ├── Source
│ │ ├── CTMediator+A.h
│ │ └── CTMediator+A.m
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Info.plist
│ ├── ViewController.h
│ ├── ViewController.m
│ └── main.m
├── A_Category.podspec
├── A_Category.xcodeproj
├── FILE_LICENSE
├── Podfile
├── readme.md
└── upload.sh
到此,A和A_Category的準備工作就做好了。
3、完善代碼邏輯使得每個部分都可以獨立運行
1、完善主工程及其A_Category
打開主工程,在Podfile下添加pod "A_Category", :path => "../A_Category"來本地引用A_Category,終端執(zhí)行pod install。然后編譯一下,說找不到AViewController的頭文件。此時我們把AViewController.h引用改成#import <A_Category/CTMediator+A.h>。然后繼續(xù)編譯,說找不到AViewController這個類型??匆幌逻@里是使用了AViewController的地方,于是我們在Development Pods下找到CTMediator+A.h,在里面添加一個方法(本質(zhì)修改的是A_Category工程中Source文件夾下的CTMediator+A.h和CTMediator+A.m):
- (UIViewController *)A_aViewController;
再去CTMediator+A.m中,補上這個方法的實現(xiàn),把主工程中調(diào)用的語句作為注釋放進去,將來寫Target-Action要用:
- (UIViewController *)A_aViewController
{
/*
AViewController *a = [[AViewController alloc] init];
*/
return [self performTarget:@"A" action:@"viewController" params:nil shouldCacheTarget:NO];
}
補充說明一下,performTarget:@"A"中給到的@"A"其實是Target對象的名字。一般來說,一個業(yè)務(wù)Pod只需要有一個Target就夠了,但一個Target下可以有很多個Action。Action的名字也是可以隨意命名的,只要到時候Target對象中能夠給到對應(yīng)的Action就可以了。至此,完成了Category的代碼編寫,后面也不需要再進行改動。接下來把主工程調(diào)用AViewController的地方改為基于CTMediator Category的實現(xiàn):
UIViewController *a = [[CTMediator sharedInstance] A_aViewController];
[self.navigationController pushViewController:a animated:YES];
現(xiàn)在的話,編譯就沒問題了。這時候點擊按鈕沒有反應(yīng),因為我們還沒實現(xiàn)A工程的Target-Action。
2、實現(xiàn)A工程的代碼邏輯,使得編譯通過
打開兩個工程:A_Category工程和A工程。
我們在A工程中的Source文件夾下創(chuàng)建一個文件夾:Targets,然后看到A_Category里面有performTarget:@"A",所以我們在Targets文件夾下新建一個對象,叫做Target_A。然后又看到對應(yīng)的Action是viewController,于是在Target_A中新建一個方法:Action_viewController。這個Target對象是這樣的:
//A.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface A : NSObject
- (UIViewController *)Action_viewController:(NSDictionary *)params;
@end
//A.m
#import "A.h"
#import "AViewController.h"
@implementation A
- (UIViewController *)Action_viewController:(NSDictionary *)params
{//寫實現(xiàn)的時候,對照著之前在A_Category里面的注釋去寫就可以了
AViewController *a = [[AViewController alloc] init];
return viewController;
}
@end
另外補充一點,Target對象的Action設(shè)計出來也不是僅僅用于返回ViewController實例的,它可以用來執(zhí)行各種屬于業(yè)務(wù)線本身的任務(wù)。例如上傳文件,轉(zhuǎn)碼等等各種任務(wù)其實都可以作為一個Action來給外部調(diào)用,Action完成這些任務(wù)的時候,業(yè)務(wù)邏輯是可以寫在Action方法里面的(Action具備調(diào)度業(yè)務(wù)線提供的任何對象和方法來完成自己的任務(wù)的能力。它的本質(zhì)就是對外業(yè)務(wù)的一層服務(wù)化封裝。)。
現(xiàn)在重新編譯,發(fā)現(xiàn)找不到BViewController。由于我們這次組件化實施的目的僅僅是將A業(yè)務(wù)線抽出來,BViewController是屬于B業(yè)務(wù)線的,所以我們沒必要把B業(yè)務(wù)也從主工程里面抽出來。但為了能夠讓A工程編譯通過,我們需要提供一個B_Category來使得A工程可以調(diào)度到B,同時也能夠編譯通過。
B_Category的創(chuàng)建步驟跟A_Category是一樣的,創(chuàng)建好B_Category(注意B_Category.spec文件里同樣要設(shè)置依賴項和A_Category一樣)之后,Source文件夾下的兩個類如下:
//CTMediator+B.h
#import <CTMediator/CTMediator.h>
#import <UIKit/UIKit.h>
@interface CTMediator (B)
- (UIViewController *)B_viewControllerWithContentText:(NSString *)contentText;
@end
//CTMediator+B.m
#import "CTMediator+B.h"
@implementation CTMediator (B)
- (UIViewController *)B_viewControllerWithContentText:(NSString *)contentText
{
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
params[@"contentText"] = contentText;
return [self performTarget:@"B" action:@"viewController" params:params shouldCacheTarget:NO];
}
@end
我們在A工程的Podfile文件中pod "B_Category",:path=>"../B_Category",執(zhí)行pod install,編譯之前把涉及到BViewController的頭文件和代碼實現(xiàn)用B_Category及其方法替換,修改如下:
UIViewController *viewController = [[CTMediator sharedInstance] B_viewControllerWithContentText:@"hello, world!"];
[self.navigationController pushViewController:viewController animated:YES];
現(xiàn)在編譯,就沒有問題了,但是需要注意的是,A組件的依賴項需要添加一個B_Category,所以A.podspec文件的依賴項修改如下:
s.dependency "B_Category"
s.dependency "CTMediator"
現(xiàn)在組件A被完全剝離出主工程,成為了獨立的組件,A和主工程,A和B之間完全解耦,唯一的問題是,點擊A頁面不會push到B頁面,因為B的Action-target還沒寫。所以我們要去主工程創(chuàng)建一個B業(yè)務(wù)線的Target-Action。創(chuàng)建的時候其實完全不需要動到B業(yè)務(wù)線的代碼,只需要新增Target_B對象即可:需要注意的是,主工程的Podfile文件中現(xiàn)在可以添加pod "A",:path=>"../A",然后pod install。
//Target_B.h:
#import <UIKit/UIKit.h>
@interface Target_B : NSObject
- (UIViewController *)Action_viewController:(NSDictionary *)params;
@end
//Target_B.m:
#import "Target_B.h"
#import "BViewController.h"
@implementation Target_B
- (UIViewController *)Action_viewController:(NSDictionary *)params
{
NSString *contentText = params[@"contentText"];
BViewController *viewController = [[BViewController alloc] init];
return viewController;
}
@end
Target_B對象在主工程內(nèi)不存在任何侵入性,將來如果B要獨立成一個組件的話,把這個Target對象帶上就可以了??偨Y(jié)一下:
1、target類是和組件捆綁在一起的,它對象命名規(guī)則和方法的命名規(guī)則是根據(jù)組件的Category中performTarget Action方法確定的
2、組件的Category中Source文件夾下的Category用來給組件進行通信
3、組件的target是用來代替組件接受消息的,這里接受數(shù)據(jù),然后對組件進行操作,因為這個工程里面可以引用到當前組件類。
至此,本地的組件化方案就已經(jīng)完成了,接下來我們要做的事情就是給這三個私有Pod(A、A_Category、B_Category)發(fā)版,發(fā)版之前去podspec里面確認一下版本號和dependency。
4、發(fā)版
發(fā)版前需要強調(diào)一點,編譯運行主工程能后實現(xiàn)組件化之前同樣的功能并且所有邏輯都沒問題之后再發(fā)版。
發(fā)版主要用的幾個命令:
git add .
git commit -m "版本號"
git tag 版本號
git push origin master --tags
./upload.sh
命令行cd進入到對應(yīng)的項目中,然后執(zhí)行以上命令就可以了。需要注意的是這里的版本號要和podspec文件中的s.version給到的版本號一致。upload.sh是配置私有Pod的腳本生成的,如果你這邊沒有upload.sh這個文件,說明這個私有Pod你還沒用腳本配置過。當然,組件化流程詳解(一)中介紹了從創(chuàng)建倉庫到發(fā)版的全過程,不熟悉這個腳本特性的,也可以按我那個教程操作。
最后,所有的Pod發(fā)完版之后,我們再把Podfile里原來的本地引用改回正常引用,也就是把:path...那一段從Podfile里面去掉就好了,改動之后記得commit并push。
最后感謝下教我文檔和基友DC