iOS 浅析路由设计模式

自从项目用了路由设计模式,每次测试告诉我有bug的时候,大哥都会微微一笑,告诉她这是服务器的bug

1. 什么是路由

在Web开发过程中,经常会遇到『路由』的概念。那么,到底什么是路由?简单来说,路由就是URL到函数的映射。

2. router和route的区别

route 就是一条路由,它将一个URL路径和一个函数进行映射,例如:

/users        ->  getAllUsers()
/users/count  ->  getUsersCount()

这就是两条路由,当访问 /users 的时候,会执行 getAllUsers() 函数;当访问 /users/count 的时候,会执行 getUsersCount() 函数。

而 router 可以理解为一个容器,或者说一种机制,它管理了一组 route 。简单来说, route 只是进行了URL和函数的映射,而在当接收到一个URL之后,去路由映射表中查找相应的函数,这个过程是由 router 来处理的。一句话概括就是 “ The router routes you to a route “。

上面乱七八糟一堆没什么用,只有一句话是有用的:路由就是URL到函数的映射。例如“User/login”,User控制器下的login方法。

首先我们来比较一下我们以前的结构模式以及与 加入路由机制后的项目结构,实现路由机制,不仅需要一个映射文件,还需要一套路由管理机制,那么采用路由机制,我们的项目架构就跟原来不一样了.

没吃脑残片之前.png

我们看一下它有哪些缺点:
(1)都要在当前页面引入要跳转页面的class 类。这就造成了页面的耦合性很高。
(2)遇到重大bug,不能够及时的修复问题,需要等待更新发版后才能解决。
(3)推送消息或者是3D-Touch需求,要求直接跳转到内部第10级界面,那么就需要写一个入口跳转到指定界面。


吃了脑残片之后.png

二、App路由能解决哪些问题

思考如下的问题,平时我们开发中是如何优雅的解决的:

1.3D-Touch功能或者点击推送消息,要求外部跳转到App内部一个很深层次的一个界面。

比如微信的3D-Touch可以直接跳转到“我的二维码”。“我的二维码”界面在我的里面的第三级界面。或者再极端一点,产品需求给了更加变态的需求,要求跳转到App内部第十层的界面,怎么处理?

2.自家的一系列App之间如何相互跳转?

如果自己App有几个,相互之间还想相互跳转,怎么处理?

3.如何解除App组件之间和App页面之间的耦合性?

随着项目越来越复杂,各个组件,各个页面之间的跳转逻辑关联性越来越多,如何能优雅的解除各个组件和页面之间的耦合性?

4.如何能统一iOS和Android两端的页面跳转逻辑?甚至如何能统一三端的请求资源的方式?

项目里面某些模块会混合ReactNative,Weex,H5界面,这些界面还会调用Native的界面,以及Native的组件。那么,如何能统一Web端和Native端请求资源的方式?

5.如果使用了动态下发配置文件来配置App的跳转逻辑,那么如果做到iOS和Android两边只要共用一套配置文件?

6.如果App出现bug了,如何不用JSPatch,就能做到简单的热修复功能?

比如App上线突然遇到了紧急bug,能否把页面动态降级成H5,ReactNative,Weex?或者是直接换成一个本地的错误界面?

7.如何在每个组件间调用和页面跳转时都进行埋点统计?每个跳转的地方都手写代码埋点?利用Runtime AOP ?

8.如何在每个组件间调用的过程中,加入调用的逻辑检查,令牌机制,配合灰度进行风控逻辑?

9.如何在App任何界面都可以调用同一个界面或者同一个组件?只能在AppDelegate里面注册单例来实现?

比如App出现问题了,用户可能在任何界面,如何随时随地的让用户强制登出?或者强制都跳转到同一个本地的error界面?或者跳转到相应的H5,ReactNative,Weex界面?如何让用户在任何界面,随时随地的弹出一个View ?

以上这些问题其实都可以通过在App端设计一个路由来解决。那么我们怎么设计一个路由呢?

