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

 

 

 

posted @ 2018-11-13 11:08  淡然微笑_Steven  阅读(2586)  评论(0编辑  收藏  举报