基本概念
首先,對(duì)于 iOS 這種面向?qū)ο缶幊痰拈_發(fā)模式來(lái)說(shuō)拯坟,我們應(yīng)該遵循以下五個(gè)原則痢甘,即 SOLID 原則扣甲。
另外,組件可以認(rèn)為是可組裝的、獨(dú)立的業(yè)務(wù)單元,具有高內(nèi)聚斤儿,低耦合的特性励饵,是一種比較適中的粒度驳癌。就像用樂高拼房子一樣,每個(gè)對(duì)象就是一塊小積木役听。一個(gè)組件就是由一塊一塊的小積木組成的有單一功能的組合颓鲜,比如門表窘、柱子、煙囪甜滨。這讓我想到了viper
設(shè)計(jì)模式
iOS 開發(fā)中的組件乐严,不是 UI 的控件,也不是 ViewController 這種大 UI 和功能的集合衣摩。因?yàn)榘貉椋琔I 控件的粒度太小,而頁(yè)面的粒度又太大艾扮。iOS 組件既琴,應(yīng)該是包含 UI 控件、相關(guān)多個(gè)小功能的合集泡嘴,是一種粒度適中的模塊甫恩。
而對(duì)于組件間如何分層這個(gè)問(wèn)題,層級(jí)最好不要超過(guò)三個(gè)酌予,你可以這么設(shè)置:
- 底層可以是與業(yè)務(wù)無(wú)關(guān)的基礎(chǔ)組件磺箕,比如網(wǎng)絡(luò)和存儲(chǔ)等;
- 中間層一般是通用的業(yè)務(wù)組件霎终,比如賬號(hào)滞磺、埋點(diǎn)、支付莱褒、購(gòu)物車等击困;
- 最上層是迭代業(yè)務(wù)組件,更新頻率最高广凸。
在實(shí)踐中阅茶,,組件化的設(shè)計(jì)方法一般分為了協(xié)議式
和中間者
兩種架構(gòu)設(shè)計(jì)方案
協(xié)議式架構(gòu)設(shè)計(jì)主要采用的是協(xié)議式編程的思路:在編譯層面使用協(xié)議定義規(guī)范,實(shí)現(xiàn)在不同地方谅海,從而達(dá)到分布管理和維護(hù)組件的目的脸哀。這種方式也遵循了依賴反轉(zhuǎn)原則,是一種很好的面向?qū)ο缶幊痰膶?shí)踐扭吁。
但是撞蜂,這個(gè)方案的缺點(diǎn)也很明顯,主要體現(xiàn)在以下兩個(gè)方面:
- 由于協(xié)議式編程缺少統(tǒng)一調(diào)度層侥袜,導(dǎo)致難于集中管理蝌诡,特別是項(xiàng)目規(guī)模變大、團(tuán)隊(duì)變多的情況下枫吧,架構(gòu)管控就會(huì)顯得越來(lái)越重要浦旱。
- 協(xié)議式編程接口定義模式過(guò)于規(guī)范,從而使得架構(gòu)的靈活性不夠高九杂。當(dāng)需要引入一個(gè)新的設(shè)計(jì)模式來(lái)開發(fā)時(shí)颁湖,我們就會(huì)發(fā)現(xiàn)很難融入到當(dāng)前架構(gòu)中宣蠕,缺乏架構(gòu)的統(tǒng)一性。
中間者架構(gòu)甥捺。它采用中間者統(tǒng)一管理的方式抢蚀,來(lái)控制 App 的整個(gè)生命周期中組件間的調(diào)用關(guān)系。同時(shí)涎永,iOS 對(duì)于組件接口的設(shè)計(jì)也需要保持一致性思币,方便中間者統(tǒng)一調(diào)用。
拆分的組件都會(huì)依賴于中間者羡微,但是組間之間就不存在相互依賴的關(guān)系了。由于其他組件都會(huì)依賴于這個(gè)中間者惶我,相互間的通信都會(huì)通過(guò)中間者統(tǒng)一調(diào)度妈倔,所以組件間的通信也就更容易管理了。在中間者上也能夠輕松添加新的設(shè)計(jì)模式绸贡,從而使得架構(gòu)更容易擴(kuò)展盯蝴。
好的架構(gòu)一定是健壯的、靈活的听怕。中間者架構(gòu)的易管控帶來(lái)的架構(gòu)更穩(wěn)固捧挺,易擴(kuò)展帶來(lái)的靈活性, CTMediator 就是按照中間者架構(gòu)思路設(shè)計(jì)的尿瞭。
CTMediator
CTMediator 使用的是運(yùn)行時(shí)解耦闽烙,解耦核心方法如下所示:
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
// 適配swift
NSString *targetClassString = nil;
if (swiftModuleName.length > 0) {
targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
} else {
targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
}
// 獲取緩存的target
NSObject *target = self.cachedTarget[targetClassString];
if (target == nil) {
// 沒有緩存則創(chuàng)建
Class targetClass = NSClassFromString(targetClassString);
target = [[targetClass alloc] init];
}
// 創(chuàng)建消息
NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
SEL action = NSSelectorFromString(actionString);
if (target == nil) {
// 這里是處理無(wú)響應(yīng)請(qǐng)求的地方之一
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
return nil;
}
// 緩存target
if (shouldCacheTarget) {
self.cachedTarget[targetClassString] = target;
}
if ([target respondsToSelector:action]) {
// 發(fā)送請(qǐng)求
return [self safePerformAction:action target:target params:params];
} else {
// 這里是處理無(wú)響應(yīng)請(qǐng)求的地方,如果無(wú)響應(yīng)声搁,則嘗試調(diào)用對(duì)應(yīng)target的notFound方法統(tǒng)一處理
SEL action = NSSelectorFromString(@"notFound:");
if ([target respondsToSelector:action]) {
return [self safePerformAction:action target:target params:params];
} else {
// 這里也是處理無(wú)響應(yīng)請(qǐng)求的地方黑竞,在notFound都沒有的時(shí)候,
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
// 移除緩存
[self.cachedTarget removeObjectForKey:targetClassString];
return nil;
}
}
performTarget:action:params:shouldCacheTarget:
方法主要是對(duì) targetName 和 actionName 進(jìn)行容錯(cuò)處理疏旨,也就是對(duì)調(diào)用方法無(wú)響應(yīng)的處理很魂。
這個(gè)方法封裝了safePerformAction:target:params
方法,入?yún)?targetName 就是調(diào)用接口的對(duì)象檐涝,actionName 是調(diào)用的方法名遏匆,params 是參數(shù)。
并且代碼中同時(shí)還能看出只有滿足Target_ 前綴的類的對(duì)象
和 Action_ 的方法
才能被 CTMediator 使用谁榜。這時(shí)幅聘,我們可以看出中間者架構(gòu)的優(yōu)勢(shì),也就是利于統(tǒng)一管理惰爬,可以輕松管控制定的規(guī)則喊暖。
處理無(wú)響應(yīng)的情況
#pragma mark - private methods
- (void)NoTargetActionResponseWithTargetString:(NSString *)targetString selectorString:(NSString *)selectorString originParams:(NSDictionary *)originParams
{
// 給一個(gè)固定的target專門用于在這個(gè)時(shí)候頂上,處理這種請(qǐng)求的
SEL action = NSSelectorFromString(@"Action_response:");
NSObject *target = [[NSClassFromString(@"Target_NoTargetAction") alloc] init];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
params[@"originParams"] = originParams;
params[@"targetString"] = targetString;
params[@"selectorString"] = selectorString;
[self safePerformAction:action target:target params:params];
}
處理有action的情況
- (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];
// 對(duì)非對(duì)象的返回類型做一次封裝
// 如果返回值類型是 void
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);
}
// 如果是bool類型
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);
}
// 如果是浮點(diǎn)型
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);
}
// 如果是無(wú)符號(hào)整型
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
}
簡(jiǎn)單實(shí)例
彈出一個(gè)Alert
[[CTMediator sharedInstance] CTMediator_showAlertWithMessage:@"casa" cancelAction:nil confirmAction:^(NSDictionary *info) {
// 做你想做的事
}];
內(nèi)部實(shí)現(xiàn)
NSString * const kCTMediatorTargetA = @"A";
NSString * const kCTMediatorActionShowAlert = @"showAlert";
- (void)CTMediator_showAlertWithMessage:(NSString *)message cancelAction:(void(^)(NSDictionary *info))cancelAction confirmAction:(void(^)(NSDictionary *info))confirmAction
{
NSMutableDictionary *paramsToSend = [[NSMutableDictionary alloc] init];
if (message) {
paramsToSend[@"message"] = message;
}
if (cancelAction) {
paramsToSend[@"cancelAction"] = cancelAction;
}
if (confirmAction) {
paramsToSend[@"confirmAction"] = confirmAction;
}
[self performTarget:kCTMediatorTargetA
action:kCTMediatorActionShowAlert
params:paramsToSend
shouldCacheTarget:NO];
}
@interface Target_A : NSObject
- (id)Action_showAlert:(NSDictionary *)params;
@end
@implementation Target_A
- (id)Action_showAlert:(NSDictionary *)params
{
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
CTUrlRouterCallbackBlock callback = params[@"cancelAction"];
if (callback) {
callback(@{@"alertAction":action});
}
}];
UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"confirm" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
CTUrlRouterCallbackBlock callback = params[@"confirmAction"];
if (callback) {
callback(@{@"alertAction":action});
}
}];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"alert from Module A" message:params[@"message"] preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:cancelAction];
[alertController addAction:confirmAction];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
return nil;
}
@end
可以看出撕瞧,指定了類名和調(diào)用方法名陵叽,把參數(shù)封裝成字典傳進(jìn)去就能夠直接調(diào)用該方法了狞尔。
但是,這種運(yùn)行時(shí)直接硬編碼的調(diào)用方式也有些缺點(diǎn)巩掺,主要表現(xiàn)在兩個(gè)方面:
- 直接硬編碼的調(diào)用方式偏序,參數(shù)是以 string 的方法保存在內(nèi)存里,雖然和將參數(shù)保存在 Text 字段里占用的內(nèi)存差不多胖替,同時(shí)還可以避免.h 文件的耦合研儒,但是其對(duì)代碼編寫效率的降低也比較明顯。
- 由于是在運(yùn)行時(shí)才確定的調(diào)用方法独令,調(diào)用方式由 [obj method] 變成 [obj performSelector:@""]端朵。這樣的話,在調(diào)用時(shí)就缺少類型檢查燃箭,是個(gè)很大的缺憾冲呢。因?yàn)椋绻椒ê蛥?shù)比較多的時(shí)候招狸,代碼編寫效率就會(huì)比較低敬拓。