ios组件化实践之路由

作者: _onePiece | 来源:发表于2018-10-06 15:11 被阅读25次
    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开篇

    相关文章

      网友评论

        本文标题:ios组件化实践之路由

        本文链接:https://www.haomeiwen.com/subject/nfqhaftx.html