iOS撸一个简单路由Router的实现代码

平常开发中用户点击头像, 进入个人主页,这看似平常的操作, 背后极有可能会牵扯到多个模块。 再如: 视频模块的播放页, 有与视频相关的音乐,点击这些音乐,需要跳转到音乐模块的播放页, 这样视频与音乐模块之间,不可避免的会产生依赖或耦合。 这个问题让人脑壳疼,相信很多朋友都这样做过,写一些代理或通知, 不停的传递事件; 有时干脆直接导入另一个模块。

因为我在公司独立开发, 顾及少一点,可以拿公司项目做实践,在尝试组件化的过程中, 了解到了路由, 对于解决上述问题, 有极大的帮助。因此我想总结并与大家分享一下。

什么是移动端路由层:

路由层的概念在服务端是指url请求的分层解析,将一个请求分发到对应的应用处理程序。移动端的路由层指的是将诸如App内页面访问、H5与App访问的访问请求和App间的访问请求,进行分发处理的逻辑层。

移动端路由层需要解决的问题:

1.对外部提供远程访问的功能,实现跨应用调用响应,包括H5应用调用、其他App应用调用、系统访问调用等
2.原生页面、模块、组件等定义,统称为资源(Resource),在跨应用调用和路由层在不同端实现的业务表现需要一致的前提下,需要对资源进行定义,在路由提供内部请求分发的时候则可以提供不依赖对外进行资源定义的功能
3.外部调用如何使用统一标示(Uniform)进行表示资源
4.如何在移动端统一定义访问请求的过程,从而达成移动端与web端的统一性
5.如何更好的兼容iOS、Android的系统访问机制、App链接协议、web端路由机制与前端开发规范等
6.如何兼容各平台(Android、iOS)App页面导航机制
7.如何解决安全访问问题
8.移动端在客户端进行动态配置

移动端路由所应用的场景:

0.H5页面与App原生页面、模块与组件的交互
1.App与App之间的相互访问
2.App内部页面跳转、模块调度与组件加载等
3.推送与通知系统解除硬编码的逻辑,动态访问原生资源,更好的支持通过通知和推送完成动态页面访问和逻辑执行
4.Extension等动态调用主App的资源
5.App实现更复杂的架构MVVM或者是VIPER架构,提供解除业务相互依赖的能力
6.以组件化为目的的工程改造,隔离各个业务,以制作单独的组件

接口预览

Router

NS_ASSUME_NONNULL_BEGIN
@interface SJRouter : NSObject
+ (instancetype)shared;

- (void)handleRequest:(SJRouteRequest *)request completionHandler:(SJCompletionHandler)completionHandler;
@end
NS_ASSUME_NONNULL_END

RouteRequest

NS_ASSUME_NONNULL_BEGIN
@interface SJRouteRequest : NSObject
- (instancetype)initWithURL:(NSURL *)URL;
- (instancetype)initWithPath:(NSString *)requestPath parameters:(nullable SJParameters)parameters;
@property (nonatomic, strong, readonly) NSString *requestPath;
@property (nonatomic, strong, readonly, nullable) SJParameters prts;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

RouteHandlerProtocol

NS_ASSUME_NONNULL_BEGIN
typedef id SJParameters;

@protocol SJRouteHandler
+ (NSString *)routePath;
+ (void)handleRequestWithParameters:(nullable SJParameters)parameters topViewController:(UIViewController *)topViewController completionHandler:(nullable SJCompletionHandler)completionHandler;
@end
NS_ASSUME_NONNULL_END

流程

简单的讲,app应用中,路由识别一个请求, 将它分派给对应的handler进行处理。 这个流程非常像发送一个网络请求(拼接参数=>发起请求=>回调)。

同样的,当Router收到下面的请求时(请求视频播放页):

- (void)push:(id)sender {
  SJRouteRequest *request = [[SJRouteRequest alloc] initWithPath:@"video/playbackInfo" parameters:@{@"video_id":@(111)}];
  [SJRouter.shared handleRequest:request completionHandler:^(id _Nullable result, NSError * _Nullable error) {
#ifdef DEBUG
    NSLog(@"%d - %s", (int)__LINE__, __func__);
#endif
  }];
}

会尝试识别路由, 找到匹配的handler,传递必要参数:

@implementation SJRouter
- (void)handleRequest:(SJRouteRequest *)request completionHandler:(SJCompletionHandler)completionHandler {
  NSParameterAssert(request); if ( !request ) return;
  Class<SJRouteHandler> handler = _handlersM[request.requestPath];
  if ( handler ) {
    [handler handleRequestWithParameters:request.requestPath topViewController:_sj_get_top_view_controller() completionHandler:completionHandler];
  }
  else {
    printf("\n (-_-) Unhandled request: %s", request.description.UTF8String);
  }
}
@end

