iOS關于CTMediator組件化實踐的詳解篇

這篇文章參考 casa 大神的組件化實踐使用Cocoapods創建私有podspec,不過因為之前對 iOS 組件化方面了解的比較少,所以在跟著 casa 的步驟一步一步組件化工程的時候遇到了不少問題,這里當做一個自己組件化的時候遇到問題的總結吧,寫得比較繁瑣,因為我盡量把每一步都講得清楚點,基礎差點的人一步一步按照本文來操作也可以感受組件化帶來的快感!
  那么,首先你要懂得組件化的整體思想,如下圖,就是通過一個中間者傳遞信息,用來降低模塊間的耦合度。

組件化思想

  好了,本文主要并不探討組件化的思想和解決方案的優劣,主要目的是讓你先成功的組件化一個小 demo,然后再回過頭分析這個 demo被組件化后是怎么達到通過中間者調度信息的。

每一個組件都是一個獨立的 pod,我們把組件化的代碼放到 git 上托管,制作成私有 pod,然后通過 cocoapods 就可以連接各個組件,最后把他們合到一起,變成一個完整的項目。

本文借用 casa 文章中的 demo,一個主工程 MainProject,主工程中有兩個業務,A部分,和 B 部分,我們要把 A 部分組件化,那么我們需要創建A 的私有 Pod 源(以 coding.net為例), 這里注意區分私有倉庫Repo,和私有 Pod的關系,可以參考下圖,在完成所有組件化步驟之后再回頭這個圖可能會更加容易理解,所以暫時不明白不要急,慢慢一步一步來。

私有 Repo 和私有 pod 的關系

組件化操作流程:

第一:添加私有 Pods源
第二:創建 MainProject Xcode工程
第三:創建 A_section 的 Xcode 工程和對應的私有 Repo
第四:創建 A_Category 的 Xode 工程和對應的私有 Repo
第五:解決主工程編譯不通過的問題
第六:為 A_section 工程創建 Target(一個組件對應一個 Target-Action)
第七:解決A_section工程編譯不通過的問題
第八:準備發版 pod

一 、添加私有 Pods源

我們在coding.net上,先創建一個 MainProject 的私有倉庫作為私有 pods 源倉庫。然后添加到本地,添加成功后會在本地Users/xxx/.cocoapods/repos 文件夾下看到自己添加的私有pod源

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

二、創建 MainProject Xcode工程

在桌面新建文件夾 Project,新建Xcode項目工程 Project/MainProject,作為我們的主工程,并且實現 casa文章里的demo 中未組件化之前的 MainProject 里的功能,就是很簡單的 push 一個 A界面,然后在 A 界面 push 到 B 界面。直接復制casa的代碼就好,也可以自己寫。這個時候我們的 Project 文件下的目錄應該是這樣的

Project文件夾

此時的MainProject是完全沒有組件化之前的工程,也沒有 cocoapods,當然 casa 的這個 MainProject 中用到了pod 'HandyFrame', 就是個布局 UI 的小分類,和組件化工程沒有什么關系,只是為了代碼寫的方便而已,所以下一步我們在 MainProject 工程中先把它pod install 下。

現在你應該保證 MainProject 已經實現了 push 兩個界面的功能了。

三、創建A_section Xcode 工程

1.新建A_section Xcode 工程

我們要把 A 業務組件化出來,也就是casa 文章中的 A,MainProject 中的 A界面。因為 coding 不能創建單個字母的私有倉庫,為了保證 Xocde 工程和私有 Repo 的名字一一對應,所以就把名字改成了 A_section,本文中的 A 和 A_section 是等同的。

2.新建私有的 A_section Repo

在 coding 上創建一個私有的 Repo,起名 A_section,用來存放 A_section 代碼。
  把 A_section 的私有倉庫 clone 下來(剛創建,所以是空倉庫),然后把 A_section 的 Xcode 工程文件全部放進去,然后 push 到倉庫,這樣你的 A_section 私有倉庫上就托管了你的 A_section Xcode 工程了,或者你使用 git remote add origin 命令,最終都是達到一個目的 。

3.分離 A_section業務代碼

然后我們在MainProject中,把屬于A_section業務的代碼移出來,然后拖放到A_section的Xcode工程中。原來MainProject里面A業務的代碼直接刪掉,此時MainProject和A_section工程編譯不過都是正常的。
這個時候A_section文件目錄應該是這樣的

未制作成私有 pod 之前的 A_section
4.把 A_section 配置成私有 Pod

cd到 A_section 文件夾下,命令

pod spec create A_section  https://git.coding.net/xxxxxxx/A_section.git

之后會在 A_section 工程中生成A_section.podspec文件。

