提及組件化你們應該都不陌生,不過也再提一下,因爲業務的複雜度擴展,各個模塊之間的耦合度愈來愈高,不但形成了「牽一髮動全身」的尷尬境地,還增長了測試的重複工程,此時,組件化就值得考慮了。組件化就是將APP拆分紅各個組件(或者說模塊),同時解除這些組件之間的耦合,而後經過路由中間件將項目所須要的組件結合起來。這樣作的好處有:html
網上關於組件化的方案很多,流傳最廣的是蘑菇街組件化的技術方案和iOS應用架構談 組件化方案這裏不對大佬們的方案妄加評價,感興趣的同窗能夠本身看看。這裏咱們聊聊另外的一種方式Protocol-Moudlegit
在iOS中,協議(Protocol)定義了一個綱領性的接口,全部類均可以選擇實現。它主要是用來定義一套對象之間的通訊規則。protocol也是咱們設計時經常使用的一個東西,相對於直接繼承的方式,protocol則偏向於組合模式。他使得兩個絕不相關的類可以相互通訊,從而實現特定的目標。github
在以前的一篇文章ResponderChain+Strategy+MVVM實現一個優雅的TableView中咱們用到了protocol來爲View提供公共的方法:bash
- (void)configCellDateByModel:(id<QFModelProtocol>)model;
架構
爲Model提供公共的方法:ide
- (NSString *)identifier;
- (CGFloat)height;
複製代碼
那麼咱們也能夠以此來構建一個輕量級的路由中間件,定義一套各個組件的通訊規則,各自管理和維護各自的模塊,對外提供必要的接口。組件化
首先看一下這個Demo的結構圖和運行效果
post
好了咱們看看路由的一些細節,它只須要提供兩個關鍵的東西:測試
首先提供單例:ui
+ (instancetype)router {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_router = [[self alloc]init];
});
return _router;
}
複製代碼
這樣的單例可能沒有人不會寫,but,這其實這僅僅是個「假的」單例,不信你可使用[QFRouter router]
和[[QFRouter alloc]init]
以及[router copy]
試試他們會不會生成三個內存地址不一樣的實例。可能你會說,誰會無聊這麼幹?可是若是設計的時候能更嚴謹的規避這種坑不會更好麼。 那麼怎麼才能才能作一個嚴謹的單例呢?能夠重寫他的alloc
和copy
方法避免建立多個實例,你能夠在Demo工程中看到細節,這裏不作展開。
迴歸正題,咱們看看如何獲取Module
經過Runtime的反射機制,咱們能夠經過NSString獲取一個class進而建立對應的對象,而Protocol又能夠獲得一個NSString,那麼是否能夠由此入手呢?答案是能夠的:
- (Class)classForProtocol:(Protocol *)protocol {
NSString *classString = NSStringFromProtocol(protocol);
return NSClassFromString(classString);
}
複製代碼
這裏傳入一個protocol便可獲取對應的Module的class,再經過class便可以獲得對應的Module的object。
經過Protocol或者URL獲取對應的Module:
#pragma mark - Public
- (id)interfaceForProtocol:(Protocol *)protocol {
Class class = [self classForProtocol:protocol];
return [[class alloc]init];
}
- (id)interfaceForURL:(NSURL *)url {
id result = [self interfaceForProtocol:objc_getProtocol(url.scheme.UTF8String)];
NSURLComponents *cp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
[cp.queryItems enumerateObjectsUsingBlock:^(NSURLQueryItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[result setValue:obj.value forKey:obj.name];//KVC設置
}];
return result;
}
複製代碼
這裏有個瑕疵就是調用方(外部組件)須要知道這個目標組件對外暴露的協議名稱,不過既然是協議,對外公開應該不算大問題吧,調用實例以下:
wayOne:
id <MoudleHome>homeMoudle = [[QFRouter router]interfaceForProtocol:@protocol(MoudleHome)];
複製代碼
這樣就拿到了對應的目標組件實例,經過對外暴露的屬性能夠對其進行傳值,經過其回調block則能夠拿到回調參數。
wayTwo:
id <MoudleMe>meMoudle = [[QFRouter router]interfaceForURL:[NSURL URLWithString:@"MoudleMe://?paramterForMe=ModuleMe"]];
複製代碼
這裏經過url傳入,經過KVC設置其屬性值。一樣地,經過其回調block則能夠拿到回調參數。
經過上面咱們瞭解到:經過Protocol能夠獲取對應的組件實例,那麼這個協議放在哪兒?如何管理呢?
在平常開發過程當中,跨組件的交互場景最多的應該就是:從組件A附帶參數跳轉到組件B的某個頁面,組件B的這個頁面中作一些操做,再回到組件A(可能有回調參數,也可能不回調參數),那麼咱們的協議應該能處理這兩個最多見和基礎的操做,因此給protocol定義了兩個屬性:
typedef void(^QFCallBackBlock)(id parameter);
#pragma mark - 基礎協議
@protocol QFMoudleProtocol <NSObject>
/// 暴露給組件外部的控制器,通常爲該組件的主控制器
@property (nonatomic, weak) UIViewController *interfaceViewController;
/// 回調參數
@property (nonatomic, copy) QFCallBackBlock callbackBlock;
@end
複製代碼
這裏的interfaceViewController爲什麼聲明成了weak屬性?這個問題先留一下,後面會聊到這一點。
有了這裏的兩個屬性咱們便可完成,對應的跳轉和參數回調,可是如何正向傳值呢?
應該還須要對應的屬性來作入參,可是組件何其多,入參何其多,若是都把正向的屬性寫入這裏面,那麼隨着時間和業務的增加,這個協議可能會十分雜亂和臃腫。
因此這裏把這個協議定爲基礎協議,對應的組件都繼承自它,而後定義各自的須要的入參屬性:
首頁組件:
#pragma mark - 」首頁「組件
@protocol MoudleHome <QFMoudleProtocol>
/// 組件「Home」首頁所須要的參數
@property (nonatomic, copy) NSString *paramterForHome;
/// 組件「Home」中詳情頁面所須要的參數
@property (nonatomic, copy) NSString *titleString;
/// 組件「Home」中詳情頁面所須要的參數
@property (nonatomic, copy) NSString *descString;
/// 組件「Home」所須要暴露的特殊接口,好比其餘組件也要跳轉到該頁面
@property (nonatomic, weak) UIViewController *detailViewController;
@end
複製代碼
能夠看到,因爲首頁組件須要對外暴露一個主頁面 QFHomeViewController
和詳細頁面 QFDetailViewController
因此參數會多一點。
個人組件:
#pragma mark - 「個人」組件
@protocol MoudleMe <QFMoudleProtocol>
/// 組件「Me」所須要的參數
@property (nonatomic, copy) NSString *paramterForMe;
@end
複製代碼
而「個人」組件,只對外提供一個QFMeViewController
頁面,參數比較簡單。
這樣,基本算是達成了對協議的處理,可是無可避免的問題就是: 這個公共協議中定義了各個組件的協議,因此須要對多個開發團隊可見,感受這也是組件化過程當中的一個廣泛問題,目前沒找到好的解決方式。
上面咱們說到了公開protocol中定義了一些屬性,好比interfaceViewController
那麼這些屬性由誰提供呢?沒錯,就是Module,經過上面的步驟咱們能夠獲取到對應的Module實例,可是咱們跳轉須要的是Controller,因此,在此時就須要Module的幫助了, Module經過公共協議定義的屬性爲外部提供Controller接口:
- (UIViewController *)interfaceViewController {
QFHomeViewController *homeViewController = [[QFHomeViewController alloc]init];
homeViewController.interface = self;
interfaceViewController = (UIViewController *)homeViewController;
return interfaceViewController;
}
複製代碼
由於Module是在對應的組件中,因此能夠隨意引用本身組件內部的頭文件完成初始化,
而對應的控制器中,須要組件外部的參數,因此這裏把Module實例也暴露給對應的控制器實例,也就是homeViewController.interface = self;
所作的事情。
在上面說協議的時候咱們提到爲何要使用weak,至此,應該比較明朗了 ———— 打破循環強引用。
經過公共協議解耦獲取到Module,Module完成爲組件內和組件外的搭橋鋪路工做,由此,使得跨組件傳值、調用、參數回調得以實現。更多細節請看QFMoudle。
因爲時間關係,如何製做私有庫就再也不贅述了,有須要歡迎留言,咱們一塊兒手把手建立一個屬於你本身的pod私有庫。
在這種組件化的實踐中,通常會把對應的組件、路由以及公共協議作成pod私有庫,而須要多個團隊知道的也就只有這個公共協議庫,把全部的協議放入這個公開協議庫中,每次升級也只須要更新這個庫就行了。
關於對組件化的態度:上面說了那麼多好處,下面說說弊端(親身體會)
若是團隊規模較小業務複雜度較低的話,不太建議作私有庫,緣由:
任何事情都是雙面的,有利有弊,望各位看官自行取捨。最後因爲筆者文筆有限,若給您形成了困擾,實在抱歉,有任何疑問or意見or建議,歡迎交流討論,固然,若是對您有用,但願順手給個Star,點個關注。贈人玫瑰,手留餘香,您的支持是我最大的動力!