IOS組件化方案

前言


應(yīng)用在逐步迭代中,代碼體積越來越大?,F(xiàn)有的工程代碼雖然已經(jīng)按照模塊分門別類存放,但是其引用文件還是很隨意,代碼間的耦合性高,使得代碼“牽一發(fā)而動(dòng)全身”,使開發(fā)人員對(duì)源代碼的維護(hù)帶來極大的不便。因此,為了今后更好的增刪和重用代碼,需要將各個(gè)業(yè)務(wù)邏輯拆分,實(shí)現(xiàn)組件化。

demo地址:demo地址

組件化簡(jiǎn)介


組件化就是將各個(gè)業(yè)務(wù)邏輯代碼或者功能代碼拆分出來,形成獨(dú)立的一部分。組件化的最初目的是代碼重用,比如一個(gè)彈窗提示功能會(huì)在很多項(xiàng)目很多頁(yè)面調(diào)用,它的樣式相對(duì)固定,使用頻率高,因此可以提取出來作為一個(gè)組件,其他項(xiàng)目只要引用該組件就能在自己的頁(yè)面中調(diào)用,這樣可以減少大量的重復(fù)代碼。其特點(diǎn)是高內(nèi)聚,即脫離掉主工程代碼也能編譯通過,低耦合,從主工程中剝離簡(jiǎn)單容易。

組件化方案


基本概念

組件化應(yīng)當(dāng)是可以完全剝離出主程序,并可以獨(dú)立編譯通過并且運(yùn)行,但是實(shí)際項(xiàng)目中,特別是沒有組件化架構(gòu)的項(xiàng)目想要中途將業(yè)務(wù)邏輯拆分出來還是有一定困難。因此在方案上我進(jìn)行了調(diào)整,允許初期組件化各個(gè)組件對(duì)主程序有依賴,如下圖所示:


業(yè)務(wù)邏輯.png

總結(jié)下方案設(shè)計(jì)大致有如下要求:

  • 各個(gè)組件可以脫離主程序獨(dú)立成塊并編譯,但允許存在少量依賴。
  • 主程序脫離各個(gè)組件可以獨(dú)立運(yùn)行,對(duì)組件沒有依賴。

組件化實(shí)現(xiàn)


采用靜態(tài)庫(kù)脫離主程序

網(wǎng)絡(luò)上有很多組件化的方法,經(jīng)過多次嘗試,發(fā)現(xiàn)都有一定弊端,而且適合新項(xiàng)目,對(duì)已有項(xiàng)目不友好。最后,我采取靜態(tài)庫(kù)加工程組的方式進(jìn)行組件化。

所謂工程組,就是將多個(gè)項(xiàng)目工程關(guān)聯(lián)到一起,由.xcworkspace文件管理。常用的cocoaPods就是用這種形式。

工程管理.png

建多個(gè)項(xiàng)目文件的好處是可以將組建的代碼和主程序代碼完全剝離開來,做到高內(nèi)聚的特點(diǎn)。

下面我將演示如何創(chuàng)建一個(gè)靜態(tài)庫(kù)工程。

1、在文件夾中創(chuàng)建新項(xiàng)目


創(chuàng)建靜態(tài)庫(kù)1.png

2、創(chuàng)建項(xiàng)目的時(shí)候選取項(xiàng)目組


選擇項(xiàng)目組.png

3、靜態(tài)庫(kù)項(xiàng)目中將編譯格式切換成static Library

選擇編譯模式.png

4、創(chuàng)建資源文件target

創(chuàng)建資源文件.png

創(chuàng)建資源文件2.png
創(chuàng)建資源文件3.png
修改資源文件屬性.png

5、將靜態(tài)包引入主工程,并設(shè)置先后編譯順序

5關(guān)聯(lián)靜態(tài)庫(kù).png
關(guān)聯(lián)靜態(tài)庫(kù)2.png

按照以上方法配置,一個(gè)組件化就完成,后續(xù)可以在NMModelA.xcodeproj中編寫組件代碼。NMModelAResource targets主要是為了存放一些資源文件,比如圖片資源、xib編譯后的nib文件。每次改動(dòng)圖片或者xib都需要把新新生成的資源文件替換掉主程序中的資源文件 過程如下圖所示:

將變動(dòng)的資源文件復(fù)制到主工程.png

