组件化-URL路由方案

一、组件化

1.目的

为了解耦:把复杂系统拆分成多个组件,分离组件边界和责任,便于独立升级和维护。

2.好处

  • 明确组件职责

按合理的粒度将系统拆分成多个组件,确保组件各司其职,以便于复用。

  • 提升开发/编译/测试效率

开发人员只须关注自己的组件,不再需要导入或编译其他组件,从而提升开发和编译速度。

  • 减少组件间的依赖

通信层实现了组件间的通信,而不用在组件间跳转、调用服务时引用其他组件的文件。

3.方案

目前 iOS 组件化的实践中,比较流行的解决方案可归纳为以下三种:

1.基于URL+Block的路由方案;

2.基于反射原理的方案;

3.面向协议的注册服务方案;

“没有最优的方案,只有更适合自己的方案”。

三种方案各有优缺点。你可只选其一,也可将它们组合起来使用。前提是要理解所选方案的特点,结合项目的实际需求进行取舍。

接下来的文章中,将陆续对这三种方案进行分析和梳理~

二、URL路由方案

1.场景

主页跳转到详情,传入详情页所需的头像;

在详情中简单的修改信息之后,返回主页时更新主页的标题。

2.思路

这是一个简单的组件间页面跳转服务,在组件化之前,我们最常见的处理可能是:

  • 在主页中引用详情的.h头文件;
  • 创建详情的实例
  • 通过详情提供的接口传入图片;
  • 将主页设置为详情的代理;
  • push到详情页。

这种处理的问题在于:主页中依赖了详情的头文件,且直接持有了详情的实例;详情的delegate反过来持有了主页的实例,这样两个模块间就产生了依赖和耦合。这还只是主页到详情的一种情况,如果有其他模块同样需要跳转详情,则这些模块同样也会与详情之间产生耦合。

组件化的实践中,按照合理的粒度将系统划分为多个组件之后,主要的挑战就变成了如何实现各组件之间的通信,同时减少组件间的耦合。

针对这一问题,URL+Block路由方案的实现思路和原理如下:

组件化-URL-BLOCK

2.1.服务Block

将创建详情实例的服务封装成一个block:

1
2
3
4
// DetailViewController.m
RouteHandler handler = ^ id (NSDictionary *parameters){
return [[DetailViewController alloc] init];
};
2.2.服务URL

定义URL,用它表示详情组件中的某项服务:

1
2
3
4
5
// Defines.h
// 简单的创建详情实例
static NSString *const kRouteDetaiCreateIns = @"//detail/create";
// 创建详情实例并设置图片
static NSString *const kRouteDetaiPushWithIcon = @"//detail/push";
2.3.映射URL与Block

在单例类Router中将表示某项服务的URL与真正封装了服务的Block映射到字典中:

1
[Router bindURL:kRouteDetaiPushWithIcon toHandler:handler];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Router.m
// MARK: 绑定URL-Block
+(void)bindURL:(NSString *)urlStr toHandler:(RouteHandler)handler{
[self.routes setValue:handler forKey:urlStr];
}

+ (NSMutableDictionary*)routes {
@synchronized (self) {
static NSMutableDictionary *_routes = nil;
if (!_routes) {
_routes = [NSMutableDictionary dictionary];
}
return _routes;
}
}
2.4.调用服务

在主页中通过Router调用详情的服务并获取返回值:

1
2
3
4
UIViewController *vc = [Router handleURL:url complexParams:params completion:^(id result) {
self.title = @"Updated";
}];
[self.navigationController pushViewController:vc animated:YES];
2.5.服务路由查询

+handleURL接口内部会根据之前保存的URL-Block映射关系,以url为key去查询对应的Block并执行它:

1
2
3
+ (nullable RouteHandler)handlerForURL:(nonnull NSString *)urlStr {
return [self.routes valueForKey:urlStr];
}

3.示例

3.1.映射URL与服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Defines.h

#ifndef UrlsHeader_h
#define UrlsHeader_h

#import <UIKit/UIKit.h>

// 详情页对外暴露的回调
typedef void(^DetailCallback)(void);

