iOS模块化-模块间通信

前言

前面写过一篇《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 
#import 

@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 

@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重写等功能,可以支持服务端下发,实现线上版本跳转切换。

你可能感兴趣的:(iOS模块化-模块间通信)