最后handler进行处理。

@implementation TestViewController
+ (NSString *)routePath {
  return @"video/playbackInfo";
}

+ (void)handleRequestWithParameters:(nullable SJParameters)parameters topViewController:(UIViewController *)topViewController completionHandler:(nullable SJCompletionHandler)completionHandler {
  TestViewController *vc = [TestViewController new];
  vc.completionHandler = completionHandler;
  [topViewController.navigationController pushViewController:vc animated:YES];
}
@end

至此, 我们再回过头看刚开始举的那个例子:

视频模块的播放页, 有与视频相关的音乐,点击这些音乐,需要跳转到音乐模块的播放页。

此时,可以让视频模块依赖Router, 进行跳转请求。这看起来都是依赖,实则两者差别很大了。

  1. 路由不止能处理跳转音乐模块的请求, 依赖也从多个变成只依赖Router即可。。。
  2. 在删除某个依赖模块时, 需要删除依赖的代码, 很烦的, 对吧。
  3. 吧啦吧啦吧啦吧啦吧啦。。。

所以点击跳转音乐模块,可以替换成如下操作, 发起请求:

  SJRouteRequest *request = [[SJRouteRequest alloc] initWithPath:@"audio/playbackInfo" parameters:@{@"audio_id":@(232)}];
  [SJRouter.shared handleRequest:request completionHandler:^(id _Nullable result, NSError * _Nullable error) {
#ifdef DEBUG
    NSLog(@"%d - %s", (int)__LINE__, __func__);
#endif
  }];

router找到对应的handler, 让其进行处理。

Handler

从开始到现在,可以看出Handler就是最终执行请求的那个家伙。 相信大家都有疑问, 如何成为一个Handler?

很简单,它是自动的(参见Router), 只要某个类遵守了SJRouteHandlerProtocol, 它便成为了一个Handler。再来看一遍协议吧。

NS_ASSUME_NONNULL_BEGIN
typedef id SJParameters;

@protocol SJRouteHandler
+ (NSString *)routePath;
+ (void)handleRequestWithParameters:(nullable SJParameters)parameters topViewController:(UIViewController *)topViewController completionHandler:(nullable SJCompletionHandler)completionHandler;
@end
NS_ASSUME_NONNULL_END
  1. routePath: 即路径, 表示handler能够处理的路径。当发起请求时, Router会通过路径获取到对应的handler, 交给其进行处理。
  2. handleRequestWithParameters。。。: handler进行的处理。

Router

在整个请求过程中,Router做的事情实质上就是在众多Handler中寻找命中注定的那一个。如何寻找呢?为什么遵守了SJRouteHandlerProtocol便自动成为了Handler呢?

这自然要归功于Runtime的强大力量,我们先看如何实现吧。

@implementation SJRouter
- (instancetype)init {
  self = [super init];
  if ( !self ) return nil;
  _handlersM = [NSMutableDictionary new];
  int count = objc_getClassList(NULL, 0);
  Class *classes = (Class *)malloc(sizeof(Class) * count); objc_getClassList(classes, count);
  Protocol *p_handler = @protocol(SJRouteHandler);
  for ( int i = 0 ; i < count ; ++ i ) {
    Class cls = classes[i];
    for ( Class thisCls = cls ; nil != thisCls ; thisCls = class_getSuperclass(thisCls) ) {
      if ( !class_conformsToProtocol(thisCls, p_handler) ) continue;
      if ( ![(id)thisCls respondsToSelector:@selector(routePath)] ) continue;
      if ( ![(id)thisCls respondsToSelector:@selector(handleRequestWithParameters:topViewController:completionHandler:)] ) continue;
      _handlersM[[(id<SJRouteHandler>)thisCls routePath]] = thisCls;
      break;
    }
  }
  if ( classes ) free(classes);
  return self;
}
@end
  1. objc_getClassList: 很明显了, 获取App所有类。
  2. class_conformsToProtocol: 该类是否遵守某个协议。

得益于Runtime的这两个函数,即可获取到众多的Handler。 当发起请求时, 在众多Handler中寻找注定的那一个, 岂不是易如反掌。

Request

App发起的跳转请求,更多到是内部页面之间的跳转, 我们最需要关注的就是它的路径,所以整个路由都是围绕着路径去跳转的, 而像URL中的scheme和host,体现出来的作用倒是不大。至少在我的项目中跳转第三方App(例如分享)都是使用的友盟这类的SDK去处理的。

因此, Request持有每个请求的路径, 以及必要的参数, 之后再无多余操作。

好了, 就到这里了。

下面是项目地址, 有兴趣到话可以与我一起交流哦。。。

