組件化流程詳解(二)

組件化流程詳解(一)

第二篇主要講以下幾點:

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)建私有庫

1.png

然后點擊下面的CreateRepository即可。

3、打開終端,本地建立一個私有倉庫并關(guān)聯(lián)github上的私有倉庫LSDRepo,

pod repo add [私有Pod源倉庫名字] [私有Pod源的repo地址]


2.png

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


3.png

說明已經(jīng)添加完成。

4、創(chuàng)立一個文件夾,例如Project。

(1)把我們的主工程文件夾放到Project下:~/Project/MainProject
(2)在~/Project下clone快速配置私有源的腳本repo:git clone git@github.com:casatwy/ConfigPrivatePod.git
4.png
(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。

5.png

6.png

說明創(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容