最近給公司的一個iOS項目進行組件化解耦。本身項目早期開發就不是很規范,而且剛剛開始熟悉這個項目對業務方面也不是很熟悉所以並沒有對所有的模塊進行組件化。而且組件化解耦後還存在一些問題在文章中都會寫出來。
原理和蘑菇街 App 的組件化之路類似,但是也有一些不同並沒有加入「組件A」要調用「組件B」的某個方法這種業務場景。所有組件化的模塊都是
「組件A」要調用「組件B」的這種情況。
「組件A」與「組件B」之間是透明的。
為何要對項目組件化
對每個模塊間相互調用解耦統一wap與本地調用
首先說一下組件化帶來的最大好處就是給每個模塊間解耦。之前模塊間調用不得不相互引用,這就導致了各個模塊間相互依賴。想象一種場景:A,B,C,D是四個VC,四個VC之間是這樣的關系,A與B相互跳轉、B與C相互跳轉、D與A,B相互跳轉。它們之間的關系如下圖:
而組件化在項目中引入了
Mediator對象與Action對象,引入這兩個對象後,A,B,C,D之間的關系如下圖:
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPtXi0fm94bm5vs263Mfls/7By7b4x9Kyu7nctuDJ2bj21+m8/r3hubm2vMrH1eK49tH519Oho8/Cw+a74cu1w/c8Y29kZT5NZWRpYXRvcrbUz/PT6zxjb2RlPkFjdGlvbrbUz/O1xMq1z9ahozwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L3A+DQo8aHIgLz4NCjxwPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPrbU09rSu7j2PGNvZGU+QXBwwLTLtbHYsru/ycPitcS74dPQPGNvZGU+d2Fw0rPD5rX308M8Y29kZT5OYXRpdmW1xLmmxNyho82ouf08Y29kZT5VUkzXorLhyrXP1tfpvP67r7e9sLi/ydLUyrXP1s2s0ru49jxjb2RlPlVSTLy0v8nS1NTasb612LX308PX6bz+0rK/ydLU1No8Y29kZT53YXDSs8PmtffTw9fpvP6ho9Xi0fnUrbG+0qrU2sG9tKbNrMqxtKbA7bXEwt+8rc2z0ru3xdTawcs8Y29kZT5NZWRpYXRvcrbUz/PW0LSmwO2hozxiciAvPg0KtbHIu9Xi0fnX9tPQ0ru49sixteO+zcrHyOe5+zxjb2RlPndhcNKzw+a199PD0+uxvrXYtffTw82s0ru49sSjv+nQ6NKq1/ayu82stcS0psDtyrHO3reox/i31srH1NrExMDvtffTw7XEo6zS8s6qtrzKx82ouf3Su9H5tcQ8Y29kZT5VUkzAtLX308O1xKGjxL/HsNTaztLTxbuvtcTP7sS/1tCyosO709DV4tH5tcTQ6MfzoaPL+dLUtbHHsLXEt72wuMrHv8nQ0LXEoaPI57n71ea1xNKqx/i31rXEu7C/ydLUzai5/bSrtd2yu82stcSyzsr9wLTIt7aoyse008TEwO+3osbwtcS199PDoaM8L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9wPg0KPGhyIC8+DQo8aDIgaWQ9"組件化方案的實現">組件化方案的實現
通過程序啟動時注冊
URL來實現組件化,
URL注冊用通過JLRoutes實現的。
具體使用如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
JLRoutes *routes = [JLRoutes routesForScheme:scheme_url];
[routes addRoute: LSYKey1 handler:^BOOL(NSDictionary * _Nonnull parameters) {
//要處理的邏輯
return YES; //判斷返回值來決定是否執行該處代碼
}];
}
在上面的代碼中
scheme_url是app自定義的協議,
LSYKey1是
URL的路徑,
parameters是
URL的參數。通過程序啟動初始化
JLRoutes對象,並注冊不同的
URL,當嘗試打開
scheme_url協議的
URL時就會執行對應注冊路徑下
block內的代碼。
-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation //當程序嘗試打開URL或者處理完從其它應用返回時會調用該方法 (不同版本的系統需要實現的方法不同)
{
if ([url.scheme isEqualToString:scheme_url]) {
return [JLRoutes routeURL:url];
}
return YES;
}
如果嘗試打開
scheme_url://LSYKey1?oid=123&amount=456這樣的
url時就會執行上面代碼中注冊的
block。通過
parameters取得該
url的參數。
JLRoutes *routes = [JLRoutes routesForScheme:scheme_url];
[routes addRoute: LSYKey1 handler:^BOOL(NSDictionary * _Nonnull parameters) {
NSInteger oid = [parameters[@"oid"] integerValue]; //獲取url中oid參數
NSInteger amount = [parameters[@"amount"] integerValue]; //獲取url中amount參數
return YES;
}];
上面說明如何通過
JLRoutes注冊
URL的,具體組件化解耦是
Mediator對象與
Action對象配合
JLRoutes來實現的。
首先我們需要有一個文件來統一管理本地模塊的Key值,這裡在ComponentKey.h文件中進行管理
ComponentKey.h
#import
static NSString * const LSYKey1 = @"LSYKey1";
static NSString * const LSYKey2 = @"LSYKey2";
static NSString * const LSYKey3 = @"LSYKey3";
...
Mediator對象實現的方法如下:
Mediator.h
#import
@interface Mediator : NSObject
/**
JLRountes注冊的url
*/
+(void)componentRegister;
/**
本地通過key打開模塊
@param key 模塊key
@param dic 傳遞的參數
*/
+(void)openComponentForKey:(NSString *)key parameter:(NSDictionary *)dic;
/**
該方法通過運行時的機制讓Mediator對象與Action對象解耦並將消息轉發給Action對象執行
@param targetName Action對象名
@param actionName 方法名
@param params 傳遞的參數
*/
+(void)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params;
@end
Mediator.m
@implementation Mediator
+(void)componentRegister
{
JLRoutes *routes = [JLRoutes routesForScheme:scheme_url];
[routes addRoute: LSYKey1 handler:^BOOL(NSDictionary * _Nonnull parameters) {
[self performTarget:@"Action" action:@"performActionA" params:parameters];
return YES;
}];
[routes addRoute: LSYKey2 handler:^BOOL(NSDictionary * _Nonnull parameters) {
[self performTarget:@"Action" action:@"performActionB" params:parameters];
return YES;
}];
}
+(void)openComponentForKey:(NSString *)key parameter:(NSDictionary *)dic
{
//將本地調用傳入的Key與參數轉拼接成url最後通過JLRoutes處理
if (!key.length) {
return;
}
NSMutableString *urlString = [NSMutableString stringWithString:[NSString stringWithFormat:@"%@://%@",scheme_url,key]];
[urlString appendString:@"?"];
[dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
[urlString appendString:[NSString stringWithFormat:@"%@=%@&",key,obj]];
}];
[urlString deleteCharactersInRange:NSMakeRange(urlString.length-1, 1)];
NSURL *url = [NSURL URLWithString:[urlString copy]];
[JLRoutes routeURL:url];
}
+(void)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params
{
NSString *targetClassString = [NSString stringWithFormat:@"%@", targetName];
NSString *actionString = [NSString stringWithFormat:@"%@:", actionName];
Class targetClass = NSClassFromString(targetClassString);
id target = [[targetClass alloc] init];
SEL action = NSSelectorFromString(actionString);
if ([target respondsToSelector:action]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[target performSelector:action withObject:params];
#pragma clang diagnostic pop
} else {
// 這裡是處理無響應請求的地方
}
}
}
@end
Action對象是處理從Mediator對象轉發過來的消息。上面分別執行了
performActionA與
performActionB的方法。我們可以根據功能給這些方法分類,如果
performActionA與
performActionB的方法都是執行跳轉操作,那麼我們添加一個
Action(Jump)的category專門來處理跳轉操作。將這兩個方法寫入category中。
#import "Action+Jump.h"
@implementation Action (Jump)
-(void)performActionA:(NSDictionary *)params
{
//跳轉A界面
}
-(void)performActionB:(NSDictionary *)params
{
//跳轉B界面
}
@end
此時我們想要在本地調用
LSYKey1模塊並要傳入
oid與amount這兩個參數只要執行下面代碼即可:
[Mediator openComponentForKey:LSYKey1 parameter:@{@"oid":@123,@"amount":@"456"}];
遠程調用直接返回:
[JLRoutes routeURL:url];
因為我們在程序啟動已經注冊了所有的
url:
[Mediator componentRegister];
存在的問題
上面雖然實現了組件化但是還是存在一些問題需要後續改進
本地調用與遠程調用混在一起無法區別開無法實現「組件A」要調用「組件B」的某個方法這種業務場景組件過多有可能影響程序啟動速度