1.組件化基本分層
-
組件化的分層思想是三層
- 基礎(chǔ)模塊
- 通用模塊
- 業(yè)務(wù)模塊
模塊的集成順序是從下至上对室,從
基礎(chǔ)模塊
到通用模塊
到業(yè)務(wù)模塊
合冀,但是依賴的順序是從上到下怖喻,業(yè)務(wù)
依賴通用
,通用
依賴基礎(chǔ)
一二層模塊可以看做是同一個層次宪哩,也可以是兩個層次,但是即便是同一個層次第晰,也要劃分為兩個目錄
2.業(yè)務(wù)模塊
-
通常項目模塊間的關(guān)系大致如下圖
- 各個模塊多少都有關(guān)系锁孟,模塊間進行通訊彬祖,需要導(dǎo)入彼此
-
模塊間進行組件化首先要解決模的問題就是模塊間之間的耦合關(guān)系,基本的思路是建立中間層品抽,各個模塊之間通過中間層進行通訊储笑。如下圖
3.CTMediator 通訊中間框架介紹
- 框架地址 https://github.com/casatwy/CTMediator
-
代碼結(jié)構(gòu)
- 框架的基本思路是
- 將每個模塊的業(yè)務(wù)調(diào)用隔離出一個獨立Target(這個和OC的target-action模式?jīng)]有任何關(guān)系)層,該層作為該模塊通訊的入口圆恤,通俗的講就是這個模塊的聲明文件突倍,對該模塊的方法調(diào)用入口進行一次封裝
- Target
- 在代碼封裝過程可以進行一些業(yè)務(wù)層面的判斷或者容錯
- 框架代碼思路
- 將方法的調(diào)用轉(zhuǎn)換為方法的調(diào)用轉(zhuǎn)換為不導(dǎo)入某模塊的情況下去調(diào)用該模塊的方法,避免模塊間的導(dǎo)入
- 以類名盆昙,方法名字符串形式進行調(diào)用羽历,通過方法簽名 + NSInvocation 方式對方法調(diào)用進行封裝,通過
performSelector
進行調(diào)用 - 對于參數(shù)淡喜,target, action等進行容錯處理
4.增加分類
image.png
4.1總體介紹
盡管CTMediator的代碼整體比較少秕磷,邏輯也不是很復(fù)雜,但是由于是避免對三方框架強耦合炼团,也避免框架的代碼發(fā)生過大的變化澎嚣,另外增加分類也是為了進一步解耦合,總結(jié)一下大致為以下幾點
- 避免項目或者模塊對
CTMediator
框架進行強耦合 - 防止
CTMediator
框架代碼出現(xiàn)比較大的升級或者停止維護等 - 對模塊增加一層隔離瘟芝,每個模塊需要暴露給外部調(diào)用的都寫在分類里易桃,如果想調(diào)用該模塊的方法,進導(dǎo)入該模塊對應(yīng)的分類即可模狭。
- 比如登錄模塊A颈抚,有登錄方法,注冊方法嚼鹉,忘記密碼方法贩汉,如果C在某個業(yè)務(wù)需要時需要登錄,這時只需要引入A分類即可
4.2 CTMediator代碼 和 demo介紹
下面我們針對在github給出的demo進行介紹
1. 項目整體介紹
-
項目代碼結(jié)構(gòu)
-
代碼運行起來锚赤,是一個tableView
- 我們以
present image
這個操作
2. 這個功能是將 DemoModuleADetailViewController
modal出來匹舞,并需要往這個控制器里傳遞一個UIImage,DemoModuleADetailViewController 一下簡稱為模塊A
-
模塊A
聲明暴露了一個imageView,用于賦值
@interface DemoModuleADetailViewController : UIViewController
@property (nonatomic, strong, readonly) UILabel *valueLabel;
@property (nonatomic, strong, readonly) UIImageView *imageView;
@end
-
模塊A
的target 層Target_A
類對應(yīng)的方法及其實現(xiàn)為如下线脚,主要是對參數(shù)解析出來赐稽,并進行賦值
- (id)Action_nativePresentImage:(NSDictionary *)params
{
DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];
viewController.valueLabel.text = @"this is image";
viewController.imageView.image = params[@"image"];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:viewController animated:YES completion:nil];
return nil;
}
- 方法名
Action_nativePresentImage
根據(jù)是根據(jù)目前CTM的規(guī)則拼接出來,方法名的規(guī)則是Action_
拼上方法名浑侥,nativePresentImage
是我們可以定義的方法的名字姊舵,這名字在分類的層面進行聲明拼接,在demo是定義成了一個static string
.我的理解起名和如何定義字符串寓落,只要項目內(nèi)部約定好就行括丁,大家都根據(jù)這規(guī)則就好。
3. 第二步解決了方法名的問題伶选,然后我們整體看下方法的調(diào)用史飞,從cell的點擊一步一步
-
cell 點擊尖昏,調(diào)用CTM分類方法
-
分類方法的實現(xiàn)
- 調(diào)用到CTM內(nèi)部方法,進行類 和 方法名的轉(zhuǎn)換构资,創(chuàng)建對象抽诉,內(nèi)部應(yīng)做了相應(yīng)的注釋,下一步對
performSelector:
進一步封裝
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
// tagrt判空
if (targetName == nil || actionName == nil) {
return nil;
}
// 對swift的特殊標(biāo)記
NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
// 拼接target-action的tagert,就是方法調(diào)用的類名的字符串吐绵,類名規(guī)則為Target_方法名
NSString *targetClassString = nil;
if (swiftModuleName.length > 0) {
targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
} else {
// 類名規(guī)則為Target_方法名
targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
}
// 做了類對象的緩存迹淌,避免多次創(chuàng)建對象
NSObject *target = self.cachedTarget[targetClassString];
// 類對象
if (target == nil) {
Class targetClass = NSClassFromString(targetClassString);
target = [[targetClass alloc] init];
}
// 處理方法名字 拼接規(guī)則為 Action_方法名
NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
// 方法名轉(zhuǎn) SEL
SEL action = NSSelectorFromString(actionString);
// 容錯處理
if (target == nil) {
// 這里是處理無響應(yīng)請求的地方之一,這個demo做得比較簡單拦赠,如果沒有可以響應(yīng)的target巍沙,就直接return了。實際開發(fā)過程中是可以事先給一個固定的target專門用于在這個時候頂上荷鼠,然后處理這種請求的
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
return nil;
}
// 緩存對象
if (shouldCacheTarget) {
self.cachedTarget[targetClassString] = target;
}
if ([target respondsToSelector:action]) {
// 底層方法調(diào)用
return [self safePerformAction:action target:target params:params];
} else {
// 這里是處理無響應(yīng)請求的地方句携,如果無響應(yīng),則嘗試調(diào)用對應(yīng)target的notFound方法統(tǒng)一處理
SEL action = NSSelectorFromString(@"notFound:");
if ([target respondsToSelector:action]) {
return [self safePerformAction:action target:target params:params];
} else {
// 這里也是處理無響應(yīng)請求的地方允乐,在notFound都沒有的時候矮嫉,這個demo是直接return了。實際開發(fā)過程中牍疏,可以用前面提到的固定的target頂上的蠢笋。
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
[self.cachedTarget removeObjectForKey:targetClassString];
return nil;
}
}
}
- 此部分主要是對參數(shù)進行容錯,對
performSelector
進一步封裝鳞陨,并返回方法調(diào)用方的對象
- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params
{
NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
if(methodSig == nil) {
return nil;
}
const char* retType = [methodSig methodReturnType];
if (strcmp(retType, @encode(void)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
return nil;
}
if (strcmp(retType, @encode(NSInteger)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
NSInteger result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(BOOL)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
BOOL result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(CGFloat)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
CGFloat result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(NSUInteger)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
NSUInteger result = 0;
[invocation getReturnValue:&result];
return @(result);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
}
至此一個完整CTM調(diào)用就結(jié)束了昨寞,大致的流程為