• 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;“:”,冒号表示带参数.

     

     

  • 相关阅读:
    研究人员用数据统计的方法来做文学研究
    导致大数据项目失败的4大痛点及应对策略
    导致大数据项目失败的4大痛点及应对策略
    excel怎么制作三维圆环图表
    excel怎么制作三维圆环图表
    ios开发之Swift新手入门
    ZOJ3629 Treasure Hunt IV(找规律,推公式)
    nginx源代码分析--进程间通信机制 &amp; 同步机制
    &lt;LeetCode OJ&gt; 326. Power of Three
    二进制整数的乘除运算
  • 原文地址:https://www.cnblogs.com/StevenHuSir/p/OCRouter.html
Copyright © 2020-2023  润新知