iOS 浅析路由设计模式

作者: Zhui_Do | 来源:发表于2017-04-06 16:04 被阅读1305次

    自从项目用了路由设计模式,每次测试告诉我有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

    相关文章

      网友评论

      • Blutter:这个不错MDRouter https://github.com/Modool/MDRouter
        支持参数扩展,支持结果输出,支持异步处理,支持容错处理,支持scheme,host,port筛选过滤,支持适配器分组模式,支持目标解决方案
      • 芳华绝世:当你的代码出现很多重复代码,还有很多if-else if - else if 你该考虑重构了。
        Liumouren:@芳华绝世 不用delegate,但是其他的操作如你所说并替换到他的processActionURL:(NSString*)actionURL andWithAction方法里会不会有什么弊端?
        芳华绝世:@Zhui_Do 问题1:每次增加一个业务需求,你就要给actionMap新增一个映射关系,新增一个else if判断逻辑,同时这个类也需要依赖了一个业务具体的VC,扩展很不方便。可以考虑注册机制,把actionMap通过注册的方式添加。
        问题2:随着业务需求越来越多,这个类的会依赖更多,OOP原则有一个原则就是依赖抽象,不要依赖具体。可以考虑通过协议的方式,把解析actionMap这个解析判断逻辑脱离出去,这个最后的目的其实就是拿到目标VC的实例,然后通过NavigationController push。这个协议,可以设计成这样 - (UIViewController *)viewControllerWithAction:(NSString *)action actionUrl:(NSString *)actionUrl; 那么你的代码就可以写成 if ([self.delegate respondsToSelector:@selector(viewControllerWithAction:actionUrl:)]) {
        UIViewController * desVC = [self.delegate viewControllerWithAction:action acyionUrl:actionUrl];
        [[HSTool getCurrentVC].navigationController pushViewController:desVC animated:YES];
        }

        3 写一个基类继承NSObject,遵循步骤2中的协议。每新增一个业务需求,可以新增一个子类去解析actionUrl,action,生成VC实例,有参数就设置好参数,这样既容易扩展,又容易做单元测试。如果action非法或者不存在,生成一个错误的页面返回。
        4 对于生成实例VC,可以考虑注册的时候map里面存的是,home :XXXHomePageVC,拿到类名String,通过反射机制,拿到类名,再用调用alloc init方法,返回实例。 Class vc = NSClassFromString(@"XXXHomePageVC").这样你的模块就不需要再依赖具体业务的VC,达到了解耦的目的。
        Zhui_Do:@024451bf70e9 求赐教啊,有什么想法可以分享一下:joy:
      • b59457960ac9:哎,iOS要失业了!以关注,多可以来个demo什么的就完美了
        Zhui_Do:@Twistar beijing
        b59457960ac9:@Zhui_Do 你在哪个城市呀?我最近就是在找工作。是挺难找的,也想学点东西充实下
        Zhui_Do:是挺难的,本来年后想换个工作,面了几家都没什么消息,只能在学习一下再想着跳了

      本文标题:iOS 浅析路由设计模式

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