接下來就是編輯A_section.podspec文件。最好用編輯工具,如 Sublime, 會有語法高亮,方便編輯,如果用文本編輯的話,很可能你修改了內容,標點符號會自動更改成中文的,我就因為這個錯誤耽誤了半天,而且很難發現,所以不要用默認的文本編輯器。

打開之后,如果不知道該怎么填一方面可以參考官方文檔,查看每個屬性的意思,另外也可以重新開個文件夾然后執行

pod lib create A_section

然后會問你幾個問題,自己如實回答就好。 這個命令的作用是 cocoapods 提供一個 demo 給你,你可以對照這個 demo 里的.podspec 文件去填寫。

如果你也懶得去看得話,我給一些必須用到的參數都做了注釋,可以按照下面的代碼編輯自己的.podspec 文件,如果對配置私有 pod 方面不是很熟悉的朋友,一定要仔細看我的每個注釋,基本都說的通俗易懂。

Pod::Spec.new do |s|
  s.name         = "A_section"

  #發版版本號,每更新一次代碼就改變一次版本號
  s.version      = "0.0.1"

  #一個簡單的總結,隨便寫
  s.summary      = "A short description of A_section." 

  #一定要寫上,不寫的話,執行 pod lib lint 驗證項目的時候會報找不到 UIKIT 等框架錯誤
  s.platform = :ios, "8.0"

  #描述,隨便寫 但是要比 s.summary 長度長
  s.description  = <<-DESC
   short description of A_section short description of A_section
                   DESC

  #你的 git 倉庫首頁的網頁 url,注意并不是 https/ssh這種代碼倉庫地址
  s.homepage     = "https://coding.net/u/xxxx/p/A_section"
   
  #直接寫 MIT
  s.license      = "MIT"
  
  #你是誰
  s.author             = { "" => "" }
  
  #這里就是你 git 倉庫的 https/ssh 地址了
  s.source       = { :git => "https://git.coding.net/xxxx/A_section.git", :tag => "#{s.version}" }

  #這里的文件夾下的內容就是這個 pods 被pod install 的時候會被下載下來的文件,不在這個文件夾,將不會被引用
  # Classes 目錄和.podspec 目錄是平級的。
  #你可以隨便指定文件夾名稱,只要這個文件夾是真實存在的
  #Classes/**/*.{h,m},表示 Classes 文件夾及其文件夾下的所有.h,.m 文件。
  s.source_files  =  "A_section/Classes/**/*.{h,m}"

  #資源文件地址,下面的所有.png資源都被打包成 s.name.bundle
  s.resource = ['Images/*.png','Sounds/*']

  #資源文件地址,和 resource 的區別是,這個屬性可以指定 bundle 的名字,下面的所有.png文件都會被打包成 ABC_section.bundle
  s.resource_bundle = {
  'ABC_section' => ['Classes/ABCImage/*png']

  }

  #指定公有頭文件,如果沒有寫,那么所有 pod 中的頭文件都默認公有,可以被 import。如果指定了某些頭文件,那么只有這些被指定的頭文件才可以被 import。
  s.public_header_files = 'Classes/Public/*.h'

  #這個 pods 還依賴于其他哪些 pods
  s.dependency "B_Category"
  s.dependency "HandyFrame"

編輯完A_section.podspec,把 A_section 的業務代碼,移動到 s.source_files 指定的文件夾中,以保證 pod 發版的時候,這些文件能被發布出去。

這是我自己對 A_section 的配置

A_section.podspec 配置

根據我自己對 A_section 的配置,重新調整A_section 工程的文件結構這個時候因該是這樣的

A_section 配置 spec之后文件結構

這里針對 A_section 工程我們還要pod "HandyFrame",因為它也用到了里面的布局 UI 的方法。執行pod install 安裝HandyFrame。

此時編譯, A_section 編譯失敗,應該報錯找不到BViewController.h,這里暫時先放下,后面再解決。

四、創建 A_Category 的 Xode 工程和對應的私有 Repo

按照創建 A_section的流程,我們創建 A_Category 的 Xode 工程和對應的私有 Repo,同樣讓你的 A_Category工程托管到你的 A_Category 私有倉庫上。

然后去A_Category下,在Podfile中添加一行pod "CTMediator",然后執行pod install。

對照之前的格式編輯A_Category.podspec文件,然后在s.source_file對應的文件夾中新建基于CTMediator的Category:CTMediator+A。

CTMediator+A.h,在里面添加一個方法:

- (UIViewController *)A_aViewController;

再去CTMediator+A.m中,補上這個方法的實現,把MainProject中調用的語句作為注釋放進去,將來寫Target-Action要用

- (UIViewController *)A_aViewController
{
    /*
        AViewController *viewController = [[AViewController alloc] init];
     */
    return [self performTarget:@"A" action:@"viewController" params:nil shouldCacheTarget:NO];
}

最后你的A_Category工程應該是這樣的:
和配置之后的文件結構截圖