// 简单的创建详情实例
static NSString *const kRouteDetaiCreateIns = @"//detail/create";
// 创建详情实例并设置图片
static NSString *const kRouteDetaiPushWithIcon = @"//detail/push";
// 跳转主页
static NSString *const kRouteHome = @"//home";

#endif /* UrlsHeader_h */

Defines.h头文件用于定义URL,也包括其他必要参数。它是一个通用文件,不属于某个组件,可在.pch文件中导入此头文件。

在这里你可以定义一个URL字符串@"//detail/push,其中第一个字符串”detail”表示详情组件,第二个字符串”push“表示跳转功能。后面我们就使用此URL来实现从主页到详情的跳转。

3.2.定义Router
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// Router.h
// 表示服务的block,接受字典类型的参数集合
typedef _Nullable id (^RouteHandler)( NSDictionary * _Nullable parameters);
// 回调block
typedef void (^RouteCompletion)(_Nullable id result);

@interface Router : NSObject

/// 绑定URL与Block
/// @param urlStr 对外暴露的代表某组件服务的URL
/// @param handler 服务接口,以block形式暴露给外部调用者
+ (void)bindURL:(nonnull NSString *)urlStr
toHandler:(nonnull RouteHandler)handler;

/// 调用服务接口
/// @param urlStr 代表目标组件服务接口的URL
/// @param complexParams URL中不方便传递的参数
/// @param completion 回调
+ (nullable id)handleURL:(nonnull NSString *)urlStr
complexParams:(nullable NSDictionary*)complexParams
completion:(nullable RouteCompletion)completion;

/// 该URL是否有与之对应的Block
/// @param urlStr URL字符串
+ (BOOL)canHandleUrl:(NSString*)urlStr;

@end

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// Router.m
#import "Router.h"

static Router *mInstance = nil;

@implementation Router

+ (instancetype)sharedInstance{
return [[self alloc] init];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mInstance = [super allocWithZone:zone];
});
return mInstance;
}

// MARK: Getter&Setter

+ (NSMutableDictionary*)routes {
@synchronized (self) {
static NSMutableDictionary *_routes = nil;
if (!_routes) {
_routes = [NSMutableDictionary dictionary];
}
return _routes;
}
}

// MARK: 绑定URL-Block
+(void)bindURL:(NSString *)urlStr toHandler:(RouteHandler)handler{
[self.routes setValue:handler forKey:urlStr];
}

// MARK: 根据URL调用服务
+ (nullable id)handleURL:(nonnull NSString *)urlStr
complexParams:(nullable NSDictionary*)complexParams
completion:(nullable RouteCompletion)completion {

// 重新获取参数之前的URL.host和URL.path,如“//detail/push”
NSString *URLKey = [self getKeyFromURL:urlStr];

// 查找url对应的block
RouteHandler handler = [self handlerForURL:URLKey];

//拼接参数
NSDictionary *paramsInURL = [self.class parametersInURL:urlStr];
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithDictionary:complexParams];
[params addEntriesFromDictionary:paramsInURL];

id obj = handler(params);

return obj;
}

// MARK: 获取URL中?号之后的参数
+ (nullable NSDictionary*)parametersInURL:(nonnull NSString*)urlStr {
NSURL *URL = [NSURL URLWithString:urlStr];
NSMutableDictionary *params = nil;
NSString *query = URL.query;
if(query.length > 0) {
params = [NSMutableDictionary dictionary];
NSArray *list = [query componentsSeparatedByString:@"&"];
for (NSString *param in list) {
NSArray *elts = [param componentsSeparatedByString:@"="];
if([elts count] < 2) continue;
NSString *decodedStr = [[elts lastObject] stringByRemovingPercentEncoding];
[params setObject:decodedStr forKey:[elts firstObject]];
}
}
return params;
}

// MARK:根据URL获取对应的Block
+ (nullable RouteHandler)handlerForURL:(nonnull NSString *)urlStr {
return [self.routes valueForKey:urlStr];
}

// MARK:URL是否有与之对应的Block
+ (BOOL)canHandleUrl:(NSString*)urlStr{
return [self getKeyFromURL:urlStr].length != 0;
}

