前言
前面写过一篇《iOS 组件化》,里面介绍了组件化和模块化的区别,模块化可以简单理解为业务模块的组件化
。
模块间通信
APP按业务划分成若干个模块后,不同模块之间的通信不能像原来那样直接调用了,一个模块并不知道另一个模块的存在,所以需要一个中介者来处理。
MGJRouter
通过url-block(protocal-block)方式管理组件,先注册,再使用。
例如商品详情页面
// 注册商品详情URL
[MGJRouter registerURLPattern:@"mgj://detail?id=:id" toHandler:^(NSDictionary *routerParameters) {
NSNumber *id = routerParameters[@"id"];
// create view controller with id
// push view controller
}];
// 打开商品详情
[MGJRouter openURL:@"mgj://detail?id=404"]
注册时根据pathComponents
构建一个多层的NSMutableDictionary,最后的block使用类似于Swift中的匿名参数_
作为key。
- (void)addURLPattern:(NSString *)URLPattern andHandler:(MGJRouterHandler)handler
{
NSMutableDictionary *subRoutes = [self addURLPattern:URLPattern];
if (handler && subRoutes) {
subRoutes[@"_"] = [handler copy];
}
}
- (void)addURLPattern:(NSString *)URLPattern andObjectHandler:(MGJRouterObjectHandler)handler
{
NSMutableDictionary *subRoutes = [self addURLPattern:URLPattern];
if (handler && subRoutes) {
subRoutes[@"_"] = [handler copy];
}
}
- (NSMutableDictionary *)addURLPattern:(NSString *)URLPattern
{
NSArray *pathComponents = [self pathComponentsFromURL:URLPattern];
NSMutableDictionary* subRoutes = self.routes;
for (NSString* pathComponent in pathComponents) {
if (![subRoutes objectForKey:pathComponent]) {
subRoutes[pathComponent] = [[NSMutableDictionary alloc] init];
}
subRoutes = subRoutes[pathComponent];
}
return subRoutes;
}
CTMediator
使用Target-Action方式,不需要注册,只要按规定命名规范命名Class和Selector即可。一个模块完成后,需要创建一个Target_{Module_Name}的类,把公开的方法(Action)添加到这里。然后新建一个CTMediator的Category
,Category根据上面Target中的Action方法提供对外接口。
Target_A.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface Target_A : NSObject
- (UIViewController *)Action_nativeFetchDetailViewController:(NSDictionary *)params;
- (id)Action_nativePresentImage:(NSDictionary *)params;
- (id)Action_showAlert:(NSDictionary *)params;
// 容错
- (id)Action_nativeNoImage:(NSDictionary *)params;
@end
Category CTMediator+CTMediatorModuleAActions.h
#import "CTMediator.h"
#import <UIKit/UIKit.h>
@interface CTMediator (CTMediatorModuleAActions)
- (UIViewController *)CTMediator_viewControllerForDetail;
- (void)CTMediator_showAlertWithMessage:(NSString *)message cancelAction:(void(^)(NSDictionary *info))cancelAction confirmAction:(void(^)(NSDictionary *info))confirmAction;
- (void)CTMediator_presentImage:(UIImage *)image;
- (UITableViewCell *)CTMediator_tableViewCellWithIdentifier:(NSString *)identifier tableView:(UITableView *)tableView;
- (void)CTMediator_configTableViewCell:(UITableViewCell *)cell withTitle:(NSString *)title atIndexPath:(NSIndexPath *)indexPath;
- (void)CTMediator_cleanTableViewCellTarget;
@end
在.m文件中主要是调用核心类CTMediator
的- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
方法。
在该方法中首先根据参数构建Target和Action
NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
上面的target如果是Swift,需要加上Swift中的Module
NSString *targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
然后使用runtime,得到Class和Selector。CTMediator使用了NSInvocation来执行方法,根据返回值的不同,做了不同的调用方式。
- (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
}
异步回调使用params的方式,有点隐晦,个人感觉没有MGJ的completion的方式直接。
总结
MGJRouter使用起来很简单,只要管理好需要使用的router就可以了。CTMediator使用Target-Action的方式也很优秀,我也是看了源代码才理解了Target-Action的概念。两个框架代码都比较简单,很容易看懂。个人比较喜欢MGJRouter的方式,也在网上看了它的变种,支持URL重写等功能,可以支持服务端下发,实现线上版本跳转切换。