全栈汇总iOS开发者IOS

ios组件化实践之路由

2018-10-06  本文已影响25人  _onePiece
router.png

前言

其实早有很多大佬,写过路由的设计方案,更有一些开源的代码例子来抛砖引玉.但愿本文也有一些闪光点,可以被当做借鉴.首先我希望读者可以按照顺序先阅读其它几篇博客,这些都是精品,如果仅仅是觉得好,收藏起来留待他日我觉得是暴殄天物了.因为我发现收藏或者喜欢的博客,往往束之高阁.
而且在吸收了大佬的思想,作为受益者,我也有义务去推广.

limboy蘑菇街 App 的组件化之路可以说是先驱者了,先阅读这篇可以对路由的设计及作用有一点大致的理解.我认为在Load中注册的方式不可取,这会导致项目庞大后的常驻内存以及注册分散的问题.

然后读casatwyiOS应用架构谈 组件化方案,该文阐述了蘑菇街的不足,以及Target-Action模式的优势.我认为这是一个很好的思路,主动发现服务的功能很强大.
还有在现有工程中实施基于CTMediator的组件化方案示例.

沟通总是容易有不够充分的地方,为此limboy写了蘑菇街 App 的组件化之路·续来补充阐述不足及吸收Target-Action的思想而完善Protocol/URL 注册.

当然还有其它的路由设计方案,一缕殇流化隐半边冰霜
iOS 组件化 —— 路由设计思路分析中总结的很好,在看完我推荐的几篇博文后,这一篇阅读速度可以加快.

我的观点

JLRoutes : 降龙十八掌,内力深厚.

Routable ; 一阳指(不知道怎么描述了).

HHRouter : 缥缈剑法,灵动(动态绑定入参).

MGJRouter : 七伤拳,初练不觉,越练越伤,伤及肺腑.

CTMediator : 打狗棒法,招式新颖,变化多端.

我的实践结果

我是基于Target-Action的思想,来写自己的路由的.但是和CTMediator不同的是,我没有基于Runtime来做主动发现服务,更没有利用方法签名指定Target来执行Action.因为CTMediator不能算是路由,而是一整个组件化的方案(CTMediator的demo可以自行下载).可以明显看出下面的代码并不是路由需要做的事情.本文只写路由,负责页面跳转及一个参数保存.

TableViewController.m

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 通过mediator来配置cell实例,由于target已经被cache了,高频调用不是问题。
    return [[CTMediator sharedInstance] CTMediator_tableViewCellWithIdentifier:@"cell" tableView:tableView];
}

CTMediator+CTMediatorModuleAActions.m
- (UITableViewCell *)CTMediator_tableViewCellWithIdentifier:(NSString *)identifier tableView:(UITableView *)tableView
{
    return [self performTarget:kCTMediatorTargetA
                        action:kCTMediatorActionCell
                        params:@{
                                 @"identifier":identifier,
                                 @"tableView":tableView
                                 }
             shouldCacheTarget:YES];
}

为了方便使用者调用,使用链式语法.另外虽然ViewController是push还是present或者是其它的方式是由调用者决定的,但是我认为这个没有必要每一个地方都写同样的代码.可以把转场方式当做参数来传递,这同样是调用者决定的,所以你会看到一个非常简介的语法及调用方式

入参,回调,转场方式,动画方式,一共四个维度,16种情况,本文不一一列出了
//push
1.无入参,无回调
    ShareRouter.target(SNRouterPath00001).push();
2.无入参,有回调
    ShareRouter.target(SNRouterPath01001).push().back(^(id data) {
            NSLog(@"%@", data);
    });
3.有入参,无回调
    ShareRouter.target(SNRouterPath02001)
    .send(@"有入参,无回调 入参为 nice")
    .push();
4.有入参,有回调
     ShareRouter.target(SNRouterPath03001)
     .send(@"wupeng, send data").push()
     .back(^(id data) {
         NSLog(@"%@", data);
      });
      
//接收数据
//这里可以借鉴HHRouter将入参和回调与ViewController,动态绑定,这样
ShareRouter.sendData = @"有入参,有回调, 入参为 wupeng";
//回调数据
ShareRouter.backData = @"有入参,有回调, 回调为wupeng";

路由组成部分

1.RouterPath:包含所有ViewController的枚举,这个以说是手动注册,也可以说不是.因为业务需要,统一配置路由码,比如00001,前两位表示组件编号,后三位表示组件00的001页面.如果没有一个业务需求是不需要这一层的.但是我认为最好加上这一层,方便管理.而且这映射关系字典是@(RouterPath):@"TargetViewController",这样并不会有多少常驻内存问题.还有另外一个好处就是不用硬编码.比如一般的URL注册mgj://router/path这样就会造成硬编码,或者是CTMediator的Target也是硬编码.当然他们也可以将这部分抽出来自定义,统一管理.