A_Category配置后

五、解決主工程編譯不通過的問題

去主工程的Podfile下添加,然后執行 pod install

pod "CTMediator"
pod "A_Category", :path => "../A_Category

然后編譯一下,說找不到AViewController的頭文件。此時我們把頭文件引#import AViewController.h用改成#import <A_Category/CTMediator+A.h>。

然后繼續編譯,說找不到AViewController這個類型,然后我們把主工程調用AViewController的地方改為基于CTMediator Category A的實現

UIViewController *viewController = [[CTMediator sharedInstance] A_aViewController];

[self.navigationController pushViewController:viewController animated:YES];

再編譯一下,編譯通過。

到此為止主工程就改完了,現在跑主工程點擊這個按鈕跳不到A頁面是正常的,因為我們還沒有在MainProject工程中引入 A_section 組件,也沒有在A_section工程中實現Target-Action。

此時主工程中關于A業務的改動就全部結束了,后面的組件化實施過程中,就不會再有針對A業務線對主工程的改動了。

六、為 A_section 工程創建 Target(一個組件對應一個 Target-Action)

此時我們關掉所有XCode窗口。然后打開兩個工程:A_Category工程和A_section工程。

我們在A_section工程中創建一個文件夾:Target,然后看到A_Category里面有performTarget:@"A",所以我們新建一個對象,叫做Target_A。

然后又看到對應的Action是viewController,于是在Target_A中新建一個方法:Action_viewController。這個Target對象是這樣的:

頭文件:
#import <UIKit/UIKit.h>

@interface Target_A : NSObject

- (UIViewController *)Action_viewController:(NSDictionary *)params;

@end

實現文件:
#import "Target_A.h"
#import "AViewController.h"

@implementation Target_A

- (UIViewController *)Action_viewController:(NSDictionary *)params
{
    AViewController *viewController = [[AViewController alloc] init];
    return viewController;
}

@end

最終你的 A_section 工程目錄應該是這樣


A_section創建 Target 之后

七、解決A_section工程編譯不通過的問題

然后我們再繼續編譯A_section工程,發現找不到BViewController。由于我們這次組件化實施的目的僅僅是將A_section業務線抽出來,BViewController是屬于B業務線的,所以我們沒必要把B業務也從主工程里面抽出來。但為了能夠讓A_section工程編譯通過,我們需要提供一個B_Category來使得A_section工程可以調度到B,同時也能夠編譯通過。

新建 B_Category Xcode 工程,和對應的私有倉庫 Repo,然后一樣托管到對應的遠程倉庫, 配置.podspec 文件,配置B_Category.podspec 文件的時候,在最后加上 s.dependency "CTMediator"(這樣是因為下一步 A_section 從本地 pod B_Category 的時候也可以pod 到CTMediator) ,pod "CTMediator", 創建CTMediator+B 分類代碼,

頭文件:
#import <CTMediator/CTMediator.h>
#import <UIKit/UIKit.h>

@interface CTMediator (B)

- (UIViewController *)B_viewControllerWithContentText:(NSString *)contentText;

@end

實現文件:
#import "CTMediator+B.h"

@implementation CTMediator (B)

- (UIViewController *)B_viewControllerWithContentText:(NSString *)contentText
{
    /*
        BViewController *viewController = [[BViewController alloc] initWithContentText:@"hello, world!"];
     */
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    params[@"contentText"] = contentText;
    return [self performTarget:@"B" action:@"viewController" params:params shouldCacheTarget:NO];
}

@end

最終你的 B_Category ,應該可以直接編譯通過,像這樣

B.png

B_Category添加好后,我們在A_section工程的Podfile中本地指過去

pod "B_Category", :path => "../B_Category"

然后我們對應地在A_section工程中修改頭文件引用為#import <B_Category/CTMediator+B.h>,并且把調用的代碼改為:

UIViewController *viewController = [[CTMediator sharedInstance] B_viewControllerWithContentText:@"hello, world!"];

[self.navigationController pushViewController:viewController animated:YES];

此時再編譯一下,編譯通過了。注意哦,這里A業務線跟B業務線就已經完全解耦了,跟主工程就也已經完全解耦了。

此時還有一個收尾工作是我們給B業務線創建了Category,但沒有創建Target-Action。所以我們要去MainProject創建一個B業務線的Target-Action。創建的時候其實完全不需要動到B業務線的代碼,只需要新增Target_B對象即可:

Target_B頭文件:
#import <UIKit/UIKit.h>

@interface Target_B : NSObject

- (UIViewController *)Action_viewController:(NSDictionary *)params;

@end

Target_B實現文件:
#import "Target_B.h"
#import "BViewController.h"

@implementation Target_B