+(void) processActionURL:(NSString*)actionURL andWithAction:(NSString*)action
{
       NSDictionary * ACTION_MAP = @{
      /*浏览器*/    @"browser":@"HSWebViewController",
      /*资讯详情*/  @"news_detail":@"HSNewsDetailController",
      /*帖子详情*/  @"topic_detail":@"HSTopicDatailTableViewController",
      /*章节列表*/  @"material_detail":@"HSMaterialDetailTableViewController",
      /*资料管理*/  @"material_manage":@"MaterialManage",
      /*题目展示*/  @"question_detail":@"HSMaterialQuestionController",
      /*文章详情*/  @"article_detail":@"",
      /*问卷调查*/  @"survey_detail":@"",
      /*套餐详情*/  @"meal_detail":@"",
      /*课程详情*/  @"course_detail":@"",
      /*购买页面*/  @"purchase":@"HSPayViewController",
      /*列表页面*/  @"search_list":@"HSComonSearchTBV",
      /*用户帮助页面*/ @"guide":@"HSHelpCenterViewController",
      /*二维码扫描*/ @"qrcode":@"HSScanViewController",
      /*领取课程*/  @"playcode":@"HSPlayCodeViewController",
      /*我的课程*/  @"my_course":@"",
      /*个人设置*/  @"profile_setting":@"HSPensonalSettingController",
      /*系统设置*/  @"system_setting":@"HSSettingController",
      /*用户信息*/  @"user_info":@"HSUserInfoVC"
                 };
    NSDictionary * dict = [HSNetUrlProcess queryDictWithUrlString:actionURL];
    if ([action isEqualToString:@"browser"]) { // 0 跳转到浏览器
        HSWebViewController * webView = [[HSWebViewController alloc]init];
        webView.urlStr = dict[HSOBJ_URL];
        webView.hidesBottomBarWhenPushed = YES;
        [[HSTool getCurrentVC].navigationController pushViewController:webView animated:YES];
    }
    else if([action isEqualToString:@"news_detail"])//1
    {
        HSNewsDetailController *newsVC=[[HSNewsDetailController alloc]init];
        newsVC.param=dict;
        [[HSTool getCurrentVC].navigationController pushViewController:newsVC animated:YES];
    }
    else if([action isEqualToString:@"topic_detail"])//2
    {
        HSTopicDatailTableViewController * topicDetail = [[HSTopicDatailTableViewController alloc]init];
        topicDetail.param = dict;
        [[HSTool getCurrentVC].navigationController pushViewController:topicDetail animated:YES];
    }
    else if([action isEqualToString:@"material_detail"])//3
    {
        HSMaterialDetailTableViewController *materialDeialVC=[[HSMaterialDetailTableViewController alloc]init];
        materialDeialVC.param=dict;
        [[HSTool getCurrentVC].navigationController pushViewController:materialDeialVC animated:YES];
    }
    else if([action isEqualToString:@"material_manage"])//4
    {
        MaterialManage * manage = [[MaterialManage alloc]init];
        manage.param = dict;
        [[HSTool getCurrentVC].navigationController pushViewController:manage animated:YES];
    }
    else if([action isEqualToString:@"system_setting"])//5
    {
        HVsystemSetingTableController * setting = [[HVsystemSetingTableController alloc]init];
        [[HSTool getCurrentVC].navigationController pushViewController:setting animated:YES];
    }
    else if([action isEqualToString:@"profile_setting"])//6
    {
        HSPensonalSettingController * personSet = [[HSPensonalSettingController alloc]init];
        [[HSTool getCurrentVC].navigationController pushViewController:personSet animated:YES];
    }
    else if([action isEqualToString:@"user_info"])//7
    {
        HSUserInfoVC * personSet = [[HSUserInfoVC alloc]init];
        personSet.param = dict;
        [[HSTool getCurrentVC].navigationController pushViewController:personSet animated:YES];
    }
    else if([action isEqualToString:@"guide"])//8
    {
        HSHelpCenterViewController * help = [[HSHelpCenterViewController alloc]init];
        [[HSTool getCurrentVC].navigationController pushViewController:help animated:YES];
    }
    else if([action isEqualToString:@"qrcode"])//9
    {
        if (![FLLoginDataModel currentUser].isLogin) {
            HSLoginController * log = [[HSLoginController alloc]init];
            log.hidesBottomBarWhenPushed = YES;
            [[HSTool getCurrentVC].navigationController pushViewController:log animated:YES];
            return;
        }
        NSString *mediaType = AVMediaTypeVideo;
        
        AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
        if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied){
            
            UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"请在设置中打开APP相机权限" message:nil preferredStyle:UIAlertControllerStyleAlert];
            UIAlertAction *action  =[UIAlertAction actionWithTitle:@"取消" style:(UIAlertActionStyleCancel) handler:nil];
            [alert addAction:action];
            [[HSTool getCurrentVC] presentViewController:alert animated:YES completion:nil];
            return;
        }
        
        HSScanViewController * scan = [[HSScanViewController alloc]init];
        HSNavigationViewController *nav =[[HSNavigationViewController alloc]initWithRootViewController:scan];
        [scan.navigationController.navigationBar setHidden:YES];
        [[HSTool getCurrentVC] presentViewController:nav animated:YES completion:nil];
    }
    else if([action isEqualToString:@"search_list"])//10
    {
        HSComonSearchTBV * searchtbv = [[HSComonSearchTBV alloc]init];
        searchtbv.param =  dict;
        if (title.length>0) {
            searchtbv.title = title;
        }
        [[HSTool getCurrentVC].navigationController pushViewController:searchtbv animated:YES];
    }
    else if([action isEqualToString:@"playcode"])//11
    {
        HSPlayCodeViewController * playcode = [[HSPlayCodeViewController alloc]init];
        [[HSTool getCurrentVC].navigationController pushViewController:playcode animated:YES];
    }
    else if([action isEqualToString:@"question_detail"])//12
    {
        HSMaterialQuestionController * questionVC = [[HSMaterialQuestionController alloc]init];
       
        questionVC.param = dict;
        [[HSTool getCurrentVC].navigationController pushViewController:questionVC animated:YES];
    }
    else if([action isEqualToString:@"purchase"]) //购买界面 13
    {
        HSPayViewController * pay = [[HSPayViewController alloc]init];
        pay.param = dict;
        [[HSTool getCurrentVC].navigationController pushViewController:pay animated:YES];
    }
    else if([action isEqualToString:@"article_detail"])//14
    {
        HSNewsContentVC * article = [[HSNewsContentVC alloc]init];
        article.paramDict = dict;
        article.isArticleDetail = YES;
        if (title.length>0) {
            article.title = title;
        }
        [[HSTool getCurrentVC].navigationController pushViewController:article animated:YES];

    }
    else if([action isEqualToString:@"survey_detail"])//15
    {
        
    }
    else if([action isEqualToString:@"meal_detail"]||[action isEqualToString:@"pack_detail"])//16
    {
        HSMealDetailViewController *detailVC=[[HSMealDetailViewController alloc]init];
        detailVC.param=dict;
      
        [[HSTool getCurrentVC].navigationController pushViewController:detailVC animated:YES];
    }
    else if([action isEqualToString:@"course_detail"])//17
    {

        HSCourseDetailViewController *detailVC=[[HSCourseDetailViewController alloc]init];
        detailVC.param=dict;
        if ([[HSTool getCurrentVC] isKindOfClass:[HSCourseDetailViewController class]]||[[HSTool getCurrentVC] isKindOfClass:[HSVideoDetailViewController class]]) {
            detailVC.isPushToSelfVC = YES;
        }
        [[HSTool getCurrentVC].navigationController pushViewController:detailVC animated:YES];
        
    }
    else if([action isEqualToString:@"my_course"])//18
    {
        HSMyCourseViewController *mycourse = [[HSMyCourseViewController alloc]init];
        [[HSTool getCurrentVC].navigationController pushViewController:mycourse animated:YES];
    }
    else if([action isEqualToString:@"teacher_detail"])
    {
        HVTeacherDetailVC * teacherDetail = [[HVTeacherDetailVC alloc]init];
        teacherDetail.param = dict;
        [[HSTool getCurrentVC].navigationController pushViewController:teacherDetail animated:YES];

    }
    else if([action isEqualToString:@"suggest"])
    {
        FeedBackVC * feedBack = [[FeedBackVC alloc]init];
        feedBack.param = dict;
        [[HSTool getCurrentVC].navigationController pushViewController:feedBack animated:YES];
    }
    else if([action isEqualToString:@"video_detail"])
    {
        HSVideoDetailViewController *videoDetail= [[HSVideoDetailViewController alloc]init];
        videoDetail.param  = dict;
        [[HSTool getCurrentVC].navigationController  pushViewController:videoDetail animated:YES];
    }
    else if([action isEqualToString:@"answer_detail"])
    {//"answer/view?answer_id=d8c83aa9e8707475c545a8966ae052ea&token=357487b027fda52852792171a18ac2f4"
        HSAnalysisViewController *analysisVC = [[HSAnalysisViewController alloc]init];
        analysisVC.param = dict;
        [[HSTool getCurrentVC].navigationController  pushViewController:analysisVC animated:YES];
    }
    else if([action isEqualToString:@"qq"])
    {
        [HSTool jumpToQQWithQQnumber:dict[HSOBJ_URL]];
    }
    else if([action isEqualToString:@"phone"])
    {
        [HSTool jumpToPhoneWithPhonenumber:dict[HSOBJ_URL]];
    }
    else if([action isEqualToString:@"webview"])
    {
        HSWebViewController * webView = [[HSWebViewController alloc]init];
        webView.urlStr = dict[HSOBJ_URL];
       
        webView.hidesBottomBarWhenPushed = YES;
         webView.shouldHideNavBar = YES;
        [[HSTool getCurrentVC].navigationController pushViewController:webView animated:YES];
    }
    else if([action isEqualToString:@"login"])
    {
        HSLoginController * login = [[HSLoginController alloc]init];
        [[HSTool getCurrentVC].navigationController pushViewController:login animated:YES];
    }
    else if([action isEqualToString:@"order_detail"])
    {
        OrderDetailController * detail = [[OrderDetailController alloc]init];
        detail.param = dict;
        [[HSTool getCurrentVC].navigationController pushViewController:detail animated:YES];
    }
    else if([action isEqualToString:@"web_login"])
    {

        SwipLoginVC * swip = [[SwipLoginVC alloc]init];
        swip.param = dict;
        [[HSTool getCurrentVC].navigationController pushViewController:swip animated:YES];
    }
    else if([action isEqualToString:@"aboutus"])
    {
         [[HSTool getCurrentVC].navigationController pushViewController:[[HvaboutVC alloc]init] animated:YES];
    }
    else if([action isEqualToString:@"message_detail"]){
        
        HSMessageDetailViewController * detailVC = [[HSMessageDetailViewController alloc]init];
        detailVC.param = dict;
        [[HSTool getCurrentVC].navigationController pushViewController:detailVC animated:YES];
    }
    else if([action isEqualToString:@"score"]){
        IntegralDetailVC *detailVC = [[IntegralDetailVC alloc]init];
        [[HSTool getCurrentVC].navigationController pushViewController:detailVC animated:YES];
    }
    else if([action isEqualToString:@"close"]){
        [[HSTool getCurrentVC].navigationController popViewControllerAnimated:YES];
    }
    else if([action isEqualToString:@"goods_detail"]){
        HSGoodsDetailViewController * detailVC = [[HSGoodsDetailViewController alloc]init];
        detailVC.param = dict;
        [[HSTool getCurrentVC].navigationController pushViewController:detailVC animated:YES];
    }
    else if([action isEqualToString:@"self"]){
        [AFNetworkTool HVDataCache:dict NetBlock:^(NSDictionary *json) {
            
        } ErrorCode:^(int errorCode) {
            
        } Fail:^{
            
        } Setting:nil];
    }
    else if([action isEqualToString:@"update_store"])
    {
        
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:actionURL]];
        //   [HSCoverView showMessage:@"未指定跳转内容" finishBlock:nil];
    }

