基本概念
首先,对于 iOS 这种面向对象编程的开发模式来说,我们应该遵循以下五个原则,即 SOLID 原则。
另外,组件可以认为是可组装的、独立的业务单元,具有高内聚,低耦合的特性,是一种比较适中的粒度。就像用乐高拼房子一样,每个对象就是一块小积木。一个组件就是由一块一块的小积木组成的有单一功能的组合,比如门、柱子、烟囱。这让我想到了viper
设计模式
iOS 开发中的组件,不是 UI 的控件,也不是 ViewController 这种大 UI 和功能的集合。因为,UI 控件的粒度太小,而页面的粒度又太大。iOS 组件,应该是包含 UI 控件、相关多个小功能的合集,是一种粒度适中的模块。
而对于组件间如何分层这个问题,层级最好不要超过三个,你可以这么设置:
- 底层可以是与业务无关的基础组件,比如网络和存储等;
- 中间层一般是通用的业务组件,比如账号、埋点、支付、购物车等;
- 最上层是迭代业务组件,更新频率最高。
在实践中,,组件化的设计方法一般分为了协议式
和中间者
两种架构设计方案
协议式架构设计主要采用的是协议式编程的思路:在编译层面使用协议定义规范,实现在不同地方,从而达到分布管理和维护组件的目的。这种方式也遵循了依赖反转原则,是一种很好的面向对象编程的实践。
但是,这个方案的缺点也很明显,主要体现在以下两个方面:
- 由于协议式编程缺少统一调度层,导致难于集中管理,特别是项目规模变大、团队变多的情况下,架构管控就会显得越来越重要。
- 协议式编程接口定义模式过于规范,从而使得架构的灵活性不够高。当需要引入一个新的设计模式来开发时,我们就会发现很难融入到当前架构中,缺乏架构的统一性。
中间者架构。它采用中间者统一管理的方式,来控制 App 的整个生命周期中组件间的调用关系。同时,iOS 对于组件接口的设计也需要保持一致性,方便中间者统一调用。
拆分的组件都会依赖于中间者,但是组间之间就不存在相互依赖的关系了。由于其他组件都会依赖于这个中间者,相互间的通信都会通过中间者统一调度,所以组件间的通信也就更容易管理了。在中间者上也能够轻松添加新的设计模式,从而使得架构更容易扩展。
好的架构一定是健壮的、灵活的。中间者架构的易管控带来的架构更稳固,易扩展带来的灵活性, CTMediator 就是按照中间者架构思路设计的。
CTMediator
CTMediator 使用的是运行时解耦,解耦核心方法如下所示:
- (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) {
// 没有缓存则创建
Class targetClass = NSClassFromString(targetClassString);
target = [[targetClass alloc] init];
}
// 创建消息
NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
SEL action = NSSelectorFromString(actionString);
if (target == nil) {
// 这里是处理无响应请求的地方之一
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
return nil;
}
// 缓存target
if (shouldCacheTarget) {
self.cachedTarget[targetClassString] = target;
}
if ([target respondsToSelector:action]) {
// 发送请求
return [self safePerformAction:action target:target params:params];
} else {
// 这里是处理无响应请求的地方,如果无响应,则尝试调用对应target的notFound方法统一处理
SEL action = NSSelectorFromString(@"notFound:");
if ([target respondsToSelector:action]) {
return [self safePerformAction:action target:target params:params];
} else {
// 这里也是处理无响应请求的地方,在notFound都没有的时候,
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
// 移除缓存
[self.cachedTarget removeObjectForKey:targetClassString];
return nil;
}
}
performTarget:action:params:shouldCacheTarget:
方法主要是对 targetName 和 actionName 进行容错处理,也就是对调用方法无响应的处理。
这个方法封装了safePerformAction:target:params
方法,入参 targetName 就是调用接口的对象,actionName 是调用的方法名,params 是参数。
并且代码中同时还能看出只有满足Target_ 前缀的类的对象
和 Action_ 的方法
才能被 CTMediator 使用。这时,我们可以看出中间者架构的优势,也就是利于统一管理,可以轻松管控制定的规则。
处理无响应的情况
#pragma mark - private methods
- (void)NoTargetActionResponseWithTargetString:(NSString *)targetString selectorString:(NSString *)selectorString originParams:(NSDictionary *)originParams
{
// 给一个固定的target专门用于在这个时候顶上,处理这种请求的
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];
// 对非对象的返回类型做一次封装
// 如果返回值类型是 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);
}
// 如果是浮点型
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
}
简单实例
弹出一个Alert
[[CTMediator sharedInstance] CTMediator_showAlertWithMessage:@"casa" cancelAction:nil confirmAction:^(NSDictionary *info) {
// 做你想做的事
}];
内部实现
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
可以看出,指定了类名和调用方法名,把参数封装成字典传进去就能够直接调用该方法了。
但是,这种运行时直接硬编码的调用方式也有些缺点,主要表现在两个方面:
- 直接硬编码的调用方式,参数是以 string 的方法保存在内存里,虽然和将参数保存在 Text 字段里占用的内存差不多,同时还可以避免.h 文件的耦合,但是其对代码编写效率的降低也比较明显。
- 由于是在运行时才确定的调用方法,调用方式由 [obj method] 变成 [obj performSelector:@""]。这样的话,在调用时就缺少类型检查,是个很大的缺憾。因为,如果方法和参数比较多的时候,代码编写效率就会比较低。
网友评论