每次添加新資源文件或者新建xib記得從NMModelA target刪除,添加到 NMModelAResource中,否則實(shí)際包會(huì)生成2份或多份圖片,造成包大小變大

刪除圖片.png
添加圖片.png

使用靜態(tài)庫(kù)加工程組的方式的好處:

  • 高內(nèi)聚,低耦合

由于工程的限制,主程序的代碼無法直接引用各個(gè)組件的代碼,組件中的代碼也無法直接引用主程序,真正實(shí)現(xiàn)了組件化的概念。

  • 編譯調(diào)試方便

在驗(yàn)證組件化功能的時(shí)候可以直接編譯主程序,在組件工程里下斷點(diǎn),就像編譯一個(gè)程序一樣,簡(jiǎn)單方便。

  • 靜態(tài)庫(kù)只需要.h不需要.m就能編譯

前面我們說到,一些沒有組件化架構(gòu)的項(xiàng)目想要完全去除依賴非常困難,因此組件化程序往往需要引用主程序的頭文件。使用靜態(tài)庫(kù)可以很好解決該問題,只要在靜態(tài)庫(kù)中添加需要引用的.h文件,而不用添加.m文件就能編譯通過,并打成靜態(tài)包,可以作為臨時(shí)過渡期的方法。該方案同樣適用于解決庫(kù)沖突問題。

  • 圖片資源、本地化文件與主程序共用

將圖片資源、本地化文件添加到主程序中,可以實(shí)現(xiàn)本地化、圖片資源共用。

  • 完成組件代碼測(cè)試后可以打成.framework包引入工程

靜態(tài)庫(kù)文件不會(huì)暴露源代碼,因此可以大大減少編程人員誤修改組件代碼,導(dǎo)致出錯(cuò)的問題。

組件與主程序之間的交互


既然組件代碼與主程序不能直接交互,那么怎么做到一些常用操作,比如界面跳轉(zhuǎn)呢?
答案是RunTime。object使用runTime可以很好解決耦合問題,甚至做到不變動(dòng)代碼,刪除組件,主程序成功編譯并且不崩潰的神奇效果。以下是總體設(shè)計(jì)思想:

總體設(shè)計(jì)思路.png

  • 主程序和組件代碼只能通過各自的接口文件獲取數(shù)據(jù)

  • 接口程序間通過runtime調(diào)用方法

  • 由于runtime performSelector方法的參數(shù)個(gè)數(shù)限制和回調(diào)類型限制,這里規(guī)定接口最終回調(diào)類型必須是對(duì)象,參數(shù)傳id,多個(gè)參數(shù)用NSDictionary封裝傳遞

這里我創(chuàng)建了一個(gè)接口基本類HSLinkClass,所有的接口都是繼承自該類,主要提供了對(duì)象方法和類方法的調(diào)用。

.h文件

/**
 回調(diào)block

 @param retunValue 該方法return 的值
 @param performSuccess 該方法是否執(zhí)行成功
 @param classIsLoad 該類是否加載
 */
typedef void (^plugInCallBackblock)(id retunValue, BOOL performSuccess,BOOL classIsLoad);

@interface HSLinkClass : NSObject
/**
 調(diào)用對(duì)象方法

 @param className 類名
 @param selectStr 方法名
 @param params 參數(shù)字典
 @param block 回調(diào)函數(shù)
 */
- (void)hsObjectMethodClassName:(NSString *)className performSelectorStr:(NSString *)selectStr param:(id)params block:(plugInCallBackblock)block;

/**
 調(diào)用類方法

 @param className 類名
 @param selectStr 方法名
 @param params 參數(shù)字典
 @param block 回調(diào)函數(shù)
 */
- (void)hsClassMethodClassName:(NSString *)className performSelectorStr:(NSString *)selectStr param:(id)params block:(plugInCallBackblock)block;

/**
 初始方法
 */
+ (instancetype)shareObject;

@end

.m文件

@implementation HSLinkClass

/**
 初始方法

 */
+ (instancetype)shareObject{
    return [[self alloc] init];
}