typedef NS_ENUM(NSUInteger, SNRouterPath) {
    //module00
    SNRouterPath00000 = 0,
    SNRouterPath00001,
    SNRouterPath00002,
    SNRouterPath00003,
    SNRouterPath00004,
    //module01
    SNRouterPath01000 = 1000,
    SNRouterPath01001,
    SNRouterPath01002,
    SNRouterPath01003,
    SNRouterPath01004,
    //module02
    SNRouterPath02000 = 2000,
    SNRouterPath02001,
    SNRouterPath02002,
    SNRouterPath02003,
    SNRouterPath02004,
    //module03
    SNRouterPath03000 = 3000,
    SNRouterPath03001,
    SNRouterPath03002,
    SNRouterPath03003,
    SNRouterPath03004,
};

2.RouterPathMap:将RouterPath映射成ViewController
另外写了Router的Category,来添加映射关系,为了更加便于阅读,将每个组件隔离开来,然后通过NSDictory+Add的category来使字典相加,获取所有的map映射关系.

- (NSDictionary *)maps {
    NSDictionary *module00 = [NSDictionary dictionary];
    NSDictionary *module01 = [NSDictionary dictionary];
    NSDictionary *module02 = [NSDictionary dictionary];
    NSDictionary *module03 = [NSDictionary dictionary];
   
    module00 = @{
                 @(SNRouterPath00000) : @"ViewController0",
                 @(SNRouterPath00001) : @"ViewController0",
                 @(SNRouterPath00002) : @"ViewController0",
                 @(SNRouterPath00003) : @"ViewController0",
                 @(SNRouterPath00004) : @"ViewController0",
                 };
    module01 = @{
                 @(SNRouterPath01000) : @"ViewController1",
                 @(SNRouterPath01001) : @"ViewController1",
                 @(SNRouterPath01002) : @"ViewController1",
                 @(SNRouterPath01003) : @"ViewController1",
                 @(SNRouterPath01004) : @"ViewController1",
                 };
    module02 = @{
                 @(SNRouterPath02000) : @"ViewController2",
                 @(SNRouterPath02001) : @"ViewController2",
                 @(SNRouterPath02002) : @"ViewController2",
                 @(SNRouterPath02003) : @"ViewController2",
                 @(SNRouterPath02004) : @"ViewController2",
                 };
    module03 = @{
                 @(SNRouterPath03000) : @"ViewController3",
                 @(SNRouterPath03001) : @"ViewController3",
                 @(SNRouterPath03002) : @"ViewController3",
                 @(SNRouterPath03003) : @"ViewController3",
                 @(SNRouterPath03004) : @"ViewController3",
                 };
   
   module00 = [module00 add:module01, module02, module03, nil];
   return module00;
}

3.CurrentViewController:当前ViewController.这个其实是不属于Router的,但是确实这个app共有的,是任何地方的可以调用的.由于Router需要管理转场,所以使用了CurrentViewController.这个再UIWindow+Current的category中.

4.目前SNRouter是对<ReactiveObjC.h>有依赖的,因为我认为RAC的信号机制,很符合路由.但是在实践的过程中,有些用牛刀了.真正用到的也就是一个RACSubject,如果项目里面没有<ReactiveObjC.h>,仅仅是因为SNRouter就添加RAC是不值得的.为此我把RAC的订阅部分和发送部分都放到放到pushAnimated: 和 presentAnimated: 中.这样稍微改一下这两个方法变可以解除RAC的依赖. 留个小彩蛋,读者请自行尝试.
以下是SNRouter的所有参数,目前都是本地调用.远程通过URL调用,稍后我在加上.

#import <Foundation/Foundation.h>
#import <ReactiveObjC.h>
#import "SNRouterPath.h"

#define ShareRouter [SNRouter router]

typedef void(^SendBlock)(id data);

@interface SNRouter : NSObject

@property (nonatomic, copy, readonly) SNRouter *(^ target)(SNRouterPath targetPath);

@property (nonatomic, weak) id sendData;

@property (nonatomic, copy, readonly) SNRouter *(^ send)(id data);

@property (nonatomic, weak) id backData;

@property (nonatomic, copy, readonly) SNRouter *(^ back)(SendBlock back);

@property (nonatomic, copy, readonly) SNRouter *(^ push)(void);

@property (nonatomic, copy, readonly) SNRouter *(^ pushAnimatedNO)(void);

@property (nonatomic, copy, readonly) SNRouter *(^ present)(void);

@property (nonatomic, copy, readonly) SNRouter *(^ presentAnimatedNO)(void);

+ (instancetype)router;

本文demo

写作记录

1.2018.10.6开篇

上一篇下一篇

猜你喜欢

热点阅读