ios组件化实践之路由

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开篇

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342