// 重新获取参数之前的URL.host和URL.path,如“//detail/push”
+ (NSString*)getKeyFromURL:(NSString*)urlStr{
NSURL *url = [NSURL URLWithString:urlStr];
NSString *host = url.host;
NSString *path = url.path;
NSString *URLKey = [NSString stringWithFormat:@"//%@%@",host,path];

return URLKey;
}
@end

这是URL路由方案中的核心类。其主要作用有:

  • 提供全局静态字典,以便保存URL与Block的映射关系;
  • 提供接口以便调用方通过约定好的URL调用目标组件的服务;

一些需要注意的细节:

1、+bindURL接口中的urlStr参数是代表目标组件服务接口的纯URL,如"//detail/push",不带任何其他参数,在全局字典中保存映射关系时,key=URL,value=block。

2、+handleURL接口中的urlStr参数则是完整的URL,可包含调用方向目标组件传递的参数,如”//detail/push?a=1&b=1”;

3、在+handleURL方法内根据URL查询Block时,key为从urlStr中拆分出的url.host+url.path部分,即“//detail/push”

4、本示例中Router.h只提供了最简单和最主要的两个接口,实际使用中可根据需求自己扩展其他功能,这里不做过多的延伸,因为后面会讲到有赞开源的库Bifrost,那里进行了完善的封装。

3.3.注册服务
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
// DetailViewController.h
@interface DetailViewController : UIViewController
@property (nonatomic, strong) UIImage *img;
@property (nonatomic, copy) DetailCallback callBack; // 回调block
@end

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// DetailViewController.m
#import "Router.h"
@interface DetailViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *mIconView;
@end

@implementation DetailViewController

+ (void)load{
// 以block的形式封装对外服务
RouteHandler handler = ^ id (NSDictionary *parameters){
UIImage *img = parameters[@"img"];
DetailCallback callback = parameters[@"block"];
DetailViewController *vc = [[UIStoryboard storyboardWithName:@"Main" bundle:nil]
instantiateViewControllerWithIdentifier:@"DetailViewController"];
vc.img = img;
vc.callBack = callback;
return vc;
};
// 将URL与Block的关系注册到路由表中
[Router bindURL:kRouteDetaiPushWithIcon toHandler:handler];

// 可注册多个Block,以便对外提供更多服务
RouteHandler handler2 = ^ (NSDictionary *params){
return [[DetailViewController alloc] init];
};
[Router bindURL:kRouteDetaiCreateIns toHandler:handler2];
}

- (void)viewDidLoad {
[super viewDidLoad];
_mIconView.image = _img;
}

- (IBAction)onDismissAction:(id)sender {
self.callBack(); //回调给服务调用方
[self.navigationController popViewControllerAnimated:YES];
}

-(void)dealloc{
NSLog(@"+++DetailViewController dealloced~~");
}
@end

这一步是在被调用的组件中进行的,即DetailViewController。注册的操作是在+load()方法中进行的,这样就能保证在应用启动阶段将表示当前组件服务接口的URL和block自动注册到Router中,以便后面随时调用。

+load()方法中可以注册多个对外提供服务的block,就相当于对外提供了多个接口,区分好对应的URL即可。

3.4.本地调用服务

所谓本地调用,就是我们的应用内部组件之间的服务调用,比如主页调用详情的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// HomeViewController.m
#import "Router.h"

@implementation HomeViewController

- (void)viewDidLoad {
[super viewDidLoad];
}

- (IBAction)onPushAction:(id)sender {
// 声明回调 从头像页面返回当前页面时更新标题
DetailCallback block = ^(){
self.title = @"Info Checked";
};
NSDictionary *params = @{@"img":[UIImage imageNamed:@"1"],@"block":block};
NSString *url = [kRouteDetaiPushWithIcon stringByAppendingString:@"?num=1&orderid=10001"];

// 调用服务
UIViewController *vc = [Router handleURL:url complexParams:params completion:^(id result) {
self.title = @"Updated";
}];
[self.navigationController pushViewController:vc animated:YES];
}
@end
  • 调用方使用约定好的URL字符串指明要调用的服务接口;
  • 将简单的参数以URL.query的形式拼接到URL中;
  • 对于复杂的参数,可包装到字典params中;
  • 回调函数中定义好自己的业务逻辑,作为completion参数;
  • 通过Router调用服务,传入之前的参数,等待接收并处理返回值即可。
