iOS 架构

MVC

Model持有数据,View显示与用户交互的界面,而Controller被定义成最小的可重用单元,负责协调Model与View之间的交互。

但在实际项目中,却因业务的复杂使其变得臃肿,因为它们经常被混杂到View的生命周期中,因此很难说View和ViewController是分离的。

实例

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
/* Model */
@interface StudentModel : NSObject

@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *gendor;
@property(nonatomic, copy) NSString *brith;
@property(nonatomic, copy) NSString *classNo;

@end

/* View */
@interface StudentCell : UITableViewCell

// 创建 Cell 视图包含的内容,Cell 使用 StoryBoard 创建
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *gendorLabel;
@property (weak, nonatomic) IBOutlet UILabel *brithLabel;
@property (weak, nonatomic) IBOutlet UILabel *classNoLabel;

@end

/* Controller */
@interface StudentController()

@property (nonatomic, strong) NSArray *myDataArray;

@end

@implementation StudentController

- (void)viewDidLoad {
[super viewDidLoad];

// ...创建 StudentModel 集,从网络经处理
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [self.myDataArray count];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 80;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

StudentCell *cell = [tableView dequeueReusableCellWithIdentifier:@"StudentCell" forIndexPath:indexPath];

StudentModel *studentModel = self.myDataArray[indexPath.row];
cell.nameLabel.text = studentModel.name;
cell.gendorLabel = studentModel.gendor;
cell.brithLabel = studentModel.brith;
cell.classNoLabel = studentModel.classNo;

return cell;
}

@end

不足

传统App中,Model数据只定义了Model的属性,复杂的业务数据处理逻辑只能硬塞给Controller,而Controller需要处理View传来的所有交互,最终导致了控制器成了垃圾箱,越来越不可维护。

为了给Controller瘦身,后来衍生出MVVM和MVP

MVVM

MVVM把Controller当成View,View和Model之间没有紧耦合。ViewModel负责业务处理和数据转化,就是把原来Controller的业务逻辑和页面逻辑等剥离出来放到ViewModel层。

实例

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
/* Model */
@interface StudentModel : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *gendor;
@property (nonatomic, copy) NSString *brith;
@property (nonatomic, copy) NSString *classNo;

@end

/* ViewModel */
@protocol StudentViewModelDelegate

- (void)finishedSetupStudentModels;

- (void)finishedUpdateStudentModels;

@end

@class StudentModel;
@interface StudentViewModel: NSObject

@property (nonatomic, weak) id<StudentViewModelDelegate> delegate;

- (void)setupStudentModels;

- (void)updateStudentModelsWithPage:(NSInteger)page;

- (NSInteger)numberOfRowsInSection:(NSInteger)section;
- (StudentModel *)dataWithCellRow:(NSInteger)row;

@end

@interface StudentViewModel()

@property (nonatomic, strong) NSArray *myDataArray;

@end

@implementation StudentViewModel

- (void)setUpStudentModels {
// ...创建 StudentModel 集,从网络经处理
if (self.delegate) {
[self.delegate finishedSetupStudentModels];
}
// ...
}

- (void)updateStudentModelsWithPage:(NSInteger)page {
// ...创建 StudentModel 集,从网络经处理
if (self.delegate) {
[self.delegate finishedUpdateStudentModels];
}
// ...
}

- (NSInteger)numberOfRowsInSection:(NSInteger)section {
return [self.myDataArray count];
}

- (StudentModel *)dataWithCellRow:(NSInteger)index {
return self.myDataArray[row];
}

@end

/* View */
@interface StudentCell : UITableViewCell

// 创建 Cell 视图包含的内容,Cell 使用 StoryBoard 创建
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *gendorLabel;
@property (weak, nonatomic) IBOutlet UILabel *brithLabel;
@property (weak, nonatomic) IBOutlet UILabel *classNoLabel;

@end

@interface StudentController()

@property (nonatomic, strong) StudentViewModel *studentViewModel;

@end

@implementation StudentController

- (void)viewDidLoad {
[super viewDidLoad];
_studentViewModel = [StudentViewModel new];
[_studentViewModel setUpStudentModels];
_studentViewModel.delegate = self;

// ...
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_studentViewModel numberOfRowsInSection:section];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 80;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

StudentCell *cell = [tableView dequeueReusableCellWithIdentifier:@"StudentCell" forIndexPath:indexPath];

StudentModel *studentModel = [_studentViewModel dataWithCellRow:indexPath.row];

cell.nameLabel.text = studentModel.name;
cell.gendorLabel = studentModel.gendor;
cell.brithLabel = studentModel.brith;
cell.classNoLabel = studentModel.classNo;

return cell;
}

@end

优点

低耦合

View 可以独立于Model变化和修改

可重用性

可以把一些视图逻辑放在一个 ViewModel里面,让很多 View 重用这段视图逻辑

可测试

通常界面是比较难于测试的,而 MVVM 模式可以针对 ViewModel 来进行测试

不足

难被调试

数据绑定使得一个位置的 Bug 被快速传递到别的位置,比较难定位

提高维护成本

组件化

随着公司业务的不断发展,项目需求越来越多,各个业务代码耦合也越来越多传统的MVC或者MVVM架构已经无法高效的管理工程代码,急需要一种新技术来更好地管理工程,而组件化是一种能够解决代码耦合的技术。

但是组件化也会使项目体积变大,冷启动速度变慢,运行性能不如传统的架构等的问题。

MGJRouter

采用url-block方案,通过在启动时注册组件提供的服务,把调用组件使用的url和组件提供的服务block对应起来,保存到内存中。在使用组件的服务时,通过url找到对应的block,然后获取服务。

使用