无论是路由还是中间人或者是组件化,其实感觉思想上都差不多,现在举个例子还说明一下上面的代码:例如界面上有一个按钮,服务器就会给你一个action和action_url ,action表示你要跳转的页面,url表示下一个页面网络请求的地址,项目中几乎所有的点击事件都由服务器控制,服务器也会传给你相应的action和url。
这样的封装,也为与H5交互提供了便捷,

 [_bridge registerHandler:@"WebViewAction" handler:^(id data, WVJBResponseCallback responseCallback) {

 NSDictionary *dic  = [NSJSONSerialization JSONObjectWithData:[data dataUsingEncoding:(NSUTF8StringEncoding)] options:NSJSONReadingAllowFragments error:nil];
           
 [HSTool processActionURL:[dic stirngWithSaftKey:@"action_url"] andWithAction:[dic stirngWithSaftKey:@"action"]];        
 }];

还记得以前每个H5页面都要单独一个controller,然后跟着后台小哥一顿对接口,想一堆的标识符,现在只需要一个webVC,只需要两行代码就能实现,而且支持所有原生支持的事件,感觉写的这么方便我好想要失业了。。。。。。

如法炮制的在UI显示上我们同样写好了二十多种type的UI样式,每种样式对应一个cell,一个controller可以显示二十几种样式,完全可以通过后端来配置一个自己想要的页面,从二十几种中选出你要展示的样式,你可以用type1也可以用type2这个controller有很高的复用率。

曾经想过既然所有的事件跳转都交给Tool去处理,那可不可以同样实现一个view制作这样的一个工具类,其实也可以,不过高度和布局实现比较麻烦。

但是需求来了也没办法,后来我直接把支持type解析的controller,用child controller,addsubview的方式直接把这个controller当成了一个万能的view,但是由于controller是个tableview,而你却非要把它当成一个view,所以高度是一个很麻烦的问题,后来用KVO解决了,感觉很尴尬,虽然能满足需求,但是总感觉哪里怪怪的。

总结:

相对来说这样的设计还是很方便的,低耦合,而且支持小小的热更新,样式不定,事件不定,都由服务器端来控制,用起来很方便,最重要的是,我发现我几乎没什么bug,有bug基本上全是后台大兄弟的,哈哈

相关:与路由设计模式配套的视图处理方式

相关:

http://www.cnblogs.com/oc-bowen/p/6489070.html
https://casatwy.com/iOS-Modulization.html
http://www.cocoachina.com/ios/20170315/18893.html
http://blog.csdn.net/tianyitianyi1/article/details/60070763

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容