[toc]
现状
- 编译
- 开发效率(编译;模糊、不便捷的已有能力)
- 代码混乱,层次不明,沉重的冗余,杂乱的引用
- 测试
模块化
业务模块化、功能组件化
模块化方案对比维度
使用是否方便
1.1 快速调用(接口可读性)
1.2 传参方便、规范
1.3 便捷的管理和维护解耦程度
2.1 能否完全解耦,是否需要额外依赖
2.2 新的同样功能的模块能否快速替换现有模块模块更新是否快捷
3.1 新版模块快速被其它业务模块使用
3.2 模块接口变更时能否被其它模块感知回滚要方便
4.1 当发现某个模块不能上线需要紧急回滚到上个版本时改动范围是否可控。改造项目所需工作量
5.1 需要投入多少人力能将现有的项目实现模块化。性能
6.1 模块间方法频繁调用性能要可控
基本原理
类型I:操作映射型
类型II:反射型
类型III:抽象型
实例分析
1. MGJRouter
ModuleA注册
/// 例1 普通
[MGJRouter registerURLPattern:@"mgj://foo/bar" toHandler:^(NSDictionary *routerParameters) {
NSLog(@"routerParameters[@"userInfo"] = %@", routerParameters[@"userInfo"]);
}];
/// 例2 block参数
[MGJRouter registerURLPattern:@"mgj://requestUserInfo" toHandler:^(NSDictionary *routerParameters) {
NSString *url = routerParameters[@"url"];
[MJHttpManager.manager requestUserInfoWithUrl:url completion:^(NSDictionary * _Nullable userInfo, NSError *_Nullable error) {
void (^completion)(id result) = routerParameters[MGJRouterParameterCompletion];
if (completion) {
completion(userInfo);
}
}];
}];
/// 例3 返回NSObject
[MGJRouter registerURLPattern:@"mgj://loginViewController" toObjectHandler:^(id)(NSDictionary *routerParameters) {
MGJLoginViewController *loginVC = [[MGJLoginViewController alloc] init];
return loginVC;
}];
/// 例4
#define TEMPLATE_URL @"mgj://search/:keyword"
[MGJRouter registerURLPattern:TEMPLATE_URL toHandler:^(NSDictionary *routerParameters) {
NSLog(@"search[keyword]:%@", routerParameters[@"keyword"]); // Hangzhou
}];
ModuleB调用
/// 例1 普通
[MGJRouter openURL:@"mgj://foo/bar" withUserInfo:@{@"user_id": @1998} completion:nil];
/// 例2 block参数
[MGJRouter openURL:@"mgj://requestUserInfo?url=xxxx" withUserInfo:nil completion:^(NSDictionary *result){
NSLog(@"用户信息 = %@",result);
}];
/// 例3 返回NSObject
MGJLoginViewController *loginVC = [MGJRouter objectForURL:@"mgj://loginViewController"];
if ([loginVC isKindOfClass:[MGJLoginViewController class]]) {
NSLog(@"同步获取 登录VC 成功");
} else {
NSLog(@"同步获取 登录VC 失败");
}
/// 例4
[MGJRouter openURL:[MGJRouter generateURLWithPattern:TEMPLATE_URL parameters:@[@"Hangzhou"]]];
核心
+ (void)openURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo completion:(void (^)(id result))completion
{
URL = [URL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSMutableDictionary *parameters = [[self sharedInstance] extractParametersFromURL:URL matchExactly:NO];
[parameters enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) {
if ([obj isKindOfClass:[NSString class]]) {
parameters[key] = [obj stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}
}];
if (parameters) {
MGJRouterHandler handler = parameters[@"block"];
if (completion) {
parameters[MGJRouterParameterCompletion] = completion;
}
if (userInfo) {
parameters[MGJRouterParameterUserInfo] = userInfo;
}
if (handler) {
[parameters removeObjectForKey:@"block"];
handler(parameters);
}
}
}
优点:
- 核心代码量少且简白,便于维护、定位问题。
- 适合应用于简单页面级解耦。
缺点:
- 注册、调用、传参存在大量硬编码,而且接口也太松散了,官方建议参考
例4
来规避这个问题,但是TEMPLATE_URL
又应该定义在哪里,业务模块怎样暴露自己的能力?而且宏
也只能解决部分问题。 - 由于多了注册操作,在哪里以及什么时候注册又是个问题。使用block的形式注册,也容易循环引用。
- block作为参数的调用很不智能,block只能有一个为
NSDictionary
类型的参数。 - 同步返回数据很不友好,只能返回
id
类型的数据(例3)。 - 模块接口的声明者和模块接口的实现者方相互无法感知对方的变动。
2. CTMediator
Adapter层是CTMediator的分类,增加业务模块的过程也就是增加分类的过程。
ModuleB调用形式:
/// 例1
[[CTMediator sharedInstance] CTMediator_showAlertWithMessage:@"弹窗" cancelAction:nil confirmAction:^(NSDictionary *info) {
// Just do it
}];
/// 例2
UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForDetail];
Adapater_A层:
- (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:@"Module_A"
action:@"showAlert"
params:paramsToSend
shouldCacheTarget:NO];
}
CTMediator核心方法:
// 核心接口
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget;
// 核心方法
- (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);
}
……
}
ModuleA实现:
@interface Module_A : NSObject
- (id)showAlert:(NSDictionary *)params;
@end
@implementation Module_A
- (id)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
优点:
- 核心代码量少,便于维护
- 业务模块间无需硬编码即可实现调用
- 业务库无需向
CTMediator
注册
缺点:
- Adapater层是个分类,不同业务模块间方法名不能同名
-
safePerformAction:target:params:
的实现方式导致业务库的方法只能是methodName:
形式且参数必须为NSDictionary
类型 - Adapater层及传参扔采用硬编码方式调用导致业务库接口变更Adapater层无法感知(无编译错误),运行时才会Crash
3. Poseidon(改良版BeeHive)
模块调用
// 例1
id<HomeModuleProtocol> homeModule = [PDModuleManager.manager moduleInstanceForModuleProtocol:@protocol(HomeModuleProtocol)];
UIViewController *vc = homeModule.homeViewController;
[self presentViewController:vc animated:YES completion:nil];
// 例2
id<CameraModuleProtocol> cameraModule = [PDModuleManager.manager moduleInstanceForModuleProtocol:@protocol(CameraModuleProtocol)];
[cameraModule pickAnPhotoWithCompletion:^(NSData *imageData, NSError * _Nullable error) {
if (error) {
NSLog(@"Error: %@", error.localizedDescription);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:error.localizedDescription delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[alert show];
} else {
self.imageView.image = [UIImage imageWithData:imageData];
}
}];
// 例3
UIViewController *vc = PDModule(HomeModuleProtocol).homeViewController;
[self presentViewController:vc animated:YES completion:nil];
接口层
@protocol CameraModuleProtocol <PDModule>
- (void)pickAnPhotoWithCompletion:(void(^)(NSData *imageData, NSError *error))completion;
@end
模块实现
@interface CameraModule : NSObject <CameraModuleProtocol>
@end
@PD_EXPORT_MODULE(CameraModuleProtocol, CameraModule);
@implementation CameraModule
- (void)pickAnPhotoWithCompletion:(void (^)(NSData *, NSError *))completion {
[[[ImagePickerController alloc] init] showWithCompletion:completion];
}
@end
核心
// 声明
@PD_EXPORT_MODULE(CameraModuleProtocol, CameraModule);
char * kCameraModuleProtocol __attribute((used, section("__DATA, 'PDModule'"))) = "{\"\"CameraModuleProtocol\"\":\"CameraModule\"\"\"}"
__attribute__((constructor))
void initProphet() {
_dyld_register_func_for_add_image(__pd_dyld_callback);
}
static void __pd_dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide) {
// read modules
NSArray<NSString *> *services = __pd_readConfiguration("PDModule",mhp);
// PDModuleManager register modules
........
}
static NSArray<NSString *>* __pd_readConfiguration(char *sectionName,const struct mach_header *mhp) {
NSMutableArray *configs = [NSMutableArray array];
unsigned long size = 0;
const struct mach_header_64 *mhp64 = (const struct mach_header_64 *)mhp;
uintptr_t *memory = (uintptr_t *)getsectiondata(mhp64, SEG_DATA, sectionName, &size);
unsigned long counter = size / sizeof(void *);
for(int idx = 0; idx < counter; ++idx) {
char *string = (char *)memory[idx];
if (!string) { continue; }
NSString *str = [NSString stringWithUTF8String:string];
if (!str) {
continue;
} else {
[configs addObject:str];
}
}
return configs;
}
优点
- 无硬编码,接口可读性强调用和传参和原生几乎无差,理论上调用效率也比反射方案更高。
- 模块接口变动可在编译阶段报错(有利有弊吧)
- 核心代码量少且简单,有利于维护
缺点
- 建议新建专门遵守协议的类(还算可接受吧,毕竟频率不高)
- 接口层和实现层依旧不能相互感知,变动无法通知到对方
补充
模块生命周期、模块保活、 模块优先级、application方法的透传
4. 简白Category型
ModuleB调用
UIViewController *vc = [MIModuleA playAudioViewControllerWithSongID:@"0001"];
声明层
@interface MIModuleA : NSObject
+ (UIViewController *)playerViewControllerWithSongID:(NSString *)songID;
@end
@implementation MIModuleA
/// 默认实现
+ (UIViewController *)playerViewControllerWithSongID:(NSString *)songID {
return [[UIViewController alloc] init];
}
@end
ModuleA实现
@implementation MIModuleA (implementationA)
+ (UIViewController *)playerViewControllerWithSongID:(NSString *)songID {
return [[PlayerViewController alloc] initWithSongID:songID];
}
@end
优点:
- 开局一个分类方法全靠累加,方法名、传参无需硬编码,调用简单直白,Category的方式理论上效率还可能高些。
- 由于过于简白基本无需维护代码。
缺点:
- 接口层更改API后模块实现层无法感知修改。
- 由于MIModuleA (implementationA)过于单薄,不方便在其中添加复杂逻辑。
- 分类重写方法的过程中方法名存在出错而无报错的情况。
难点
- 随着项目增长单个子业务模块样例工程所依赖的业务模块就很繁多导致快速编译又变成了奢望
- 模块间传通用对象
- 怎样快速回滚单个子业务模块