提出页面路由这个开发需求后,讨论决定先从iOS开始探索。如果确实值得,Android再跟进。
准备工作
先选定Routable作为路由中间件。因为它离我们的需求最接近,在使用过程中按需要对它进行改造。然后把所有视图控制器文件列在了表格里,共有90多张页面。初步计划,先把所有视图控制器都扫一遍,有疑点和难点的暂时放一放,回头再归类总结,思考解决方案。注:下面我会把视图控制器称做为页面
实践过程
使用页面路由,需先注册短链和相应视图控制器。短链里还需要给定参数列表:
[[YCRoutable sharedRouter] map:@"Coupon/:coupon_id" toController:[CouponViewController class]];
就像凯撒之前说的,短链的一个缺点就是不能带复杂参数。那么究竟算不算一个缺点,后面会给出我的一个思考。扫第一遍的时候还是很快的,如果只是简单参数的页面,改造起来非常简单。
在扫完第一遍后,也发现了几类不能直接改造的页面:
1,页面间存在回调函数:代理,Block,通知广播。
2,特殊的出入栈规则:不是顺序入栈,或不是顺序出栈。
3,复杂参数。
4,一个页面有多个初始化方式。
5,某些页面打开有前提条件:用户登录,用户登录且绑定了手机号。
页面间数据流动:代理,Block,通知消息
页面间回调的主要原因是,页面间有数据的流动。通知消息的解耦效果最好,但是范围不可控,更加适合需要广播的场合。Block和代理作用非常类似,但是Block需要额外暴露出接口,显式提供安插Block的口子;相比来讲,代理就没有那么高的要求,因此使用代理可以使得页面间进一步解耦。如下是一个代理的调用形势:
UIViewController *delegateVC = [YCPageRouter rootNav].viewControllers[ [YCPageRouter rootNav].viewControllers.count-2];
if ([delegateVC respondsToSelector:@selector(poiSelectedPageCall:)]) {
[delegateVC performSelector:@selector(poiSelectedPageCall:) withObject:poiMc];
}
通知消息本来对页面路由改造是没有影响的,不需要考虑。但是在改造过程中发现使用:(1)之前代码里滥用通知。比方,有张页面嵌套了很多层视图。当最里层视图有动作时,需要告知最上层通知时,开发人员用了通知的方式来实现。实际上这个时候需要反思的是,为什么会要将视图层级嵌套这么深。然后重新改造了页面,将视图层级拉平,完全就不需要通知。(2)通知消息的命名混乱。统一为“模块名+事件名”。(3)存在无用或无效的通知。删除不再使用的通知,并将所有通知消息统一到一处进行管理。
接着就是将页面间Block回调改为代理。有意思的是,在改造过程中发现有的页面根本就无需回调,完全可以自己完成动作。
回头再来思考下为什么存在页面间代理、Block、通知。它们的目的是为了页面间数据的流动;通知是一种依赖度很低的数据流动,有无通知不会影响该页面走向。代理是一种重度依赖的数据流动,意味着接受不到该数据,页面逻辑就无法执行,或者影响页面逻辑的走向。比方停车管理页面,如果没有停车优惠券选择页面,就无法继续完成停车费支付的逻辑。
特殊出入栈规则
页面路由中间件本身包装了pop和push页面,这可以让开发少写很多pop和push代码。另外的好处就是,限制开发人员自己创建其他navigationcontroller。对于这应用来讲,就一个root navigationcontroller,它的导航栏之类限定了统一风格。
缺点就是它顺序入栈顺序出栈,不能照顾到特殊的出入栈需求。比方,有个退款操作过程:订单详情->退款提交页面->退款结果页面;从退款结果页面回退的时候,需要跳过退款提交页面,之后回到订单详情页面。(1)之前的做法是:在提交退款请求成功后,直接操作页面栈,将顶部退款提交页面直接替换成退款结果页面。(2)现在的做法:在提交退款请求成功后,先pop退款提交页面,再push退款结果页面。从视觉可以感觉到,现在的做法下,先是款提交页面收起的动画,然后是退款结果页面从底部出现的动画。产品体验上也玩去可以接受。
还有个特殊的需求:评论页面里,评论发送成功后,pop评论页面,过渡到评论列表页;如果上个页面本身就是评论列表页面,那么就直接pop评论页面。(1)之前的做法是,要先查看前个页面是否是评论列表页,然后决定跳转动作。这就必须要知道评论列表页的具体class name,导致了页面间耦合。(2)现在的做法是,在改造了Routable中间件,增加了一类页面配置叫UILaunchMode,目前支持两种类型UILaunchStandard和UILaunchSingleTask。UILaunchSingleTask意味着,该页面在页面栈里只会存在一个实例。跳转到该页面时,如果该实例已经在堆栈里存在,就把该实例前的页面全部pop掉。
复杂参数
一个页面初始化时,需要传入复杂参数,这是短链所不能完成的。在解决复杂传参前,思考了下传参的作用是什么:页面代码只是一套逻辑,只有灌入参数,明确其运行的上下文(context)关系后,才能呈现相对应的页面;简单传参、无需传参说明前后两张页面没有什么承启关系;对照网页访问几乎没有碰到过需要传复杂参数的场景,原因是网页直接对接了数据库、加上用户会话(session),只要一个id就可以索引出上下文。对应的解决方案有,
1,减去不必要的传参,有些用不到参数就不用再传了。
2,不再传递结构体,提出出来每个都作为一个参数。
3,创建了一个AppContext类,用来保存登录用户的信息,以及其他App层面的上下文数据。
4,建立针对模块的Context类,来保存模块上下文数据。
多个初始化方式
一张页面有多个初始化方式,也就说明复用度高。复用度高真的是好事吗?有个停车券列表页面被用到两个场景里:场景1,显示所有停车券列表;场景2,结算停车费时,从停车券中选择对应的券。看起来两张页面长得很像,仔细分析一下会发现它们行为有很大区别:2个场景的初始上下文方式不一样;结算场景下,还需要有操作动作和操作数据回传。最终我们还是把这两张页面拆开,结果也没多写多少代码,逻辑反而清晰了很多。这是新手最容易犯错的地方,把“长得很像”的页面硬糅和在一起,还觉得自己掌握了面向对象的精髓,复用了代码。“复用”应该是:1,共用代码下沉。2,不是“长得像”复用,而是将本质上类似的东西复用。
当然有多种初始化还是必要的。
体会
开始做页面路由的时候是为了满足产品业务的需求,实际过程中体会到对项目开发的好处:(1)帮助我们理清页面的关系,最大化将页面与页面间进行解耦。统一了页面调用关系,传参方式,数值回传方式,堆栈管理方式。(2)短链方式做页面路由,可以迫使我们在以后新项目里,自然将页面间解耦。(3)为下面进一步模块化打下了基础。
思考
1,是不是所有的页面都要以短链方式暴露出去?这个问题应该从产品角度来考虑,产品原型中出现的页面都需要通过短链暴露。
2,以短链方式路由非常怪?其实不怪,就是网页开发的一套模式。
3,服务统一:要做到Android和iOS端服务统一,两端的页面一致,传参方式也要一致。
4,新老版本兼容性处理:服务更新后,如果老版本接到新的短链格式,也要能处理;或给出提示页面。
待做
1,还是有些特殊情况没有想好如何解决,比方将对象数组作为传参对象、特别的页面栈管理。
2,目前这套东西没有框架化,只能算是模板化。意味着有新人加入有学习成本、老人写代码可以随意按自己喜好不按照模板来。之后目标是,框架化。
3,将工程模块化,可以随意插拔。也方便进行测试(功能测试,自动化测试)。
网友评论