Wrappres' Studio.

组件化之 URL Route

字数统计: 1.3k阅读时长: 5 min
2020/10/05 Share

组件化之 URL Route

iOS 中主要有三种组件化方式,URL Route、target-action、protocol 匹配。这里主要讲第一种:URL Route。
这类方式大致的思想是通过注册,根据不同 url 结构存入不同的 block。等待调用时,通过存储的 block 中返回对象或执行操作。没有利用 Runtime,相对来说实现比较简单。

优缺点

优点

实现简单。

缺点

  • 依赖于命名规定,无法在编译时发现问题
  • 通过硬编码的字符串来做解耦,某种意义上来说也是一种伪解耦,并没有做到真正的解耦。

代码实例

1
2
3
4
5
// 注册某个URL
[URLRouter registerURL:@"app://editor" handler:^(NSDictionary *userInfo) {
UIViewController *editorViewController = [[EditorViewController alloc] initWithParam:userInfo];
return editorViewController;
}];
1
2
3
4
// 调用路由
[URLRouter openURL:@"app://editor/?debug=true" completion:^(NSDictionary *info) {

}];

MGJRouter 的实现

MGJRouter 是一个全局的单利对象,拥有以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/**
* 注册 URLPattern 对应的 Handler,在 handler 中可以初始化 VC,然后对 VC 做各种操作
*
* @param URLPattern 带上 scheme,如 mgj://beauty/:id
* @param handler 该 block 会传一个字典,包含了注册的 URL 中对应的变量。
* 假如注册的 URL 为 mgj://beauty/:id 那么,就会传一个 @{@"id": 4} 这样的字典过来
*/
+ (void)registerURLPattern:(NSString *)URLPattern toHandler:(MGJRouterHandler)handler;

/**
* 注册 URLPattern 对应的 ObjectHandler,需要返回一个 object 给调用方
*
* @param URLPattern 带上 scheme,如 mgj://beauty/:id
* @param handler 该 block 会传一个字典,包含了注册的 URL 中对应的变量。
* 假如注册的 URL 为 mgj://beauty/:id 那么,就会传一个 @{@"id": 4} 这样的字典过来
* 自带的 key 为 @"url" 和 @"completion" (如果有的话)
*/
+ (void)registerURLPattern:(NSString *)URLPattern toObjectHandler:(MGJRouterObjectHandler)handler;

/**
* 取消注册某个 URL Pattern
*
* @param URLPattern URLPattern
*/
+ (void)deregisterURLPattern:(NSString *)URLPattern;

/**
* 打开此 URL
* 会在已注册的 URL -> Handler 中寻找,如果找到,则执行 Handler
*
* @param URL 带 Scheme,如 mgj://beauty/3
*/
+ (void)openURL:(NSString *)URL;

/**
* 打开此 URL,同时当操作完成时,执行额外的代码
*
* @param URL 带 Scheme 的 URL,如 mgj://beauty/4
* @param completion URL 处理完成后的 callback,完成的判定跟具体的业务相关
*/
+ (void)openURL:(NSString *)URL completion:(void (^)(id result))completion;

/**
* 打开此 URL,带上附加信息,同时当操作完成时,执行额外的代码
*
* @param URL 带 Scheme 的 URL,如 mgj://beauty/4
* @param userInfo 附加参数
* @param completion URL 处理完成后的 callback,完成的判定跟具体的业务相关
*/
+ (void)openURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo completion:(void (^)(id result))completion;

/**
* 查找谁对某个 URL 感兴趣,如果有的话,返回一个 object
*
* @param URL 带 Scheme,如 mgj://beauty/3
*/
+ (id)objectForURL:(NSString *)URL;

/**
* 查找谁对某个 URL 感兴趣,如果有的话,返回一个 object
*
* @param URL 带 Scheme,如 mgj://beauty/3
* @param userInfo 附加参数
*/
+ (id)objectForURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo;

/**
* 是否可以打开URL
*
* @param URL 带 Scheme,如 mgj://beauty/3
*
* @return 返回BOOL值
*/
+ (BOOL)canOpenURL:(NSString *)URL;
+ (BOOL)canOpenURL:(NSString *)URL matchExactly:(BOOL)exactly;