https://github.com/changsanjiang/SJRouter

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Vue+axios 实现http拦截及路由拦截实例

    现如今,每个前端对于Vue都不会陌生,Vue框架是如今最流行的前端框架之一,其势头直追react.最近我用vue做了一个项目,下面便是我从中取得的一点收获. 基于现在用vue+webpack搭建项目的文档已经有很多了,我就不再累述了. 技术栈 vue2.0 vue-router axios 拦截器 首先我们要明白设置拦截器的目的是什么,当我们需要统一处理http请求和响应时我们通过设置拦截器处理方便很多. 这个项目我引入了element ui框架,所以我是结合element中loading和me

  • iOS开发之拦截URL转换成本地路由模块URLRewrite详解

    本文主要给大家介绍了关于iOS拦截URL转换成本地路由模块URLRewrite的相关内容,分享出来供各位iOS开发者们参考学习,下面话不多说了,来一起看看详细的介绍: 需求场景 做过电商App的可能都遇到过这样的需求,在商场首页,各种各样动态的跳转,跳转商品详情.秒杀列表.品牌列表.搜索结果.分类结果页面等等等等.同一个位置,可能今天跳这个商品,明天跳转那个商品,运营配的就是一个web端的URL. 拦截webView里面的URL. 需求分析 拦截各种各样的URL,跳转到指定的原生页面. URL的

  • iOS 模块化之JLRoute路由示例

    JLRoutes是一个调用极少代码 , 可以很方便的处理不同URL schemes以及解析它们的参数,并通过回调block来处理URL对应的操作 , 可以用于处理复杂跳转逻辑的三方库. 1.在日常开发中 , push , present 出现在整个程序的各个地方 , 如果你想快速理清一个项目的整体逻辑 , 非常麻烦 . 大多数情况 , 你得找到代码目录 ,根据层级结构分出关系 , 然后找到对应的push位置 , 寻找下一级页面 , 如果本身项目的目录就非常乱 , 那么如果要了解一个项目的整体跳转

  • iOS路由(MGJRouter)的实现

    背景 最开始想做路由,是因为当时app中有大量与H5之间的交互,原生和H5的跳转操作比较多比较频繁,新增一个跳转又涉及到改代码发版本,为了统一iOS.安卓和H5的跳转,引入了路由. 作用 后来发现路由,还可很多作用.Router就像是个调度中心,各个模块通过路由调度其他模块,模块之间不需要相互引用,调度方式更加统一,更加自由,能够实现解耦的作用,同时也为之后的组件化开发提供了基础. 路由选择 目前github优秀的路由设计已经有很多,如JLRoutes,MGJRouter,CTMediator.

  • iOS使用核心的50行代码撸一个路由组件

    使用组件化是为了解耦处理,多个模块之间通过协议进行交互.而负责解析协议,找到目的控制器,或者是返回对象给调用者的这个组件就是路由组件.本文讲解如何使用核心的50行代码实现一个路由组件. 组件化和路由 路由的实现 路由注册实现 路由使用实现 客户端的使用 一些小想法 组件化和路由 之前看过挺多的关于路由管理.路由处理的文章,常常会和组件化出现在一起,一开始不知道为何路由和组件化出现在一起,后来公司的项目中使用了路由组件(他本身也是一个组件,确切的说是一个中间人或者中介者),才突然想明白了,原来如此

  • 如何在iOS上使用MVVM进行路由详解

    前言 我已经在几个项目中使用MVVM了一段时间,我真的很喜欢它的简单性.特别是,如果你像许多人一样从MVC迁移,你只需要在你的架构中增加一层ViewModel.如果您发现太多层级造成的复杂,这确实使事情变得更容易. 这是一个良好的开端,但这种简单并不总是好的.在MVVM中,您将业务逻辑移出视图控制器(VC),然后意识到它仍然很胖.视图模型(VM)现在具有业务逻辑,但是展示数据(格式化)或路由如何?他们仍然被困在VC中,我们需要将它们移出. #示例流程 假设我们正在实现登陆页面,如下所示. ##路

  • iOS撸一个简单路由Router的实现代码

    平常开发中用户点击头像, 进入个人主页,这看似平常的操作, 背后极有可能会牵扯到多个模块. 再如: 视频模块的播放页, 有与视频相关的音乐,点击这些音乐,需要跳转到音乐模块的播放页, 这样视频与音乐模块之间,不可避免的会产生依赖或耦合. 这个问题让人脑壳疼,相信很多朋友都这样做过,写一些代理或通知, 不停的传递事件: 有时干脆直接导入另一个模块. 因为我在公司独立开发, 顾及少一点,可以拿公司项目做实践,在尝试组件化的过程中, 了解到了路由, 对于解决上述问题, 有极大的帮助.因此我想总结并与大

  • 从零开始用electron手撸一个截屏工具的示例代码

    最近在尝试利用 electron 将一个 web 版的聊天工具包装成一个桌面 APP.作为一个聊天工具,截屏可以说是一个必备功能了.不过遗憾的是没有找到很成熟的库来用,也可能是打开方式不对,总之呢没看到现成的,于是就想从头撸一个简单的截图工具.下面就进入正题吧! 思路 electron 提供了截取屏幕的 API,可以轻松的获取每个屏幕(存在外接显示器的情况)和每个窗口的图像信息. 把图片截取出来,然后创建一个全屏的窗口盖住整个屏幕,将截取的图片绘制在窗口上,然后再覆盖一层黑色半透明的元素,看起来

  • 原生js基于canvas实现一个简单的前端截图工具代码实例

    这篇文章主要介绍了原生js基于canvas实现一个简单的前端截图工具代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 先看效果 代码如下 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <style type="text/css"> *{

  • node.js 一个简单的页面输出实现代码

    安装过程就不说了.如果成功是能使用node的命令.node.js调试是非常方便的.每种后台语言都有一个向那个黑黢黢的控制台团输出语用的命令.node.js沿用FF那套东西,也就是console对象与其方法.我们首先建一个example.js文件,内容如下,然后在控制台打开它. 复制代码 代码如下: console.log("hello node.js") for(var i in console){ console.log(i+" "+console[i]) } n

  • 用java的spring实现一个简单的IOC容器示例代码

    要想深入的理解IOC的技术原理,没有什么能比的上我们自己实现它.这次我们一起实现一个简单IOC容器.让大家更容易理解Spring IOC的基本原理. 这里会涉及到一些java反射的知识,如果有不了解的,可以自己去找些资料看看. 注意 在上一篇文章,我说,启动IOC容器时,Spring会将xml文件里面配置的bean扫描并实例化,其实这种说法不太准确,所以我在这里更正一下,xml文件里面配置的非单利模式的bean,会在第一次调用的时候被初始化,而不是启动容器的时候初始化.但是我们这次要做的例子是容

  • js编写一个简单的产品放大效果代码

    购物网站的产品页经常会放有一个产品展示图区.该图区有一个功能就是产品图的放大功能,移动左侧的焦点区域,可以放大细节部分观看,详情如下图.实现该功能的方法也非常简单. 实验:制作产品焦点放大图. 所需技能: 1.基本的获取页面元素的方法: 2.几个简单的事件: 3.会使用dom设置元素的属性: 案例原理: 1.焦点框的跟随鼠标事件: 2.焦点框的移动区域规定: 3.大盒子内容的显示: 适合对象:js初学者 ----------------------------------------------

  • iOS开源一个简单的订餐app UI框架

    前言 学 Swift 也有一段时间了,做了一些小的 demo.一直想做个完整的项目,发现这边学校的外卖订餐也逐渐流行起来,不像中国有那么多强大的外卖软件,美国也有,但不多,起码中国人对那些软件都不太熟知也不怎么用.打算专门针对午餐的外卖做个app,做了几天,只做出个 UI,看上去很小的软件,新手做起来感觉东西还是有点多. Swift 如何与后端交互 之类的之后再慢慢学吧,有数据库之类的我都挺熟悉,SQL 或者 MongoDB. 目录 在这个 app 中,所有 UI 都是用代码创建的,你可以在 1

  • SpringBoot+Websocket实现一个简单的网页聊天功能代码

    最近做了一个SpringBoot的项目,被SpringBoot那简介的配置所迷住.刚好项目中,用到了websocket.于是,我就想着,做一个SpringBoot+websocket简单的网页聊天Demo. 效果展示: 当然,项目很简单,没什么代码,一眼就能明白 导入websocket的包. 通过使用SpringBoot导入包的时候,我们可以发现,很多包都是以 spring-boot-starter 开头的,对于我这种强迫症 ,简直是福音 <dependency> <groupId>

  • 一个简单的js动画效果代码

    实现的功能:移动,暂停,恢复,停止. 未实现的功能:无法实现单位换算,不能实现颜色渐变(还有很多,有待完善...) 代码中的使用方法如下: 复制代码 代码如下: var $m = $M("divAnimate"); _("btnAnimate").onclick = function(){ this.disabled = true; var that = this; $m.animate({left:"300px",top:"100px

  • 一个简单的PHP验证码实现代码

    实现代码: <?php //将验证码保存到session里,供全局使用 session_start(); $nums = ""; for($i=0;$i<4;$i++){  //产生随机数并转换成十六进制  $nums.=dechex(mt_rand(0,15)); } //将验证码写入session $_SESSION['code']=$nums; //设置验证码长和宽 $_width = 60; $_height = 20; //创建一张图片 $_img = image

随机推荐