1
2
3
4
5
6
7
// 如果有可变参数(包括 URL Query Parameter)会被自动解析
[MGJRouter registerURLPattern:@"mgj://search/:query" toHandler:^(NSDictionary *routerParameters) {
NSLog(@"routerParameters[query]:%@", routerParameters[@"query"]); // bicycle
NSLog(@"routerParameters[color]:%@", routerParameters[@"color"]); // red
}];

[MGJRouter openURL:@"mgj://search/bicycle?color=red"];
1
2
3
4
5
6
7
8
9
// 常规使用加字典传参
[MGJRouter registerURLPattern:@"mgj://category/travel" toHandler:^(NSDictionary *routerParameters) {
NSLog(@"routerParameters[MGJRouterParameterUserInfo]:%@", routerParameters[MGJRouterParameterUserInfo]);
// @{@"user_id": @1900}
}];

[MGJRouter openURL:@"mgj://category/travel" withUserInfo:@{@"user_id": @1900} completion:^{
[self appendLog:@"Open 结束,我是 Completion Block"];
}];

不足

组件本身依赖了中间件,且分散注册使的耦合较多

团队合作需要文档说明(URL和参数列表)

网上有说组件多了会造成内存问题是错误的。因为block本身是结构体xxx_block_imp_x(详见“iOS Block分析”),占用20B的内存空间外加外部变量的空间,$2^{20}$个也就占用了20MB而已。

CTMediator

             --------------------------------------
             | [CTMediator sharedInstance]        |
             |                                    |
             |                openUrl:       <<<<<<<<<  (AppDelegate)  <<<<  Call From Other App With URL
             |                                    |
             |                   |                |
             |                   |/               |
             |                                    |
             |                parseUrl            |
             |                                    |
             |                   |                |
.................................|...............................
             |                   |                |
             |                   |/               |
             |                                    |
             |  performTarget:action:params: <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<  Call From Native Module
             |                                    |
             |                   |                |
             |                   |                |
             |                   |/               |
             |                                    |
             |             -------------          |
             |             |           |          |
             |             |  runtime  |          |
             |             |           |          |
             |             -------------          |
             |               .       .            |
             ---------------.---------.------------
                           .           .
                          .             .
                         .               .
                        .                 .
                       .                   .
                      .                     .
                     .                       .
                    .                         .
-------------------.-----------      ----------.---------------------
|                 .           |      |          .                   |
|                .            |      |           .                  |
|               .             |      |            .                 |
|              .              |      |             .                |
|                             |      |                              |
|           Target            |      |           Target             |
|                             |      |                              |
|         /   |   \           |      |         /   |   \            |
|        /    |    \          |      |        /    |    \           |
|                             |      |                              |
|   Action Action Action ...  |      |   Action Action Action ...   |
|                             |      |                              |
|                             |      |                              |
|                             |      |                              |
|Business A                   |      | Business B                   |
-------------------------------      --------------------------------

这幅图是组件化方案的一个简化版架构描述,主要是基于Mediator模式和Target-Action模式,中间采用了runtime来完成调用。这套组件化方案将远程应用调用和本地应用调用做了拆分,而且是由本地应用调用为远程应用调用提供服务。(引用原作者的图和话)

使用

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
//以A、B两个子模块为例(作者示例)
//1.创建 A + Target_A Pod

A
├── A
| ├── A
| │ ├── AViewController.h
| │ └── AViewController.m
| │ ├── Target_A.h
| │ └── Target_A.m

//Target_A.h
@interface Target_A : NSObject

- (UIViewController *)Action_viewController:(NSDictionary *)params;

@end

//Target_A.m
@implementation Target_A

- (UIViewController *)Action_viewController:(NSDictionary *)params {
AViewController *viewController = [[AViewController alloc] init];
return viewController;
}

@end

//2.创建A_Category Pod,依赖CTMediator和A两上pods

A_Category
│ ├── A_Category
│ │ ├── A_Category

//CTMediator+A.h
@interface CTMediator(A)

- (UIViewController *)A_aViewController;

@end

//CTMediator+A.m
@implementation CTMediator

- (UIViewController *)A_aViewController {
return [self performTarget:@"A" action:@"viewController" params:nil shouldCacheTarget:NO];
}

@end

//3.创建 B Pod

B
├── B
| ├── B
| │ ├── BViewController.h
| │ └── BViewController.m
| │ ├── Target_B.h
| │ └── Target_B.m

//...

//4.创建A_Category Pod,依赖CTMediator和A两上pods

B_Category
│ ├── B_Category
│ │ ├── B_Category

//...

//5.使用
[self presentViewController:[[CTMediator sharedInstance] A_aViewController] animated:YES completion:nil];

[[CTMediator sharedInstance] performActionWithUrl:[NSURL URLWithString:@"app://A/viewController"] completion:^(NSDictionary *info) {
UIViewController *viewController = info[@"result"];
[xxx presentViewController:viewController animated:YES completion:nil]
}];

不足

Hard Code,增加Target和Catagory(可忽略)的代码量

performTarget:action:params:shouldCacheTarget会频繁创建和销毁Target对象

当子模块需要调用子模块时,则该子模块就必须依赖CTMediator,这时和MGJRouter是一样的,被子模块依赖。

所以可以改成子模块依赖CTMediator,写Target,通过Target-Action直接调用(直接写其分类不要Target,那么要么依赖别的子模块,要么performSelector自己的方法),这样的话,调用方觉得不爽。

有人会问这样还不如直接通过对象类和方法以及参数来调用了,这样会暴露业务的一些类和方法。

目前本人也没有想到更好的方案,欢迎邮件交流。

-------------本文结束感谢您的阅读-------------