3.5.远程服务调用

所谓远程调用,就是别的应用向我们的应用发起的服务调用,大致过程如下:

  • 别的应用通过[[UIApplication sharedApplication] openURL:url]传递指定格式的URL到我们的应用;
  • 我们的应用在下面的回调中接收URL,经过Router查询和调用与URL绑定的服务Block:
1
2
3
4
5
6
7
8
9
10
11
- (BOOL)application:(UIApplication *)app 
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
BOOL canHandle = [Router canHandleUrl:url.absoluteString];
if (canHandle) {
[Router handleURL:url.absoluteString complexParams:nil completion:^(id result) {
}];
}
return canHandle;
}

远程调用时传递的URL一定要按照约定的格式拼接;

基于URL的远程调用有个缺点,即URL中参数的类型将会受到限制,图片、数据、block等类型的参数将无法直接拼接到URL后面。

4.流程总结

  • 将组件对外提供的服务以Block的形式进行封装;
  • 定义URL以表示组件对外提供的某个接口;
  • 应用启动阶段在全局字典中保存URL与Block的映射关系;
  • 简单参数拼接到URL中,复杂参数包装到complexParams字典中;
  • 服务调用方传入URL,由Router查询并调用与之绑定的Block;

5.目录结构

整个工程的目录结构如下:

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
.
├── HelModules+URL
│   ├── AppDelegate.h
│   ├── AppDelegate.m
│   ├── Assets.xcassets
│   │   ├── AppIcon.appiconset
│   │   └── Contents.json
│   ├── Base.lproj
│   │   ├── LaunchScreen.storyboard
│   │   └── Main.storyboard
│   ├── Common
│   │   ├── Defines.h
│   │   └── PrefixHeader.pch
│   ├── Detail
│   │   ├── DetailViewController.h
│   │   └── DetailViewController.m
│   ├── Home
│   │   ├── HomeViewController.h
│   │   └── HomeViewController.m
│   ├── Icons
│   │   ├── 1@2x.png
│   │   └── 1@3x.png
│   ├── Info.plist
│   ├── Routers
│   │   ├── Router.h
│   │   └── Router.m
│   └── main.m

6.Bifrost

整个URL解耦方案的核心是Router类:

  • 提供注册接口,将URL与Block的映射关系保存到全局字典中;
  • 根据URL查询并调用对应Block。

这里着重推荐一下有赞开源的组件化库”#Bifrost“,它提供了对Router完善的封装。

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
90
91
92
93
94
95
96
97
// Bifrost+Router.h
#import "Bifrost.h"

#define BFComplete(Params, Result) [Bifrost completeWithParameters:Params result:Result]

// default keys in the parameters of BifrostRouteHandler
extern NSString * _Nonnull const kBifrostRouteURL; //the key for the raw url
extern NSString * _Nonnull const kBifrostRouteCompletion; //the key for the completion block.

/**
The handler for a binded url

@param parameters containers above 2 keys and parameters from the query string and complexParams
@return the obj returned by the handler
*/
typedef _Nullable id (^BifrostRouteHandler)( NSDictionary * _Nullable parameters);

/**
The completion block to be invoked at the end of the router handler block

@param result completion result. defaultly it is the returned object of the BifrostRouteHandler.
*/
typedef void (^BifrostRouteCompletion)(_Nullable id result);

@interface Bifrost (Router)

/**
The method to bind a URL to handler

@param urlStr The URL string. Only scheme, host and api path will be used here.
Its query string will be ignore here.
@param handler the handler block.
The BifrostRouteCompletion should be invoked at the end of the block
*/
+ (void)bindURL:(nonnull NSString *)urlStr toHandler:(nonnull BifrostRouteHandler)handler;

/**
The method to unbind a URL

@param urlStr The URL string. Only scheme, host and api path will be used here.
Its query string will be ignore here.
*/
+ (void)unbindURL:(nonnull NSString *)urlStr;

/**
Method to unbind all URLs
*/
+ (void)unbindAllURLs;

