zoukankan      html  css  js  c++  java
  • iOS组件化之路由设计(Router)

    前言

    随着用户的需求越来越多,对App的用户体验也变的要求越来越高。为了更好的应对各种需求:

    App架构:开发人员从软件工程的角度,将App架构由原来简单的MVC变成MVVM,VIPER等复杂架构。更换适合业务的架构,是为了后期能更好的维护项目。

    版本快速迭代:但是用户依旧不满意,继续对开发人员提出了更多更高的要求,不仅需要高质量的用户体验,还要求快速迭代,最好一天出一个新功能,而且用户还要求不更新就能体验到新功能。为了满足用户需求,于是开发人员就用H5,ReactNative,Weex等技术对已有的项目进行改造。

    ③组件化:项目架构也变得更加的复杂,纵向的会进行分层,网络层,UI层,数据持久层。每一层横向的也会根据业务进行组件化。

    尽管这样做了以后会让开发更加有效率,更加好维护,但是如何解耦各层,解耦各个界面和各个组件,降低各个组件之间的耦合度,如何能让整个系统不管多么复杂的情况下都能保持“高内聚,低耦合”的特点?

    1.引子

    大前端React和Vue.路由的作用主要是保证视图和URL的同步。当用户在页面进行操作的时候,应用会在若干个交互状态中切换,路由则可以记录下某些重要的状态,比如用户查看一个网站,用户是否登录、在访问网站的哪一个页面。用户可以通过手动输入或者与页面进行交互改变URL,然后通过同步或者异步的方式向服务端发送请求获取资源,成功后重新绘制UI。

    2.App路由能解决那些问题

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

    2->如何解除App组件之间和App页面之间的耦合性?

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

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

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

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

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

    3.APP跳转实现

    1->URL Scheme方式

    比如说,在iPhoneSafari浏览器上面输入如下的命令,会自动打开一些App

    //打开邮箱:
    mailto://

    关于系统功能跳转的URL汇总列表https://www.jianshu.com/p/32ca4bcda3d1

    2.Universal Links 方式

    iOS 9.0新增加了一项功能是Universal Links,使用这个功能可以使我们的App通过HTTP链接来启动App

    1.如果安装过App,不管在微信里面http链接还是在Safari浏览器,还是其他第三方浏览器,都可以打开App

    2.如果没有安装过App,就会打开网页。

    具体设置

    1.App需要开启Associated Domains服务,并设置Domains,注意必须要applinks:开头。

    2.域名必须要支持HTTPS

    上传内容是Json格式的文件,文件名为apple-app-site-association到自己域名的根目录下,或者.well-known目录下。iOS自动会去读取这个文件。具体的文件内容请查看官方文档

    如果App支持了Universal Links方式,那么可以在其他App里面直接跳转到我们自己的App里面。如下图,点击链接,由于该链接会Matcher到我们设置的链接,所以菜单里面会显示用我们的App打开。

    在浏览器里面也是一样的效果,如果是支持了Universal Links方式,访问相应的URL,会有不同的效果。如下图:

    4.App内组件路由设计

    主要解决:

    各个页面和组件之间的跳转问题

    各个组件之间相互调用

    代码高复用、方便测试

    如何设计一个路由

    Route实现

    主工程与首页模块、分类、登录模块不直接建立关联,而是先通过router(路由)与你要调用的模块建立关系 ,从而实现各个模块的解耦和复用、

    ①.OCTarget_index类(解耦+交互)

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface OCTarget_Index : NSObject
    - (id)action_home:(NSDictionary*)params;
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "OCTarget_Index.h"
    #import <UIKit/UIKit.h>
    @implementation OCTarget_Index
    - (id)action_home:(NSDictionary*)params {
        UIViewController *homeVC = [UIViewController new];
        homeVC.title = @"首页"; 
        return homeVC;
    }
    @end

    2.代码实现:

    HKOCRouter.h

    #import <UIKit/UIKit.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface HKOCRouter : UIView
    
    +(instancetype)shareInstance;
    - (id)openUrl:(NSString *)urlStr;
    //返回值id,外部调用,通过target 和 action 来唯一确认一个类里面的方法
    - (id)performTarget:(NSString*)targetName action:(NSString*)actionName param:(NSDictionary*)params;
    @end
    
    NS_ASSUME_NONNULL_END

    HKOCRouter.m

    #import "HKOCRouter.h"
    
    @implementation HKOCRouter
    +(instancetype)shareInstance {
        static HKOCRouter * mediator;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            mediator = [[HKOCRouter alloc] init];
        });
        return mediator;
    }
    - (id)openUrl:(NSString *)urlStr {
        NSURL *url = [NSURL URLWithString:urlStr];
        NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
        //查询
        NSString *urlString = [url query];
        //切割字符串
        for (NSString *param in [urlString componentsSeparatedByString:@"&"]) {
            NSArray *elts = [param componentsSeparatedByString:@"="];
            if (elts.count<2) continue;
            id firstEle = [elts firstObject];
            id lastEle = [elts lastObject];
            if (firstEle && lastEle) {
                [params setObject:lastEle forKey:firstEle];
            }
        }
        NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""];
        if ([actionName hasPrefix:@"native"]) {
            return @(NO);
        }
        id result = [self performTarget:url.host action:actionName param:params];
        return result;
        
    }
    -(id)performTarget:(NSString *)targetName action:(NSString *)actionName param:(NSDictionary *)params {
        //这个目标的类名字符串
        NSString * targetClassString = [NSString stringWithFormat:@"OCTarget_%@",targetName];
        NSString *actionMethodString = [NSString stringWithFormat:@"action_%@",actionName];
        Class targetClass = NSClassFromString(targetClassString);
        NSObject *target = [[targetClass alloc] init];
        
        SEL action = NSSelectorFromString(actionMethodString);
        //判断
        if ([target respondsToSelector:action]) {
            return [self safePerformAction:action target:target param:params];
        }else {
            SEL action = NSSelectorFromString(@"notFound:");
            if ([target respondsToSelector:action]) {
                return [self safePerformAction:action target:target param:params];
            }else {
                return nil;
            }
        }
    }
    
    //1.通过对象调用指定的方法
    //2.传参
    - (id)safePerformAction:(SEL)action target:(NSObject*)target param:(NSDictionary*)params {
        NSMethodSignature *methodSig = [target methodSignatureForSelector:action];
        if (methodSig == nil) {
            return nil;
        }
        //获取这个方法返回值的地址
        const char *retType = [methodSig methodReturnType];
        
        //id 是可以返回任意对象,所以我们单独处理基本变量。 NSInteger Bool Void
        if (strcmp(retType, @encode(NSInteger)) == 0) {
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
            
            //为什么传2?前面0个1这两个位置已经被target和action给占了
            [invocation setArgument:&params atIndex:2];
            [invocation setTarget:target];
            [invocation setSelector:action];
            [invocation invoke];
            
            NSInteger result = 0;
            [invocation getReturnValue:&result];
            return @(result);
        }
        
        if (strcmp(retType, @encode(BOOL)) == 0) {
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
            //为什么传2?前面0个1这两个位置已经被target和action给占了
            [invocation setArgument:&params atIndex:2];
            [invocation setTarget:target];
            [invocation setSelector:action];
            [invocation invoke];
            
            NSInteger result = 0;
            [invocation getReturnValue:&result];
            return @(result);
        }
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        return [target performSelector:action withObject:target withObject:params];
    #pragma clang diagnostic pop
       
    }
    @end

    使用:

    UIViewController * vc = [[HKOCRouter shareInstance] openUrl:@"http://Index/home:"];

    Index为组件索引;home为actionName;“:”,冒号表示带参数.

     

     

  • 相关阅读:
    【转载】SAP_ECC6.0_EHP4或SAP_ECC6.0_EHP5_基于Windows_Server_2008R2_和SQL_server_2008下的安装
    使用delphi 开发多层应用(二十四)KbmMW 的消息方式和创建WIB节点
    使用delphi 开发多层应用(二十三)KbmMW 的WIB
    实现KbmMw web server 支持https
    KbmMW 服务器架构简介
    Devexpress VCL Build v2014 vol 14.1.1 beta发布
    使用delphi 开发多层应用(二十二)使用kbmMW 的认证管理器
    KbmMW 4.50.00 测试版发布
    Basic4android v3.80 beta 发布
    KbmMW 认证管理器说明(转载)
  • 原文地址:https://www.cnblogs.com/StevenHuSir/p/OCRouter.html
Copyright © 2011-2022 走看看