/**
* 调用此方法来拼接 urlpattern 和 parameters
*
* #define MGJ_ROUTE_BEAUTY @"beauty/:id"
* [MGJRouter generateURLWithPattern:MGJ_ROUTE_BEAUTY, @[@13]];
*
*
* @param pattern url pattern 比如 @"beauty/:id"
* @param parameters 一个数组,数量要跟 pattern 里的变量一致
*
* @return 返回生成的URL String
*/
+ (NSString *)generateURLWithPattern:(NSString *)pattern parameters:(NSArray *)parameters;

注册(Register)

注册的方法主要有两个+ (void)registerURLPattern:(NSString *)URLPattern toHandler:(MGJRouterHandler)handler;+ (void)registerURLPattern:(NSString *)URLPattern toObjectHandler:(MGJRouterObjectHandler)handler;
主要的区别在于 block 中是否返回对象。
底层实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (void)addURLPattern:(NSString *)URLPattern andHandler:(MGJRouterHandler)handler
{
NSMutableDictionary *subRoutes = [self addURLPattern:URLPattern];
if (handler && subRoutes) {
subRoutes[@"_"] = [handler copy];
}
}

- (void)addURLPattern:(NSString *)URLPattern andObjectHandler:(MGJRouterObjectHandler)handler
{
NSMutableDictionary *subRoutes = [self addURLPattern:URLPattern];
if (handler && subRoutes) {
subRoutes[@"_"] = [handler copy];
}
}

- (NSMutableDictionary *)addURLPattern:(NSString *)URLPattern
{
NSArray *pathComponents = [self pathComponentsFromURL:URLPattern];

NSMutableDictionary* subRoutes = self.routes;

for (NSString* pathComponent in pathComponents) {
if (![subRoutes objectForKey:pathComponent]) {
subRoutes[pathComponent] = [[NSMutableDictionary alloc] init];
}
subRoutes = subRoutes[pathComponent];
}
return subRoutes;
}

通过pathComponentsFromURL方法,将 URL 进行拆分。例如将mgj://foo/bar=>mgj, foo, bar。并通过循环将 URL 进行分层。
此时routes的内容为:

1
2
3
4
5
6
7
8
{
mgj = {
foo = {
bar = {
};
};
};
}

并返回最内层的字典的指针,并在外部将 block 进行赋值。赋值后的内容为:

1
2
3
4
5
6
7
8
9
{
mgj = {
foo = {
bar = {
"_" = "<__NSMallocBlock__: 0x600002b88b10>";
};
};
};
}

至此成功将 Block 对象存入单利对象中的字典内,注册的流程到此结束。

执行(open)

open 的方法以下三个:

  • \+ (**void**)openURL:(NSString *)URL;
  • \+ (**void**)openURL:(NSString *)URL completion:(**void** (^)(**id** result))completion;
  • \+ (**void**)openURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo completion:(**void** (^)(**id** result))completion;

三者的区别主要是是否带参数,是否有完成的回调。

内部实现都为同一个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
+ (void)openURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo completion:(void (^)(id result))completion
{
URL = [URL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSMutableDictionary *parameters = [[self sharedInstance] extractParametersFromURL:URL matchExactly:NO];

[parameters enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) {
if ([obj isKindOfClass:[NSString class]]) {
parameters[key] = [obj stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}
}];

if (parameters) {
MGJRouterHandler handler = parameters[@"block"];
if (completion) {
parameters[MGJRouterParameterCompletion] = completion;
}
if (userInfo) {
parameters[MGJRouterParameterUserInfo] = userInfo;
}
if (handler) {
[parameters removeObjectForKey:@"block"];
handler(parameters);
}
}
}

URL:mgj://foo/bar进入方法,利用- (NSMutableDictionary *)extractParametersFromURL:(NSString *)url matchExactly:(**BOOL**)exactly方法根据URLroutes中取出对应的block
此时parameters中的内容为:

1
2
3
4
{
MGJRouterParameterURL = "mgj://foo/bar";
block = "<__NSMallocBlock__: 0x600002b88b10>";
}

其中stringByAddingPercentEscapesUsingEncodingstringByReplacingPercentEscapesUsingEncoding两个方法主要用于解决编码问题。
最后将completionuserInfo都存入parameters中一并当作参数执行对应的block
到此根据 URL 执行的流程结束。

CATALOG
  1. 1. 组件化之 URL Route
    1. 1.1. 优缺点
      1. 1.1.1. 优点
      2. 1.1.2. 缺点
    2. 1.2. 代码实例
    3. 1.3. MGJRouter 的实现
      1. 1.3.1. 注册(Register)
      2. 1.3.2. 执行(open)