/**
The method to check whether a url can be handled

@param urlStr The URL string. Only scheme, host and api path will be used here.
Its query string will be ignore here.
*/
+ (BOOL)canHandleURL:(nonnull NSString *)urlStr;

/**
Method to handle the URL

@param urlStr URL string
@return the returned object of the url's BifrostRouteHandler
*/
+ (nullable id)handleURL:(nonnull NSString *)urlStr;

/**
Method to handle the url with completion block

@param urlStr URL string
@param completion The completion block
@return the returned object of the url's BifrostRouteHandler
*/
+ (nullable id)handleURL:(nonnull NSString *)urlStr
completion:(nullable BifrostRouteCompletion)completion;

/**
The method to handle URL with complex parameters and completion block

@param urlStr URL string
@param complexParams complex parameters that can't be put in the url query strings
@param completion The completion block
@return the returned object of the url's BifrostRouteHandler
*/
+ (nullable id)handleURL:(nonnull NSString *)urlStr
complexParams:(nullable NSDictionary*)complexParams
completion:(nullable BifrostRouteCompletion)completion;

/**
Invoke the completion block in the parameters of BifrostRouteHandler.
Recommend to use macro BFComplete for convenient.

@param params parameters of BifrostRouteHandler
@param result the result for the BifrostRouteCompletion
*/
+ (void)completeWithParameters:(nullable NSDictionary*)params result:(_Nullable id)result;

@end

从头文件中可以看到,Bifrost的主要功能包括:

  • 绑定URL-Block;
  • 根据URL处理简单参数、复杂参数、带回调的服务调用;
  • 解除单个或所有URL与Block的映射;
  • 检测某个URL是否绑定了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
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#import "Bifrost+Router.h"

#define BFLog(msg) NSLog(@"[Bifrost] %@", (msg))
#define BFKey(URL) [Bifrost keyForURL:URL]

NSString *const kBifrostRouteURL = @"kBifrostRouteURL";
NSString *const kBifrostRouteCompletion = @"kBifrostRouteCompletion";

@implementation Bifrost (Router)

// 从带参数的URL中取出.host和.path部分,作为绑定block所用的键
+ (nonnull NSString*)keyForURL:(nonnull NSString*)urlStr {
NSURL *URL = [NSURL URLWithString:urlStr];
NSString *key = [NSString stringWithFormat:@"%@%@", URL.host, URL.path];
return key;
}

// 取出URL.query部分,以&分割参数,重新组成字典
+ (nullable NSDictionary*)parametersInURL:(nonnull NSString*)urlStr {
NSURL *URL = [NSURL URLWithString:urlStr];
NSMutableDictionary *params = nil;
NSString *query = URL.query;
if(query.length > 0) {
params = [NSMutableDictionary dictionary];
NSArray *list = [query componentsSeparatedByString:@"&"];
for (NSString *param in list) {
NSArray *elts = [param componentsSeparatedByString:@"="];
if([elts count] < 2) continue;
NSString *decodedStr = [[elts lastObject] stringByRemovingPercentEncoding];
[params setObject:decodedStr forKey:[elts firstObject]];
}
}
return params;
}

// 静态字典,全局一份,保存URL与Block的映射关系
+ (NSMutableDictionary*)routes {
@synchronized (self) {
static NSMutableDictionary *_routes = nil;
if (!_routes) {
_routes = [NSMutableDictionary dictionary];
}
return _routes;
}
}

// 保存URL与Block的映射关系
+ (void)bindURL:(nonnull NSString *)urlStr toHandler:(nonnull BifrostRouteHandler)handler {
[self.routes setObject:handler forKey:BFKey(urlStr)];
}

// 解除URL与Block的映射关系
+ (void)unbindURL:(nonnull NSString *)urlStr {
[self.routes removeObjectForKey:BFKey(urlStr)];
}

// 清空所有的映射
+ (void)unbindAllURLs {
[self.routes removeAllObjects];
}

// 根据URL查询已绑定的Block
+ (nullable BifrostRouteHandler)handlerForURL:(nonnull NSString *)urlStr {
return [self.routes objectForKey:BFKey(urlStr)];
}