- (UIViewController *)Action_viewController:(NSDictionary *)params
{
    NSString *contentText = params[@"contentText"];
    BViewController *viewController = [[BViewController alloc] initWithContentText:contentText];
    return viewController;
}

@end

這個時候我們MainProject 結構應該是


MainProject

八、準備發版 pod

1.先在本地測試

接下來我們就要為發版 pod 做準備,我們先在本地實驗 pod 可用不可用,此時我們的 Project 文件夾中應該有四個文件,像這樣


完成后的文件夾.png

我們可以把MainProject 中的 podfile 文件改成

target 'MainProject' do
  pod "A_section", :path => "../A_section"
  pod "A_Category", :path => "../A_Category"
  pod "B_Category", :path => "../B_Category"
  pod "CTMediator"
  pod 'HandyFrame'
end

然后執行pod install,這里的目的是測試在本地情況下pod 是否能順利執行。如果順利的話在安裝完成之后,會在 Mainproject 工程的 development pods 文件夾下查看到本地的 pods。你運行 MainProject 達到的效果應該是和沒有組件化之前是完全一樣的。

2.發布組件

準備發布pod之前要檢查依賴是不是寫上了,否則會發版失敗,

A_section的 A_section.podspec 文件最后應該有這兩個依賴

  s.dependency "B_Category"
  s.dependency "HandyFrame"

A_Category 中 A_Category.podspec和B_Category.podspec 文件最后應該有

s.dependency "CTMediator"

B_Category 中 B_Category.podspec和B_Category.podspec 文件最后應該有

  s.dependency "CTMediator"

然后我們就可以把把私有 Repo 發布到網上了,

1. 把代碼push 到 git 上
git add .
git commit -m "initial pod"
git push 

2. 為這版的代碼打上 tag 號,tag 號一定要和.podspec 文件的 s.version 號一致
git tag 0.0.1
git push --tags

3. 發布 pod 到私有 Pods
pod repo push PrivatePodRepo   A_section.podspec   --verbose   --allow-warnings

接著發布A_Category,B_Category的 pods,都是相同的步驟。如果在上傳代碼到 git 上的時候碰到403錯誤,或者沒有權限等問題,可以參考我的另一篇筆記
  然后到我們的 MainProject 主工程之下,把 Podfile 文件內容改成下面這個樣子,

添加上你的私有 Pods 的地址
source 'https://git.coding.net/xxxxx/PrivatePodRepo.git'
source 'https://github.com/CocoaPods/Specs.git'

target 'MainProject' do
  pod "CTMediator"
  pod "A_Category"
  pod "A_section"
  pod 'HandyFrame'
end

然后執行 pod install,組件化到此結束,現在你可以好好研究下,組件化的過程是如何通過中間者協調信息的。

如果組件化本例的時候遇到什么問題,可以到 casa 在 github 上開的orgnization上一一對照你的組件化前和組件化后的配置對不對。

3.補充

pod install的過程就是首先到本地

/Users/用戶名/.cocoapods/repos/PrivatePodRepo

文件夾中尋找對應的私有 pod,找到之后根據.podspec 文件的version 號,然后到 git 的地址上找到 tag 號和 version 號一致的那個版本的代碼 pull 下來。

所以,我們如果修改了組件的內容,想更新的話,記得要修改s.version的版本號,然后 打上 tag, tag 要和 version 號一樣。

如果在 push 到 Repo的時候

pod repo push PrivatePodRepo   A_section.podspec   --verbose   --allow-warnings

報錯  [!] The repo at ../../../.cocoapods/repos/xxxx is not clean

解決方案: 把本地的私有 Pod 刪除,之后再重新添加

關于 xib 和圖片:這里是很簡單的組件化Demo,如果你的組件中用到了圖片,或者 xib 資源,要指定資源的文件路徑,否則不會把圖片打包到你的組件中,

s.resource_bundles = {
    'A_section' => ['A_section/AImages/**/*.{png}', 'A_section/Classes/*.{xib}']  
}

或者

s.resources = ['A_section/AImages/**/*.{png}', 'A_section/Classes/*.{xib}']

我實驗的結果是:如果使用 Xocde 工程自帶的 Assets 那個文件夾的話,圖片也無法打包到組件中,最好自己重新創建一個新的文件夾用來存放圖片資源。

然后代碼中獲取 xib, png 等 resource 時,bundle 重新設置,這樣就保證了無論在組件中,還是在 MainProject 工程中,都可以配置到正確的 Bundle,如果你使用 s.resource_bundles={},配置了自定義的 bundle名稱,那么 [bundle pathForResource:@"A_section" ofType:@"bundle"]中就要替換成相應的名稱。

//mainBundle
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *bundlePath = [bundle pathForResource:@"A_section" ofType:@"bundle"];
    if (bundlePath)
    {
       //組件資源所在的 bundle     
       bundle = [NSBundle bundleWithPath:bundlePath];
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容