/**
 調(diào)用對(duì)象方法

@param className 類名
@param selectStr 方法名
@param params 參數(shù)字典
@param block 回調(diào)函數(shù)
*/
- (void)hsObjectMethodClassName:(NSString *)className performSelectorStr:(NSString *)selectStr param:(id)params block:(plugInCallBackblock)block{

    Class classObj = NSClassFromString(className);
    if (classObj == nil) {
        if (block) {
            block(nil,NO,NO);
        }
    }
    id object = [[classObj alloc] init];
    if ([object respondsToSelector:NSSelectorFromString(selectStr)]) {
        id returnValue = nil;
        if (params != nil) {
            returnValue = [object performSelector:NSSelectorFromString(selectStr) withObject:params];
            if (block) {
                block(returnValue,YES,YES);
            }
        }
        else {
             returnValue = [object performSelector:NSSelectorFromString(selectStr)];
            if (block) {
                block(returnValue,YES,YES);
            }
        }
    }
    else {
        if (block) {
            block(nil,NO,YES);
        }
    }
}

/**
 調(diào)用類方法
 
 @param className 類名
 @param selectStr 方法名
 @param params 參數(shù)字典
 @param block 回調(diào)函數(shù)
 */
- (void)hsClassMethodClassName:(NSString *)className performSelectorStr:(NSString *)selectStr param:(NSDictionary *)params block:(plugInCallBackblock)block{
    Class classObj = NSClassFromString(className);
    if (classObj == nil) {
        if (block) {
            block(nil,NO,NO);
        }
    }
   if ([classObj respondsToSelector:NSSelectorFromString(selectStr)]) {
        id returnValue = nil;
        if (params != nil) {
            returnValue = [classObj performSelector:NSSelectorFromString(selectStr) withObject:params];
        if (block) {
            block(returnValue,YES,YES);
        }
    }
    else {
        returnValue = [classObj performSelector:NSSelectorFromString(selectStr)];
        if (block) {
            block(returnValue,YES,YES);
        }
    }
}
else {
        if (block) {
            block(nil,NO,YES);
        }
    }
}
@end

通過Runtime可以判斷該模塊是否加載,在基本類中做了多種情況保護(hù)防止其崩潰。

主程序和組件程序都需要新建一個(gè)HSLinkClass的子類作為接口,也稱之為中間層,分別提供索取方法供應(yīng)方法。組件的索取方法就是主程序的供應(yīng)方法,反之也一樣。

下面是主程序跳轉(zhuǎn)到視頻扥類頁(yè)面的例子:

主程序接口代碼:


索取方法.png

組件接口代碼:


提供方法.png

主程序代碼:


主程序代碼.png

建議以及優(yōu)化


  • 每次xib變動(dòng)或者添加圖片都得手動(dòng)復(fù)制到主程序中去十分麻煩,這里可以學(xué)習(xí)cocoapods的做法,寫一個(gè)腳本放到Build Phases中,可以減少不少操作

  • 接口基類目前只是實(shí)現(xiàn)了基本方法用runtime調(diào)用,還有很大的提升空間,比如增加屬性判斷組件是否加載

總結(jié)


組件化的確會(huì)增加一定工作量,原來#import一個(gè)文件就能解決的事,現(xiàn)在需要多好幾步操作。不過,也正是因?yàn)橹暗碾S意,導(dǎo)致后續(xù)代碼功能變更,牽一發(fā)而動(dòng)全身,痛苦萬分。吃苦在前,享樂在后才是正確的工作態(tài)度。

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

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

  • 最近在思考團(tuán)隊(duì)擴(kuò)張及項(xiàng)目數(shù)量增加的情況下,如何持續(xù)保障團(tuán)隊(duì)高效產(chǎn)出的問題,很自然的想到了組件化這個(gè)話題。重翻了前段...
    其實(shí)也沒有閱讀 8,450評(píng)論 4 20
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,725評(píng)論 25 709
  • 三年學(xué)會(huì)說話,一輩子學(xué)會(huì)閉嘴。 兩性關(guān)系是世界上最難的課題,連個(gè)人的相處模式很難短時(shí)間改變。很多人一直想改變婚姻活...
    自由的U0閱讀 384評(píng)論 0 0
  • 在深邃的夜晚 我夢(mèng)見了過往 在有星星的夜空 我看到了銀河的遙遠(yuǎn) 模糊的想象中看不清未來 沒有人想到哪里是最終的家園...
    李諾亞閱讀 325評(píng)論 0 1
  • 文/泥璐 -01- 好久沒刷微博了,今天一打開微博,鋪天蓋地地向我拋來的都是《那年花開月正圓》中,杜明禮這個(gè)角色讓...
    泥璐閱讀 880評(píng)論 20 16

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