// 检测URL是否绑定了Block
+ (BOOL)canHandleURL:(nonnull NSString *)urlStr {
if (urlStr.length == 0) {
return NO;
}
if ([self handlerForURL:urlStr]) {
return YES;
} else {
return NO;
}
}

// 查询并调用URL绑定的block,url中包含了简单的参数
+ (nullable id)handleURL:(nonnull NSString *)urlStr {
return [self handleURL:urlStr complexParams:nil completion:nil];
}

// 查询并调用URL绑定的block,url中包含了简单的参数,同时附带回调函数
+ (nullable id)handleURL:(nonnull NSString *)urlStr
completion:(nullable BifrostRouteCompletion)completion {
return [self handleURL:urlStr complexParams:nil completion:completion];
}

// 查询并调用URL绑定的block,url中包含了简单的参数,复杂参数包含在complexParams字典中,同时附带回调函数
+ (nullable id)handleURL:(nonnull NSString *)urlStr
complexParams:(nullable NSDictionary*)complexParams
completion:(nullable BifrostRouteCompletion)completion {
id obj = nil;
@try {
BifrostRouteHandler handler = [self handlerForURL:urlStr];
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithDictionary:complexParams];
[params addEntriesFromDictionary:[self.class parametersInURL:urlStr]];
[params setObject:urlStr forKey:kBifrostRouteURL];
if (completion) {
[params setObject:completion forKey:kBifrostRouteCompletion];
}
if (!handler) {
NSString *reason = [NSString stringWithFormat:@"Cannot find handler for route url %@", urlStr];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setValue:@(BFExceptionUrlHandlerNotFound) forKey:kBifrostExceptionCode];
[userInfo setValue:urlStr forKey:kBifrostExceptionURLStr];
[userInfo setValue:params forKey:kBifrostExceptionURLParams];
NSException *exception = [[NSException alloc] initWithName:BifrostExceptionName
reason:reason
userInfo:userInfo];
BifrostExceptionHandler handler = [self getExceptionHandler];
if (handler) {
obj = handler(exception);
}
BFLog(reason);
} else {
obj = handler(params);
}
} @catch (NSException *exception) {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:exception.userInfo];
[userInfo setValue:@(BFExceptionDefaultCode) forKey:kBifrostExceptionCode];
[userInfo setValue:urlStr forKey:kBifrostExceptionURLStr];
[userInfo setValue:complexParams forKey:kBifrostExceptionURLParams];
NSException *ex = [[NSException alloc] initWithName:exception.name
reason:exception.reason
userInfo:userInfo];
BifrostExceptionHandler handler = [self getExceptionHandler];
if (handler) {
obj = handler(ex);
}
BFLog(exception.reason);
} @finally {
return obj;
}
}

// 从参数列表中查询并调用回调函数
+ (void)completeWithParameters:(nullable NSDictionary*)params result:(_Nullable id)result {
BifrostRouteCompletion completion = params[kBifrostRouteCompletion];
if (completion) {
completion(result);
}
}

@end

库的实现并不复杂,各接口的作用已经在上面标明。它提供了异常情况的报错,同时URL和参数的处理也值得借鉴。

7.方案评价

无论是我们自己的Router还是有赞的Bifrost,其实现组件化的思路都是一样的:都是URL与Block的绑定和路由。

优点:

  • 巧妙的利用了URL的通用性,支持多端统一跳转;
  • 在应用启动阶段即完URL与成服务block的绑定;
  • 组件之间通过Router进行通信,不再需要引用对方的头文件;

缺点:

  • 远程调用时,只支持简单参数;
  • 本地调用时,传递复杂参数只能包装到complexParams字典中,不够灵活;
  • 组件间需要传递实体数据时,还需要导入对方的实体头文件,还是有依赖。

综合来看,在进行组件化时,基于URL-Block的路由方案只适合用来进行简单的本地和远程界面跳转,但其Router路由的思路是值得借鉴的。下一篇将继续介绍基于反射原理的组件化方案~


相关参考:

#©URL+Block方案demo

#©反射方案demo

#©服务协议注册方案demo

#©有赞Bifrost


组件化-URL路由方案
https://davidlii.cn/2020/09/03/modules-url.html
作者
Davidli
发布于
2020年9月3日
许可协议